Merge pull request #8211 from f1sherb0y/develop

feat(ai): multi-provider AI integration UI and connectivity test
This commit is contained in:
Jaex 2025-10-18 11:50:48 +03:00 committed by GitHub
commit c83570e59a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 1037 additions and 116 deletions

View File

@ -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);
}
}
}
}

View File

@ -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
}
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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();
}
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<string> 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<string> AnalyzeImage(string imagePath, string prompt, string reasoningEffort, string verbosity)
{
string base64Image = ImageHelpers.ImageFileToBase64(imagePath);
return await AnalyzeImageInternal(base64Image, prompt, reasoningEffort, verbosity);
}
private async Task<string> 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}");
}
}
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#endregion License Information (GPL v3)
using System.Drawing;
using System.Threading.Tasks;
namespace ShareX
{
public interface IAIProvider
{
Task<string> AnalyzeImage(Image image, string prompt, string reasoningEffort, string verbosity);
Task<string> AnalyzeImage(string imagePath, string prompt, string reasoningEffort, string verbosity);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<string> 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<string> AnalyzeImage(string imagePath, string prompt, string reasoningEffort, string verbosity)
{
string base64Image = ImageHelpers.ImageFileToBase64(imagePath);
return await AnalyzeImageInternal(base64Image, prompt, reasoningEffort, verbosity);
}
private async Task<string> 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}");
}
}
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<string> 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<string> AnalyzeImage(string imagePath, string prompt, string reasoningEffort, string verbosity)
{
string base64Image = ImageHelpers.ImageFileToBase64(imagePath);
return await AnalyzeImageInternal(base64Image, prompt, reasoningEffort, verbosity);
}
private async Task<string> 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}");
}
}
}
}
}

View File

@ -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);
}
}
}
}