diff --git a/ShareX.HelpersLib/Helpers/ImageHelpers.cs b/ShareX.HelpersLib/Helpers/ImageHelpers.cs
index 5ebf5175c..9443183b1 100644
--- a/ShareX.HelpersLib/Helpers/ImageHelpers.cs
+++ b/ShareX.HelpersLib/Helpers/ImageHelpers.cs
@@ -3077,5 +3077,21 @@ namespace ShareX.HelpersLib
}
}
}
+
+ public static string ImageToBase64(Image image, ImageFormat format)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ image.Save(ms, format);
+ byte[] imageBytes = ms.ToArray();
+ return Convert.ToBase64String(imageBytes);
+ }
+ }
+
+ public static string ImageFileToBase64(string path)
+ {
+ byte[] imageBytes = File.ReadAllBytes(path);
+ return Convert.ToBase64String(imageBytes);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/ShareX/Tools/AI/AIForm.cs b/ShareX/Tools/AI/AIForm.cs
index 4e0a16978..b44075503 100644
--- a/ShareX/Tools/AI/AIForm.cs
+++ b/ShareX/Tools/AI/AIForm.cs
@@ -58,16 +58,32 @@ namespace ShareX
private void UpdateControls()
{
- btnAnalyze.Enabled = !string.IsNullOrEmpty(Options.ChatGPTAPIKey) && (!string.IsNullOrEmpty(txtImage.Text) || pbImage.Image != null);
+ btnAnalyze.Enabled = IsAPIKeyAvailable() && (!string.IsNullOrEmpty(txtImage.Text) || pbImage.Image != null);
btnResultCopy.Enabled = !string.IsNullOrEmpty(txtResult.Text);
}
+ private bool IsAPIKeyAvailable()
+ {
+ switch (Options.Provider)
+ {
+ case AIProvider.OpenAI:
+ case AIProvider.Custom:
+ return !string.IsNullOrEmpty(Options.OpenAIAPIKey);
+ case AIProvider.Gemini:
+ return !string.IsNullOrEmpty(Options.GeminiAPIKey);
+ case AIProvider.OpenRouter:
+ return !string.IsNullOrEmpty(Options.OpenRouterAPIKey);
+ default:
+ return false;
+ }
+ }
+
private async Task AnalyzeImage()
{
txtResult.Clear();
lblTimer.ResetText();
- if (!string.IsNullOrEmpty(Options.ChatGPTAPIKey) && (!string.IsNullOrEmpty(txtImage.Text) || pbImage.Image != null))
+ if (IsAPIKeyAvailable() && (!string.IsNullOrEmpty(txtImage.Text) || pbImage.Image != null))
{
btnAnalyze.Enabled = false;
Cursor = Cursors.WaitCursor;
@@ -78,16 +94,16 @@ namespace ShareX
try
{
- ChatGPT chatGPT = new ChatGPT(Options.ChatGPTAPIKey, Options.Model);
+ IAIProvider provider = AIProviderFactory.GetProvider(Options);
string result = null;
string imagePath = txtImage.Text;
if (!string.IsNullOrEmpty(imagePath))
{
- result = await chatGPT.AnalyzeImage(imagePath, Options.Input, Options.ReasoningEffort, Options.Verbosity);
+ result = await provider.AnalyzeImage(imagePath, Options.Input, Options.ReasoningEffort, Options.Verbosity);
}
else if (pbImage.Image != null)
{
- result = await chatGPT.AnalyzeImage(pbImage.Image, Options.Input, Options.ReasoningEffort, Options.Verbosity);
+ result = await provider.AnalyzeImage(pbImage.Image, Options.Input, Options.ReasoningEffort, Options.Verbosity);
}
if (!string.IsNullOrEmpty(result))
@@ -217,4 +233,4 @@ namespace ShareX
}
}
}
-}
\ No newline at end of file
+}
diff --git a/ShareX/Tools/AI/AIOptions.cs b/ShareX/Tools/AI/AIOptions.cs
index 5d8601c49..0dbf548ec 100644
--- a/ShareX/Tools/AI/AIOptions.cs
+++ b/ShareX/Tools/AI/AIOptions.cs
@@ -27,11 +27,31 @@ using ShareX.HelpersLib;
namespace ShareX
{
+ public enum AIProvider
+ {
+ OpenAI,
+ Gemini,
+ OpenRouter,
+ Custom
+ }
+
public class AIOptions
{
- public string Model { get; set; } = "gpt-5-mini";
+ public AIProvider Provider { get; set; } = AIProvider.OpenAI;
+
[JsonEncrypt]
- public string ChatGPTAPIKey { get; set; }
+ public string OpenAIAPIKey { get; set; }
+ public string OpenAIModel { get; set; } = "gpt-4o-mini";
+ public string OpenAICustomURL { get; set; }
+
+ [JsonEncrypt]
+ public string GeminiAPIKey { get; set; }
+ public string GeminiModel { get; set; } = "gemini-1.5-flash-latest";
+
+ [JsonEncrypt]
+ public string OpenRouterAPIKey { get; set; }
+ public string OpenRouterModel { get; set; } = "google/gemini-flash-1.5";
+
public string ReasoningEffort { get; set; } = "minimal";
public string Verbosity { get; set; } = "medium";
public string Input { get; set; } = "What is in this image?";
@@ -39,4 +59,4 @@ namespace ShareX
public bool AutoStartAnalyze { get; set; } = true;
public bool AutoCopyResult { get; set; } = false;
}
-}
\ No newline at end of file
+}
diff --git a/ShareX/Tools/AI/AIOptionsForm.Designer.cs b/ShareX/Tools/AI/AIOptionsForm.Designer.cs
index d969e8e5b..f32dc0370 100644
--- a/ShareX/Tools/AI/AIOptionsForm.Designer.cs
+++ b/ShareX/Tools/AI/AIOptionsForm.Designer.cs
@@ -29,134 +29,412 @@
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AIOptionsForm));
- btnAPIKeyHelp = new System.Windows.Forms.Button();
- cbReasoningEffort = new System.Windows.Forms.ComboBox();
- lblReasoningEffort = new System.Windows.Forms.Label();
- txtAPIKey = new System.Windows.Forms.TextBox();
- lblAPIKey = new System.Windows.Forms.Label();
- cbModel = new System.Windows.Forms.ComboBox();
- lblModel = new System.Windows.Forms.Label();
- cbAutoStartRegion = new System.Windows.Forms.CheckBox();
- cbAutoStartAnalyze = new System.Windows.Forms.CheckBox();
- btnOK = new System.Windows.Forms.Button();
- btnCancel = new System.Windows.Forms.Button();
- cbAutoCopyResult = new System.Windows.Forms.CheckBox();
- cbVerbosity = new System.Windows.Forms.ComboBox();
- lblVerbosity = new System.Windows.Forms.Label();
- SuspendLayout();
+ this.btnAPIKeyHelp = new System.Windows.Forms.Button();
+ this.cbReasoningEffort = new System.Windows.Forms.ComboBox();
+ this.lblReasoningEffort = new System.Windows.Forms.Label();
+ this.cbAutoStartRegion = new System.Windows.Forms.CheckBox();
+ this.cbAutoStartAnalyze = new System.Windows.Forms.CheckBox();
+ this.btnOK = new System.Windows.Forms.Button();
+ this.btnCancel = new System.Windows.Forms.Button();
+ this.cbAutoCopyResult = new System.Windows.Forms.CheckBox();
+ this.cbVerbosity = new System.Windows.Forms.ComboBox();
+ this.lblVerbosity = new System.Windows.Forms.Label();
+ this.lblProvider = new System.Windows.Forms.Label();
+ this.cbProvider = new System.Windows.Forms.ComboBox();
+ this.gbOpenAI = new System.Windows.Forms.GroupBox();
+ this.lblOpenAICustomURL = new System.Windows.Forms.Label();
+ this.txtOpenAICustomURL = new System.Windows.Forms.TextBox();
+ this.lblOpenAIModel = new System.Windows.Forms.Label();
+ this.cbOpenAIModel = new System.Windows.Forms.ComboBox();
+ this.lblOpenAIAPIKey = new System.Windows.Forms.Label();
+ this.txtOpenAIAPIKey = new System.Windows.Forms.TextBox();
+ this.gbGemini = new System.Windows.Forms.GroupBox();
+ this.lblGeminiModel = new System.Windows.Forms.Label();
+ this.cbGeminiModel = new System.Windows.Forms.ComboBox();
+ this.lblGeminiAPIKey = new System.Windows.Forms.Label();
+ this.txtGeminiAPIKey = new System.Windows.Forms.TextBox();
+ this.gbOpenRouter = new System.Windows.Forms.GroupBox();
+ this.lblOpenRouterModel = new System.Windows.Forms.Label();
+ this.cbOpenRouterModel = new System.Windows.Forms.ComboBox();
+ this.lblOpenRouterAPIKey = new System.Windows.Forms.Label();
+ this.txtOpenRouterAPIKey = new System.Windows.Forms.TextBox();
+ this.gbOpenAI.SuspendLayout();
+ this.gbGemini.SuspendLayout();
+ this.gbOpenRouter.SuspendLayout();
+ this.SuspendLayout();
//
// btnAPIKeyHelp
//
- btnAPIKeyHelp.Image = Properties.Resources.question;
- resources.ApplyResources(btnAPIKeyHelp, "btnAPIKeyHelp");
- btnAPIKeyHelp.Name = "btnAPIKeyHelp";
- btnAPIKeyHelp.UseVisualStyleBackColor = true;
- btnAPIKeyHelp.Click += btnAPIKeyHelp_Click;
+ this.btnAPIKeyHelp.Image = global::ShareX.Properties.Resources.question;
+ resources.ApplyResources(this.btnAPIKeyHelp, "btnAPIKeyHelp");
+ this.btnAPIKeyHelp.Name = "btnAPIKeyHelp";
+ this.btnAPIKeyHelp.UseVisualStyleBackColor = true;
+ this.btnAPIKeyHelp.Click += new System.EventHandler(this.btnAPIKeyHelp_Click);
//
// cbReasoningEffort
//
- cbReasoningEffort.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
- cbReasoningEffort.FormattingEnabled = true;
- cbReasoningEffort.Items.AddRange(new object[] { resources.GetString("cbReasoningEffort.Items"), resources.GetString("cbReasoningEffort.Items1"), resources.GetString("cbReasoningEffort.Items2"), resources.GetString("cbReasoningEffort.Items3") });
- resources.ApplyResources(cbReasoningEffort, "cbReasoningEffort");
- cbReasoningEffort.Name = "cbReasoningEffort";
+ this.cbReasoningEffort.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.cbReasoningEffort.FormattingEnabled = true;
+ this.cbReasoningEffort.Items.AddRange(new object[] {
+ resources.GetString("cbReasoningEffort.Items"),
+ resources.GetString("cbReasoningEffort.Items1"),
+ resources.GetString("cbReasoningEffort.Items2"),
+ resources.GetString("cbReasoningEffort.Items3")});
+ resources.ApplyResources(this.cbReasoningEffort, "cbReasoningEffort");
+ this.cbReasoningEffort.Name = "cbReasoningEffort";
//
// lblReasoningEffort
//
- resources.ApplyResources(lblReasoningEffort, "lblReasoningEffort");
- lblReasoningEffort.Name = "lblReasoningEffort";
- //
- // txtAPIKey
- //
- resources.ApplyResources(txtAPIKey, "txtAPIKey");
- txtAPIKey.Name = "txtAPIKey";
- txtAPIKey.UseSystemPasswordChar = true;
- //
- // lblAPIKey
- //
- resources.ApplyResources(lblAPIKey, "lblAPIKey");
- lblAPIKey.Name = "lblAPIKey";
- //
- // cbModel
- //
- cbModel.FormattingEnabled = true;
- cbModel.Items.AddRange(new object[] { resources.GetString("cbModel.Items"), resources.GetString("cbModel.Items1"), resources.GetString("cbModel.Items2") });
- resources.ApplyResources(cbModel, "cbModel");
- cbModel.Name = "cbModel";
- //
- // lblModel
- //
- resources.ApplyResources(lblModel, "lblModel");
- lblModel.Name = "lblModel";
+ resources.ApplyResources(this.lblReasoningEffort, "lblReasoningEffort");
+ this.lblReasoningEffort.Name = "lblReasoningEffort";
//
// cbAutoStartRegion
//
- resources.ApplyResources(cbAutoStartRegion, "cbAutoStartRegion");
- cbAutoStartRegion.Name = "cbAutoStartRegion";
- cbAutoStartRegion.UseVisualStyleBackColor = true;
+ resources.ApplyResources(this.cbAutoStartRegion, "cbAutoStartRegion");
+ this.cbAutoStartRegion.Name = "cbAutoStartRegion";
+ this.cbAutoStartRegion.UseVisualStyleBackColor = true;
//
// cbAutoStartAnalyze
//
- resources.ApplyResources(cbAutoStartAnalyze, "cbAutoStartAnalyze");
- cbAutoStartAnalyze.Name = "cbAutoStartAnalyze";
- cbAutoStartAnalyze.UseVisualStyleBackColor = true;
+ resources.ApplyResources(this.cbAutoStartAnalyze, "cbAutoStartAnalyze");
+ this.cbAutoStartAnalyze.Name = "cbAutoStartAnalyze";
+ this.cbAutoStartAnalyze.UseVisualStyleBackColor = true;
//
// btnOK
//
- resources.ApplyResources(btnOK, "btnOK");
- btnOK.Name = "btnOK";
- btnOK.UseVisualStyleBackColor = true;
- btnOK.Click += btnOK_Click;
+ resources.ApplyResources(this.btnOK, "btnOK");
+ this.btnOK.Name = "btnOK";
+ this.btnOK.UseVisualStyleBackColor = true;
+ this.btnOK.Click += new System.EventHandler(this.btnOK_Click);
//
// btnCancel
//
- resources.ApplyResources(btnCancel, "btnCancel");
- btnCancel.Name = "btnCancel";
- btnCancel.UseVisualStyleBackColor = true;
- btnCancel.Click += btnCancel_Click;
+ resources.ApplyResources(this.btnCancel, "btnCancel");
+ this.btnCancel.Name = "btnCancel";
+ this.btnCancel.UseVisualStyleBackColor = true;
+ this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// cbAutoCopyResult
//
- resources.ApplyResources(cbAutoCopyResult, "cbAutoCopyResult");
- cbAutoCopyResult.Name = "cbAutoCopyResult";
- cbAutoCopyResult.UseVisualStyleBackColor = true;
+ resources.ApplyResources(this.cbAutoCopyResult, "cbAutoCopyResult");
+ this.cbAutoCopyResult.Name = "cbAutoCopyResult";
+ this.cbAutoCopyResult.UseVisualStyleBackColor = true;
//
// cbVerbosity
//
- cbVerbosity.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
- cbVerbosity.FormattingEnabled = true;
- cbVerbosity.Items.AddRange(new object[] { resources.GetString("cbVerbosity.Items"), resources.GetString("cbVerbosity.Items1"), resources.GetString("cbVerbosity.Items2") });
- resources.ApplyResources(cbVerbosity, "cbVerbosity");
- cbVerbosity.Name = "cbVerbosity";
+ this.cbVerbosity.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.cbVerbosity.FormattingEnabled = true;
+ this.cbVerbosity.Items.AddRange(new object[] {
+ resources.GetString("cbVerbosity.Items"),
+ resources.GetString("cbVerbosity.Items1"),
+ resources.GetString("cbVerbosity.Items2")});
+ resources.ApplyResources(this.cbVerbosity, "cbVerbosity");
+ this.cbVerbosity.Name = "cbVerbosity";
//
// lblVerbosity
//
- resources.ApplyResources(lblVerbosity, "lblVerbosity");
- lblVerbosity.Name = "lblVerbosity";
+ resources.ApplyResources(this.lblVerbosity, "lblVerbosity");
+ this.lblVerbosity.Name = "lblVerbosity";
+ //
+ // lblProvider
+ //
+ resources.ApplyResources(this.lblProvider, "lblProvider");
+ this.lblProvider.Name = "lblProvider";
+ this.lblProvider.Text = "Select Provider:";
+ //
+ // cbProvider
+ //
+ this.cbProvider.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.cbProvider.FormattingEnabled = true;
+ resources.ApplyResources(this.cbProvider, "cbProvider");
+ this.cbProvider.Name = "cbProvider";
+ this.cbProvider.SelectedIndexChanged += new System.EventHandler(this.cbProvider_SelectedIndexChanged);
+ //
+ // gbOpenAI
+ //
+ this.gbOpenAI.Controls.Add(this.lblOpenAICustomURL);
+ this.gbOpenAI.Controls.Add(this.txtOpenAICustomURL);
+ this.gbOpenAI.Controls.Add(this.lblOpenAIModel);
+ this.gbOpenAI.Controls.Add(this.cbOpenAIModel);
+ this.gbOpenAI.Controls.Add(this.lblOpenAIAPIKey);
+ this.gbOpenAI.Controls.Add(this.txtOpenAIAPIKey);
+ resources.ApplyResources(this.gbOpenAI, "gbOpenAI");
+ this.gbOpenAI.Name = "gbOpenAI";
+ this.gbOpenAI.TabStop = false;
+ this.gbOpenAI.Text = "Configure Provider";
+ //
+ // lblOpenAICustomURL
+ //
+ resources.ApplyResources(this.lblOpenAICustomURL, "lblOpenAICustomURL");
+ this.lblOpenAICustomURL.Name = "lblOpenAICustomURL";
+ //
+ // txtOpenAICustomURL
+ //
+ resources.ApplyResources(this.txtOpenAICustomURL, "txtOpenAICustomURL");
+ this.txtOpenAICustomURL.Name = "txtOpenAICustomURL";
+ //
+ // lblOpenAIModel
+ //
+ resources.ApplyResources(this.lblOpenAIModel, "lblOpenAIModel");
+ this.lblOpenAIModel.Name = "lblOpenAIModel";
+ //
+ // cbOpenAIModel
+ //
+ this.cbOpenAIModel.FormattingEnabled = true;
+ resources.ApplyResources(this.cbOpenAIModel, "cbOpenAIModel");
+ this.cbOpenAIModel.Name = "cbOpenAIModel";
+ //
+ // lblOpenAIAPIKey
+ //
+ resources.ApplyResources(this.lblOpenAIAPIKey, "lblOpenAIAPIKey");
+ this.lblOpenAIAPIKey.Name = "lblOpenAIAPIKey";
+ //
+ // txtOpenAIAPIKey
+ //
+ resources.ApplyResources(this.txtOpenAIAPIKey, "txtOpenAIAPIKey");
+ this.txtOpenAIAPIKey.Name = "txtOpenAIAPIKey";
+ this.txtOpenAIAPIKey.UseSystemPasswordChar = true;
+ //
+ // gbGemini
+ //
+ this.gbGemini.Controls.Add(this.lblGeminiModel);
+ this.gbGemini.Controls.Add(this.cbGeminiModel);
+ this.gbGemini.Controls.Add(this.lblGeminiAPIKey);
+ this.gbGemini.Controls.Add(this.txtGeminiAPIKey);
+ resources.ApplyResources(this.gbGemini, "gbGemini");
+ this.gbGemini.Name = "gbGemini";
+ this.gbGemini.TabStop = false;
+ this.gbGemini.Text = "Configure Provider";
+ //
+ // lblGeminiModel
+ //
+ resources.ApplyResources(this.lblGeminiModel, "lblGeminiModel");
+ this.lblGeminiModel.Name = "lblGeminiModel";
+ //
+ // cbGeminiModel
+ //
+ this.cbGeminiModel.FormattingEnabled = true;
+ resources.ApplyResources(this.cbGeminiModel, "cbGeminiModel");
+ this.cbGeminiModel.Name = "cbGeminiModel";
+ //
+ // lblGeminiAPIKey
+ //
+ resources.ApplyResources(this.lblGeminiAPIKey, "lblGeminiAPIKey");
+ this.lblGeminiAPIKey.Name = "lblGeminiAPIKey";
+ //
+ // txtGeminiAPIKey
+ //
+ resources.ApplyResources(this.txtGeminiAPIKey, "txtGeminiAPIKey");
+ this.txtGeminiAPIKey.Name = "txtGeminiAPIKey";
+ this.txtGeminiAPIKey.UseSystemPasswordChar = true;
+ //
+ // gbOpenRouter
+ //
+ this.gbOpenRouter.Controls.Add(this.lblOpenRouterModel);
+ this.gbOpenRouter.Controls.Add(this.cbOpenRouterModel);
+ this.gbOpenRouter.Controls.Add(this.lblOpenRouterAPIKey);
+ this.gbOpenRouter.Controls.Add(this.txtOpenRouterAPIKey);
+ resources.ApplyResources(this.gbOpenRouter, "gbOpenRouter");
+ this.gbOpenRouter.Name = "gbOpenRouter";
+ this.gbOpenRouter.TabStop = false;
+ this.gbOpenRouter.Text = "Configure Provider";
+ //
+ // lblOpenRouterModel
+ //
+ resources.ApplyResources(this.lblOpenRouterModel, "lblOpenRouterModel");
+ this.lblOpenRouterModel.Name = "lblOpenRouterModel";
+ //
+ // cbOpenRouterModel
+ //
+ this.cbOpenRouterModel.FormattingEnabled = true;
+ resources.ApplyResources(this.cbOpenRouterModel, "cbOpenRouterModel");
+ this.cbOpenRouterModel.Name = "cbOpenRouterModel";
+ //
+ // lblOpenRouterAPIKey
+ //
+ resources.ApplyResources(this.lblOpenRouterAPIKey, "lblOpenRouterAPIKey");
+ this.lblOpenRouterAPIKey.Name = "lblOpenRouterAPIKey";
+ //
+ // txtOpenRouterAPIKey
+ //
+ resources.ApplyResources(this.txtOpenRouterAPIKey, "txtOpenRouterAPIKey");
+ this.txtOpenRouterAPIKey.Name = "txtOpenRouterAPIKey";
+ this.txtOpenRouterAPIKey.UseSystemPasswordChar = true;
//
// AIOptionsForm
//
resources.ApplyResources(this, "$this");
- AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- Controls.Add(lblVerbosity);
- Controls.Add(cbVerbosity);
- Controls.Add(cbAutoCopyResult);
- Controls.Add(btnCancel);
- Controls.Add(btnOK);
- Controls.Add(cbAutoStartAnalyze);
- Controls.Add(cbAutoStartRegion);
- Controls.Add(btnAPIKeyHelp);
- Controls.Add(cbReasoningEffort);
- Controls.Add(lblReasoningEffort);
- Controls.Add(txtAPIKey);
- Controls.Add(lblAPIKey);
- Controls.Add(cbModel);
- Controls.Add(lblModel);
- FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
- MaximizeBox = false;
- Name = "AIOptionsForm";
- ResumeLayout(false);
- PerformLayout();
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.Controls.Add(this.lblProvider);
+ this.Controls.Add(this.cbProvider);
+ this.Controls.Add(this.gbOpenAI);
+ this.Controls.Add(this.gbGemini);
+ this.Controls.Add(this.gbOpenRouter);
+ this.Controls.Add(this.lblVerbosity);
+ this.Controls.Add(this.cbVerbosity);
+ this.Controls.Add(this.cbAutoCopyResult);
+ this.Controls.Add(this.btnCancel);
+ this.Controls.Add(this.btnOK);
+ this.Controls.Add(this.cbAutoStartAnalyze);
+ this.Controls.Add(this.cbAutoStartRegion);
+ this.Controls.Add(this.btnAPIKeyHelp);
+ this.Controls.Add(this.cbReasoningEffort);
+ this.Controls.Add(this.lblReasoningEffort);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+ this.MaximizeBox = false;
+ this.Name = "AIOptionsForm";
+ this.gbOpenAI.ResumeLayout(false);
+ this.gbOpenAI.PerformLayout();
+ this.gbGemini.ResumeLayout(false);
+ this.gbGemini.PerformLayout();
+ this.gbOpenRouter.ResumeLayout(false);
+ this.gbOpenRouter.PerformLayout();
+
+ // Explicit manual layout overrides to ensure consistent UI regardless of stale .resx
+ // Increase form size to accommodate provider configuration group and other options
+ this.ClientSize = new System.Drawing.Size(420, 520);
+
+ // Provider selection at the top
+ this.lblProvider.AutoSize = true;
+ this.lblProvider.Location = new System.Drawing.Point(16, 16);
+
+ this.cbProvider.Location = new System.Drawing.Point(16, 36);
+ this.cbProvider.Size = new System.Drawing.Size(this.ClientSize.Width - 56, this.cbProvider.Height);
+ this.cbProvider.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
+
+ // Help button next to provider selector
+ this.btnAPIKeyHelp.Location = new System.Drawing.Point(this.ClientSize.Width - 16 - this.btnAPIKeyHelp.Width, 32);
+ this.btnAPIKeyHelp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+ // Recompute provider ComboBox width to avoid overlap with help button (leave 8px gap)
+ this.cbProvider.Size = new System.Drawing.Size(this.btnAPIKeyHelp.Left - 8 - this.cbProvider.Left, this.cbProvider.Height);
+
+ // Group boxes share the same location/size; visibility toggled by provider selection
+ int groupLeft = 16;
+ int groupTop = 72;
+ int groupWidth = this.ClientSize.Width - 32;
+ int groupHeight = 210;
+
+ this.gbOpenAI.Location = new System.Drawing.Point(groupLeft, groupTop);
+ this.gbOpenAI.Size = new System.Drawing.Size(groupWidth, groupHeight);
+ this.gbOpenAI.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
+
+ this.gbGemini.Location = new System.Drawing.Point(groupLeft, groupTop);
+ this.gbGemini.Size = new System.Drawing.Size(groupWidth, groupHeight);
+ this.gbGemini.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
+
+ this.gbOpenRouter.Location = new System.Drawing.Point(groupLeft, groupTop);
+ this.gbOpenRouter.Size = new System.Drawing.Size(groupWidth, groupHeight);
+ this.gbOpenRouter.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
+
+ // OpenAI group contents
+ this.lblOpenAIAPIKey.Location = new System.Drawing.Point(12, 24);
+ this.txtOpenAIAPIKey.Location = new System.Drawing.Point(12, 44);
+ this.txtOpenAIAPIKey.Size = new System.Drawing.Size(groupWidth - 24, this.txtOpenAIAPIKey.Height);
+
+ this.lblOpenAIModel.Location = new System.Drawing.Point(12, 80);
+ this.cbOpenAIModel.Location = new System.Drawing.Point(12, 100);
+ this.cbOpenAIModel.Size = new System.Drawing.Size(groupWidth - 24, this.cbOpenAIModel.Height);
+
+ this.lblOpenAICustomURL.Location = new System.Drawing.Point(12, 136);
+ this.txtOpenAICustomURL.Location = new System.Drawing.Point(12, 156);
+ this.txtOpenAICustomURL.Size = new System.Drawing.Size(groupWidth - 24, this.txtOpenAICustomURL.Height);
+
+ // Ensure labels have text and autosize; inputs expand horizontally
+ this.lblOpenAIAPIKey.AutoSize = true;
+ this.lblOpenAIAPIKey.Text = "API key:";
+ this.txtOpenAIAPIKey.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
+
+ this.lblOpenAIModel.AutoSize = true;
+ this.lblOpenAIModel.Text = "Model:";
+ this.cbOpenAIModel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
+ this.cbOpenAIModel.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDown;
+
+ this.lblOpenAICustomURL.AutoSize = true;
+ this.lblOpenAICustomURL.Text = "Custom base URL:";
+ this.txtOpenAICustomURL.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
+
+ // Gemini group contents
+ this.lblGeminiAPIKey.Location = new System.Drawing.Point(12, 24);
+ this.txtGeminiAPIKey.Location = new System.Drawing.Point(12, 44);
+ this.txtGeminiAPIKey.Size = new System.Drawing.Size(groupWidth - 24, this.txtGeminiAPIKey.Height);
+
+ this.lblGeminiModel.Location = new System.Drawing.Point(12, 80);
+ this.cbGeminiModel.Location = new System.Drawing.Point(12, 100);
+ this.cbGeminiModel.Size = new System.Drawing.Size(groupWidth - 24, this.cbGeminiModel.Height);
+
+ // Ensure labels have text and autosize; inputs expand horizontally (Gemini)
+ this.lblGeminiAPIKey.AutoSize = true;
+ this.lblGeminiAPIKey.Text = "API key:";
+ this.txtGeminiAPIKey.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
+
+ this.lblGeminiModel.AutoSize = true;
+ this.lblGeminiModel.Text = "Model:";
+ this.cbGeminiModel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
+ this.cbGeminiModel.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDown;
+
+ // OpenRouter group contents
+ this.lblOpenRouterAPIKey.Location = new System.Drawing.Point(12, 24);
+ this.txtOpenRouterAPIKey.Location = new System.Drawing.Point(12, 44);
+ this.txtOpenRouterAPIKey.Size = new System.Drawing.Size(groupWidth - 24, this.txtOpenRouterAPIKey.Height);
+
+ this.lblOpenRouterModel.Location = new System.Drawing.Point(12, 80);
+ this.cbOpenRouterModel.Location = new System.Drawing.Point(12, 100);
+ this.cbOpenRouterModel.Size = new System.Drawing.Size(groupWidth - 24, this.cbOpenRouterModel.Height);
+
+ // Ensure labels have text and autosize; inputs expand horizontally (OpenRouter)
+ this.lblOpenRouterAPIKey.AutoSize = true;
+ this.lblOpenRouterAPIKey.Text = "API key:";
+ this.txtOpenRouterAPIKey.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
+
+ this.lblOpenRouterModel.AutoSize = true;
+ this.lblOpenRouterModel.Text = "Model:";
+ this.cbOpenRouterModel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
+ this.cbOpenRouterModel.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDown;
+
+ // Remaining options stacked under the provider config group
+ // Test connection button and status
+ this.btnTestConnection = new System.Windows.Forms.Button();
+ this.lblTestStatus = new System.Windows.Forms.Label();
+
+ this.btnTestConnection.Text = "Test";
+ this.btnTestConnection.Size = new System.Drawing.Size(80, 28);
+ this.btnTestConnection.Location = new System.Drawing.Point(this.ClientSize.Width - 16 - this.btnTestConnection.Width, groupTop + groupHeight + 8);
+ this.btnTestConnection.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
+ this.btnTestConnection.Click += new System.EventHandler(this.btnTestConnection_Click);
+
+ this.lblTestStatus.AutoSize = true;
+ this.lblTestStatus.Location = new System.Drawing.Point(16, groupTop + groupHeight + 12);
+ this.lblTestStatus.Text = "";
+ this.Controls.Add(this.lblTestStatus);
+ this.Controls.Add(this.btnTestConnection);
+
+ int y = groupTop + groupHeight + 12 + 36;
+
+ this.lblReasoningEffort.Location = new System.Drawing.Point(16, y);
+ this.cbReasoningEffort.Location = new System.Drawing.Point(16, y + 24);
+ this.cbReasoningEffort.Size = new System.Drawing.Size(this.ClientSize.Width - 32, this.cbReasoningEffort.Height);
+ this.cbReasoningEffort.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
+ y += 24 + 24 + 12;
+
+ this.lblVerbosity.Location = new System.Drawing.Point(16, y);
+ this.cbVerbosity.Location = new System.Drawing.Point(16, y + 24);
+ this.cbVerbosity.Size = new System.Drawing.Size(this.ClientSize.Width - 32, this.cbVerbosity.Height);
+ this.cbVerbosity.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right)));
+ y += 24 + 24 + 12;
+
+ this.cbAutoStartRegion.Location = new System.Drawing.Point(16, y);
+ this.cbAutoStartAnalyze.Location = new System.Drawing.Point(16, y + 24);
+ this.cbAutoCopyResult.Location = new System.Drawing.Point(16, y + 48);
+
+ // OK/Cancel at bottom-right
+ this.btnOK.Location = new System.Drawing.Point(this.ClientSize.Width - 224, this.ClientSize.Height - 48);
+ this.btnCancel.Location = new System.Drawing.Point(this.ClientSize.Width - 112, this.ClientSize.Height - 48);
+ this.btnOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+ this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
+
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
}
#endregion
@@ -164,10 +442,6 @@
private System.Windows.Forms.Button btnAPIKeyHelp;
private System.Windows.Forms.ComboBox cbReasoningEffort;
private System.Windows.Forms.Label lblReasoningEffort;
- private System.Windows.Forms.TextBox txtAPIKey;
- private System.Windows.Forms.Label lblAPIKey;
- private System.Windows.Forms.ComboBox cbModel;
- private System.Windows.Forms.Label lblModel;
private System.Windows.Forms.CheckBox cbAutoStartRegion;
private System.Windows.Forms.CheckBox cbAutoStartAnalyze;
private System.Windows.Forms.Button btnOK;
@@ -175,5 +449,26 @@
private System.Windows.Forms.CheckBox cbAutoCopyResult;
private System.Windows.Forms.ComboBox cbVerbosity;
private System.Windows.Forms.Label lblVerbosity;
+ private System.Windows.Forms.Label lblProvider;
+ private System.Windows.Forms.ComboBox cbProvider;
+ private System.Windows.Forms.GroupBox gbOpenAI;
+ private System.Windows.Forms.Label lblOpenAIModel;
+ private System.Windows.Forms.ComboBox cbOpenAIModel;
+ private System.Windows.Forms.Label lblOpenAIAPIKey;
+ private System.Windows.Forms.TextBox txtOpenAIAPIKey;
+ private System.Windows.Forms.GroupBox gbGemini;
+ private System.Windows.Forms.Label lblGeminiModel;
+ private System.Windows.Forms.ComboBox cbGeminiModel;
+ private System.Windows.Forms.Label lblGeminiAPIKey;
+ private System.Windows.Forms.TextBox txtGeminiAPIKey;
+ private System.Windows.Forms.GroupBox gbOpenRouter;
+ private System.Windows.Forms.Label lblOpenRouterModel;
+ private System.Windows.Forms.ComboBox cbOpenRouterModel;
+ private System.Windows.Forms.Label lblOpenRouterAPIKey;
+ private System.Windows.Forms.TextBox txtOpenRouterAPIKey;
+ private System.Windows.Forms.Label lblOpenAICustomURL;
+ private System.Windows.Forms.TextBox txtOpenAICustomURL;
+ private System.Windows.Forms.Button btnTestConnection;
+ private System.Windows.Forms.Label lblTestStatus;
}
-}
\ No newline at end of file
+}
diff --git a/ShareX/Tools/AI/AIOptionsForm.cs b/ShareX/Tools/AI/AIOptionsForm.cs
index 99dab4a89..529652824 100644
--- a/ShareX/Tools/AI/AIOptionsForm.cs
+++ b/ShareX/Tools/AI/AIOptionsForm.cs
@@ -25,6 +25,9 @@
using ShareX.HelpersLib;
using System;
+using System.Drawing;
+using System.Net.Http;
+using System.Net.Http.Headers;
using System.Windows.Forms;
namespace ShareX
@@ -39,13 +42,28 @@ namespace ShareX
InitializeComponent();
ShareXResources.ApplyTheme(this, true);
+ foreach (AIProvider provider in Enum.GetValues(typeof(AIProvider)))
+ {
+ cbProvider.Items.Add(provider.ToString());
+ }
+
LoadOptions();
}
private void LoadOptions()
{
- cbModel.Text = Options.Model;
- txtAPIKey.Text = Options.ChatGPTAPIKey;
+ cbProvider.SelectedIndex = (int)Options.Provider;
+
+ txtOpenAIAPIKey.Text = Options.OpenAIAPIKey;
+ cbOpenAIModel.Text = Options.OpenAIModel;
+ txtOpenAICustomURL.Text = Options.OpenAICustomURL;
+
+ txtGeminiAPIKey.Text = Options.GeminiAPIKey;
+ cbGeminiModel.Text = Options.GeminiModel;
+
+ txtOpenRouterAPIKey.Text = Options.OpenRouterAPIKey;
+ cbOpenRouterModel.Text = Options.OpenRouterModel;
+
int index = cbReasoningEffort.FindStringExact(Options.ReasoningEffort);
if (index >= 0)
{
@@ -71,8 +89,18 @@ namespace ShareX
private void SaveOptions()
{
- Options.Model = cbModel.Text;
- Options.ChatGPTAPIKey = txtAPIKey.Text;
+ Options.Provider = (AIProvider)cbProvider.SelectedIndex;
+
+ Options.OpenAIAPIKey = txtOpenAIAPIKey.Text;
+ Options.OpenAIModel = cbOpenAIModel.Text;
+ Options.OpenAICustomURL = txtOpenAICustomURL.Text;
+
+ Options.GeminiAPIKey = txtGeminiAPIKey.Text;
+ Options.GeminiModel = cbGeminiModel.Text;
+
+ Options.OpenRouterAPIKey = txtOpenRouterAPIKey.Text;
+ Options.OpenRouterModel = cbOpenRouterModel.Text;
+
Options.ReasoningEffort = cbReasoningEffort.Text;
Options.Verbosity = cbVerbosity.Text;
Options.AutoStartRegion = cbAutoStartRegion.Checked;
@@ -80,9 +108,159 @@ namespace ShareX
Options.AutoCopyResult = cbAutoCopyResult.Checked;
}
+ private void cbProvider_SelectedIndexChanged(object sender, EventArgs e)
+ {
+ gbOpenAI.Visible = false;
+ gbGemini.Visible = false;
+ gbOpenRouter.Visible = false;
+ lblOpenAICustomURL.Visible = false;
+ txtOpenAICustomURL.Visible = false;
+
+ switch ((AIProvider)cbProvider.SelectedIndex)
+ {
+ case AIProvider.OpenAI:
+ gbOpenAI.Visible = true;
+ break;
+ case AIProvider.Custom:
+ gbOpenAI.Visible = true;
+ lblOpenAICustomURL.Visible = true;
+ txtOpenAICustomURL.Visible = true;
+ break;
+ case AIProvider.Gemini:
+ gbGemini.Visible = true;
+ break;
+ case AIProvider.OpenRouter:
+ gbOpenRouter.Visible = true;
+ break;
+ }
+ }
+
+ private async void btnTestConnection_Click(object sender, EventArgs e)
+ {
+ try
+ {
+ btnTestConnection.Enabled = false;
+ lblTestStatus.ForeColor = Color.Gold;
+ lblTestStatus.Text = "Testing...";
+
+ var client = HttpClientFactory.Create();
+ HttpRequestMessage req = null;
+ var provider = (AIProvider)cbProvider.SelectedIndex;
+
+ switch (provider)
+ {
+ case AIProvider.OpenAI:
+ case AIProvider.Custom:
+ {
+ var key = txtOpenAIAPIKey.Text?.Trim();
+ if (string.IsNullOrEmpty(key))
+ {
+ lblTestStatus.ForeColor = Color.IndianRed;
+ lblTestStatus.Text = "Missing OpenAI API key.";
+ return;
+ }
+
+ var baseUrl = txtOpenAICustomURL.Text;
+ if (string.IsNullOrWhiteSpace(baseUrl))
+ baseUrl = "https://api.openai.com/v1";
+ baseUrl = baseUrl.Trim().TrimEnd('/');
+
+ var url = baseUrl + "/models";
+ req = new HttpRequestMessage(HttpMethod.Get, url);
+ req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
+ break;
+ }
+
+ case AIProvider.Gemini:
+ {
+ var key = txtGeminiAPIKey.Text?.Trim();
+ if (string.IsNullOrEmpty(key))
+ {
+ lblTestStatus.ForeColor = Color.IndianRed;
+ lblTestStatus.Text = "Missing Gemini API key.";
+ return;
+ }
+
+ var url = "https://generativelanguage.googleapis.com/v1beta/models?key=" + Uri.EscapeDataString(key);
+ req = new HttpRequestMessage(HttpMethod.Get, url);
+ break;
+ }
+
+ case AIProvider.OpenRouter:
+ {
+ var key = txtOpenRouterAPIKey.Text?.Trim();
+ if (string.IsNullOrEmpty(key))
+ {
+ lblTestStatus.ForeColor = Color.IndianRed;
+ lblTestStatus.Text = "Missing OpenRouter API key.";
+ return;
+ }
+
+ var url = "https://openrouter.ai/api/v1/models";
+ req = new HttpRequestMessage(HttpMethod.Get, url);
+ req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
+ break;
+ }
+
+ default:
+ lblTestStatus.ForeColor = Color.IndianRed;
+ lblTestStatus.Text = "Select a provider first.";
+ return;
+ }
+
+ using (req)
+ using (var resp = await client.SendAsync(req))
+ {
+ var ok = (int)resp.StatusCode >= 200 && (int)resp.StatusCode < 300;
+ var text = await resp.Content.ReadAsStringAsync();
+
+ if (ok)
+ {
+ lblTestStatus.ForeColor = Color.LimeGreen;
+ lblTestStatus.Text = "Connection OK";
+ }
+ else
+ {
+ lblTestStatus.ForeColor = Color.IndianRed;
+ string summary = resp.ReasonPhrase;
+ if (string.IsNullOrWhiteSpace(summary)) summary = resp.StatusCode.ToString();
+ if (!string.IsNullOrWhiteSpace(text))
+ {
+ if (text.Length > 200) text = text.Substring(0, 200) + "...";
+ summary += ": " + text;
+ }
+ lblTestStatus.Text = summary;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ lblTestStatus.ForeColor = Color.IndianRed;
+ lblTestStatus.Text = ex.Message;
+ }
+ finally
+ {
+ btnTestConnection.Enabled = true;
+ }
+ }
+
private void btnAPIKeyHelp_Click(object sender, EventArgs e)
{
- URLHelpers.OpenURL("https://platform.openai.com/api-keys");
+ string url = "";
+ switch ((AIProvider)cbProvider.SelectedIndex)
+ {
+ case AIProvider.OpenAI:
+ case AIProvider.Custom:
+ url = "https://platform.openai.com/api-keys";
+ break;
+ case AIProvider.Gemini:
+ url = "https://aistudio.google.com/app/apikey";
+ break;
+ case AIProvider.OpenRouter:
+ url = "https://openrouter.ai/keys";
+ break;
+ }
+ URLHelpers.OpenURL(url);
}
private void btnOK_Click(object sender, EventArgs e)
@@ -99,4 +277,4 @@ namespace ShareX
Close();
}
}
-}
\ No newline at end of file
+}
diff --git a/ShareX/Tools/AI/AIProviderFactory.cs b/ShareX/Tools/AI/AIProviderFactory.cs
new file mode 100644
index 000000000..97b693352
--- /dev/null
+++ b/ShareX/Tools/AI/AIProviderFactory.cs
@@ -0,0 +1,49 @@
+#region License Information (GPL v3)
+
+/*
+ ShareX - A program that allows you to take screenshots and share any file type
+ Copyright (c) 2007-2025 ShareX Team
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ Optionally you can also view the license at .
+*/
+
+#endregion License Information (GPL v3)
+
+using System;
+
+namespace ShareX
+{
+ public static class AIProviderFactory
+ {
+ public static IAIProvider GetProvider(AIOptions options)
+ {
+ switch (options.Provider)
+ {
+ case AIProvider.OpenAI:
+ return new OpenAIProvider(options.OpenAIAPIKey, options.OpenAIModel);
+ case AIProvider.Custom:
+ return new OpenAIProvider(options.OpenAIAPIKey, options.OpenAIModel, options.OpenAICustomURL);
+ case AIProvider.Gemini:
+ return new GeminiProvider(options.GeminiAPIKey, options.GeminiModel);
+ case AIProvider.OpenRouter:
+ return new OpenRouterProvider(options.OpenRouterAPIKey, options.OpenRouterModel);
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+ }
+}
diff --git a/ShareX/Tools/AI/GeminiProvider.cs b/ShareX/Tools/AI/GeminiProvider.cs
new file mode 100644
index 000000000..70cfa0b0e
--- /dev/null
+++ b/ShareX/Tools/AI/GeminiProvider.cs
@@ -0,0 +1,98 @@
+#region License Information (GPL v3)
+
+/*
+ ShareX - A program that allows you to take screenshots and share any file type
+ Copyright (c) 2007-2025 ShareX Team
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ Optionally you can also view the license at .
+*/
+
+#endregion License Information (GPL v3)
+
+using Newtonsoft.Json;
+using ShareX.HelpersLib;
+using System;
+using System.Drawing;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ShareX
+{
+ public class GeminiProvider : IAIProvider
+ {
+ private readonly string apiKey;
+ private readonly string model;
+
+ public GeminiProvider(string apiKey, string model)
+ {
+ this.apiKey = apiKey;
+ this.model = model;
+ }
+
+ public async Task AnalyzeImage(Image image, string prompt, string reasoningEffort, string verbosity)
+ {
+ string base64Image = ImageHelpers.ImageToBase64(image, System.Drawing.Imaging.ImageFormat.Png);
+ return await AnalyzeImageInternal(base64Image, prompt, reasoningEffort, verbosity);
+ }
+
+ public async Task AnalyzeImage(string imagePath, string prompt, string reasoningEffort, string verbosity)
+ {
+ string base64Image = ImageHelpers.ImageFileToBase64(imagePath);
+ return await AnalyzeImageInternal(base64Image, prompt, reasoningEffort, verbosity);
+ }
+
+ private async Task AnalyzeImageInternal(string base64Image, string prompt, string reasoningEffort, string verbosity)
+ {
+ using (HttpClient client = new HttpClient())
+ {
+ var payload = new
+ {
+ contents = new[]
+ {
+ new
+ {
+ parts = new object[]
+ {
+ new { text = prompt },
+ new { inline_data = new { mime_type = "image/png", data = base64Image } }
+ }
+ }
+ }
+ };
+
+ string jsonPayload = JsonConvert.SerializeObject(payload);
+ StringContent content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
+
+ string url = $"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={apiKey}";
+
+ HttpResponseMessage response = await client.PostAsync(url, content);
+ string responseString = await response.Content.ReadAsStringAsync();
+
+ if (response.IsSuccessStatusCode)
+ {
+ dynamic responseObject = JsonConvert.DeserializeObject(responseString);
+ return responseObject.candidates[0].content.parts[0].text;
+ }
+ else
+ {
+ throw new Exception($"Error from Gemini API: {responseString}");
+ }
+ }
+ }
+ }
+}
diff --git a/ShareX/Tools/AI/IAIProvider.cs b/ShareX/Tools/AI/IAIProvider.cs
new file mode 100644
index 000000000..ee843d22d
--- /dev/null
+++ b/ShareX/Tools/AI/IAIProvider.cs
@@ -0,0 +1,36 @@
+#region License Information (GPL v3)
+
+/*
+ ShareX - A program that allows you to take screenshots and share any file type
+ Copyright (c) 2007-2025 ShareX Team
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ Optionally you can also view the license at .
+*/
+
+#endregion License Information (GPL v3)
+
+using System.Drawing;
+using System.Threading.Tasks;
+
+namespace ShareX
+{
+ public interface IAIProvider
+ {
+ Task AnalyzeImage(Image image, string prompt, string reasoningEffort, string verbosity);
+ Task AnalyzeImage(string imagePath, string prompt, string reasoningEffort, string verbosity);
+ }
+}
diff --git a/ShareX/Tools/AI/OpenAIProvider.cs b/ShareX/Tools/AI/OpenAIProvider.cs
new file mode 100644
index 000000000..41114e391
--- /dev/null
+++ b/ShareX/Tools/AI/OpenAIProvider.cs
@@ -0,0 +1,107 @@
+#region License Information (GPL v3)
+
+/*
+ ShareX - A program that allows you to take screenshots and share any file type
+ Copyright (c) 2007-2025 ShareX Team
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ Optionally you can also view the license at .
+*/
+
+#endregion License Information (GPL v3)
+
+using Newtonsoft.Json;
+using ShareX.HelpersLib;
+using System;
+using System.Drawing;
+using System.IO;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ShareX
+{
+ public class OpenAIProvider : IAIProvider
+ {
+ private readonly string apiKey;
+ private readonly string model;
+ private readonly string customUrl;
+
+ public OpenAIProvider(string apiKey, string model, string customUrl = null)
+ {
+ this.apiKey = apiKey;
+ this.model = model;
+ this.customUrl = customUrl;
+ }
+
+ public async Task AnalyzeImage(Image image, string prompt, string reasoningEffort, string verbosity)
+ {
+ string base64Image = ImageHelpers.ImageToBase64(image, System.Drawing.Imaging.ImageFormat.Png);
+ return await AnalyzeImageInternal(base64Image, prompt, reasoningEffort, verbosity);
+ }
+
+ public async Task AnalyzeImage(string imagePath, string prompt, string reasoningEffort, string verbosity)
+ {
+ string base64Image = ImageHelpers.ImageFileToBase64(imagePath);
+ return await AnalyzeImageInternal(base64Image, prompt, reasoningEffort, verbosity);
+ }
+
+ private async Task AnalyzeImageInternal(string base64Image, string prompt, string reasoningEffort, string verbosity)
+ {
+ using (HttpClient client = new HttpClient())
+ {
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
+
+ var payload = new
+ {
+ model = this.model,
+ messages = new[]
+ {
+ new
+ {
+ role = "user",
+ content = new object[]
+ {
+ new { type = "text", text = prompt },
+ new { type = "image_url", image_url = new { url = $"data:image/png;base64,{base64Image}" } }
+ }
+ }
+ },
+ max_tokens = 1024
+ };
+
+ string jsonPayload = JsonConvert.SerializeObject(payload);
+ StringContent content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
+
+ string url = string.IsNullOrEmpty(customUrl) ? "https://api.openai.com/v1/chat/completions" : customUrl;
+
+ HttpResponseMessage response = await client.PostAsync(url, content);
+ string responseString = await response.Content.ReadAsStringAsync();
+
+ if (response.IsSuccessStatusCode)
+ {
+ dynamic responseObject = JsonConvert.DeserializeObject(responseString);
+ return responseObject.choices[0].message.content;
+ }
+ else
+ {
+ throw new Exception($"Error from OpenAI API: {responseString}");
+ }
+ }
+ }
+ }
+}
diff --git a/ShareX/Tools/AI/OpenRouterProvider.cs b/ShareX/Tools/AI/OpenRouterProvider.cs
new file mode 100644
index 000000000..8f7fbb93b
--- /dev/null
+++ b/ShareX/Tools/AI/OpenRouterProvider.cs
@@ -0,0 +1,104 @@
+#region License Information (GPL v3)
+
+/*
+ ShareX - A program that allows you to take screenshots and share any file type
+ Copyright (c) 2007-2025 ShareX Team
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ Optionally you can also view the license at .
+*/
+
+#endregion License Information (GPL v3)
+
+using Newtonsoft.Json;
+using ShareX.HelpersLib;
+using System;
+using System.Drawing;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ShareX
+{
+ public class OpenRouterProvider : IAIProvider
+ {
+ private readonly string apiKey;
+ private readonly string model;
+
+ public OpenRouterProvider(string apiKey, string model)
+ {
+ this.apiKey = apiKey;
+ this.model = model;
+ }
+
+ public async Task AnalyzeImage(Image image, string prompt, string reasoningEffort, string verbosity)
+ {
+ string base64Image = ImageHelpers.ImageToBase64(image, System.Drawing.Imaging.ImageFormat.Png);
+ return await AnalyzeImageInternal(base64Image, prompt, reasoningEffort, verbosity);
+ }
+
+ public async Task AnalyzeImage(string imagePath, string prompt, string reasoningEffort, string verbosity)
+ {
+ string base64Image = ImageHelpers.ImageFileToBase64(imagePath);
+ return await AnalyzeImageInternal(base64Image, prompt, reasoningEffort, verbosity);
+ }
+
+ private async Task AnalyzeImageInternal(string base64Image, string prompt, string reasoningEffort, string verbosity)
+ {
+ using (HttpClient client = new HttpClient())
+ {
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
+
+ var payload = new
+ {
+ model = this.model,
+ messages = new[]
+ {
+ new
+ {
+ role = "user",
+ content = new object[]
+ {
+ new { type = "text", text = prompt },
+ new { type = "image_url", image_url = new { url = $"data:image/png;base64,{base64Image}" } }
+ }
+ }
+ },
+ max_tokens = 1024
+ };
+
+ string jsonPayload = JsonConvert.SerializeObject(payload);
+ StringContent content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
+
+ const string url = "https://openrouter.ai/api/v1/chat/completions";
+
+ HttpResponseMessage response = await client.PostAsync(url, content);
+ string responseString = await response.Content.ReadAsStringAsync();
+
+ if (response.IsSuccessStatusCode)
+ {
+ dynamic responseObject = JsonConvert.DeserializeObject(responseString);
+ return responseObject.choices[0].message.content;
+ }
+ else
+ {
+ throw new Exception($"Error from OpenRouter API: {responseString}");
+ }
+ }
+ }
+ }
+}
diff --git a/ShareX/Tools/OCR/OCRForm.cs b/ShareX/Tools/OCR/OCRForm.cs
index bfe3334a9..5ef498b8e 100644
--- a/ShareX/Tools/OCR/OCRForm.cs
+++ b/ShareX/Tools/OCR/OCRForm.cs
@@ -50,6 +50,7 @@ namespace ShareX
InitializeComponent();
ShareXResources.ApplyTheme(this, true);
+
OCRLanguage[] languages = OCRHelper.AvailableLanguages.OrderBy(x => x.DisplayName).ToArray();
if (languages.Length > 0)
@@ -133,6 +134,7 @@ namespace ShareX
cbSingleLine.Enabled = !busy;
}
+
private async Task OCR(Bitmap bmp)
{
if (bmp != null && !string.IsNullOrEmpty(Options.Language))
@@ -271,4 +273,4 @@ namespace ShareX
btnOpenServiceLink.Enabled = btnCopyAll.Enabled = !string.IsNullOrEmpty(Result);
}
}
-}
\ No newline at end of file
+}