mirror of
https://github.com/ShareX/ShareX.git
synced 2025-12-27 23:44:52 +00:00
feat(ai): multi-provider AI integration UI and connectivity test
AIOptionsForm: manual layout to fix overlaps, consistent provider GroupBox sizing, visible labels (AutoSize), editable model ComboBoxes (DropDown), and added Test button with color-coded status. Connectivity: Implement provider testing using ShareX.HelpersLib.HttpClientFactory for OpenAI/Custom (/models), Gemini, and OpenRouter endpoints. Providers: Add IAIProvider, AIProviderFactory, OpenAIProvider, GeminiProvider, and OpenRouterProvider. OCR: Revert monospace font experiment and cleanup (no functional change). Misc: Minor adjustments in AIForm, AIOptions, and ImageHelpers.
This commit is contained in:
parent
ca76364c0f
commit
555a97fe7d
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
493
ShareX/Tools/AI/AIOptionsForm.Designer.cs
generated
493
ShareX/Tools/AI/AIOptionsForm.Designer.cs
generated
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
49
ShareX/Tools/AI/AIProviderFactory.cs
Normal file
49
ShareX/Tools/AI/AIProviderFactory.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
ShareX/Tools/AI/GeminiProvider.cs
Normal file
98
ShareX/Tools/AI/GeminiProvider.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
ShareX/Tools/AI/IAIProvider.cs
Normal file
36
ShareX/Tools/AI/IAIProvider.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
107
ShareX/Tools/AI/OpenAIProvider.cs
Normal file
107
ShareX/Tools/AI/OpenAIProvider.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
104
ShareX/Tools/AI/OpenRouterProvider.cs
Normal file
104
ShareX/Tools/AI/OpenRouterProvider.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user