diff --git a/.gitignore b/.gitignore index 9491a2f..72d1290 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,10 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd + +# Large model weights +weights/ + +# Git filter-branch leftovers +.git-rewrite/ \ No newline at end of file diff --git a/Build-Installer.ps1 b/Build-Installer.ps1 new file mode 100644 index 0000000..48e7493 --- /dev/null +++ b/Build-Installer.ps1 @@ -0,0 +1,137 @@ +<# +.SYNOPSIS + Build FlowVision installer using Inno Setup + +.DESCRIPTION + This script builds the FlowVision installer. It requires: + 1. Inno Setup 6 to be installed + 2. The FlowVision project to be built in Release mode + +.EXAMPLE + .\Build-Installer.ps1 +#> + +param( + [string]$InnoSetupPath = "", + [switch]$SkipBuild +) + +$ErrorActionPreference = "Stop" + +Write-Host "=========================================" -ForegroundColor Cyan +Write-Host "FlowVision Installer Builder" -ForegroundColor Cyan +Write-Host "=========================================" -ForegroundColor Cyan +Write-Host "" + +# Find Inno Setup +if (-not $InnoSetupPath) { + $locations = @( + "C:\Program Files (x86)\Inno Setup 6\ISCC.exe", + "C:\Program Files\Inno Setup 6\ISCC.exe", + "C:\Program Files (x86)\Inno Setup 5\ISCC.exe", + "C:\Program Files\Inno Setup 5\ISCC.exe" + ) + + foreach ($loc in $locations) { + if (Test-Path $loc) { + $InnoSetupPath = $loc + break + } + } +} + +if (-not $InnoSetupPath -or -not (Test-Path $InnoSetupPath)) { + Write-Host "ERROR: Inno Setup not found!" -ForegroundColor Red + Write-Host "" + Write-Host "Please install Inno Setup 6 from: https://jrsoftware.org/isdl.php" -ForegroundColor Yellow + Write-Host "" + Write-Host "Or specify the path with: .\Build-Installer.ps1 -InnoSetupPath 'C:\path\to\ISCC.exe'" -ForegroundColor Yellow + exit 1 +} + +Write-Host "Using Inno Setup: $InnoSetupPath" -ForegroundColor Green + +# Build the project first +if (-not $SkipBuild) { + Write-Host "" + Write-Host "Building FlowVision in Release mode..." -ForegroundColor Yellow + + $msbuild = "C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe" + if (-not (Test-Path $msbuild)) { + $msbuild = "C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe" + } + if (-not (Test-Path $msbuild)) { + $msbuild = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe" + } + + if (Test-Path $msbuild) { + & $msbuild "FlowVision\FlowVision.csproj" /t:Build /p:Configuration=Release /v:minimal + if ($LASTEXITCODE -ne 0) { + Write-Host "ERROR: Build failed!" -ForegroundColor Red + exit 1 + } + Write-Host "Build completed successfully!" -ForegroundColor Green + } else { + Write-Host "WARNING: MSBuild not found, skipping build step" -ForegroundColor Yellow + } +} + +# Verify required files exist +Write-Host "" +Write-Host "Verifying build output..." -ForegroundColor Yellow + +$requiredFiles = @( + "FlowVision\bin\Release\FlowVision.exe", + "FlowVision\bin\Release\onnxruntime.dll", + "FlowVision\bin\Release\tesseract50.dll", + "FlowVision\bin\Release\tessdata\eng.traineddata" +) + +$missing = @() +foreach ($file in $requiredFiles) { + if (-not (Test-Path $file)) { + $missing += $file + } +} + +if ($missing.Count -gt 0) { + Write-Host "ERROR: Missing required files:" -ForegroundColor Red + foreach ($file in $missing) { + Write-Host " - $file" -ForegroundColor Red + } + exit 1 +} + +Write-Host "All required files present!" -ForegroundColor Green + +# Create installer output directory +$installerDir = "installer" +if (-not (Test-Path $installerDir)) { + New-Item -ItemType Directory -Path $installerDir | Out-Null +} + +# Build the installer +Write-Host "" +Write-Host "Building installer..." -ForegroundColor Yellow +Write-Host "This may take several minutes due to the large model files." -ForegroundColor Gray + +$issFile = "FlowVision-Installer.iss" +& $InnoSetupPath $issFile + +if ($LASTEXITCODE -ne 0) { + Write-Host "ERROR: Installer build failed!" -ForegroundColor Red + exit 1 +} + +Write-Host "" +Write-Host "=========================================" -ForegroundColor Green +Write-Host "Installer built successfully!" -ForegroundColor Green +Write-Host "=========================================" -ForegroundColor Green +Write-Host "" + +# Show the output +$installer = Get-ChildItem "installer\*.exe" | Sort-Object LastWriteTime -Descending | Select-Object -First 1 +if ($installer) { + Write-Host "Installer: $($installer.FullName)" -ForegroundColor Cyan + Write-Host "Size: $([math]::Round($installer.Length/1MB, 2)) MB" -ForegroundColor Cyan +} diff --git a/FlowVision-Installer.iss b/FlowVision-Installer.iss new file mode 100644 index 0000000..c1118e9 --- /dev/null +++ b/FlowVision-Installer.iss @@ -0,0 +1,91 @@ +; FlowVision Installer Script for Inno Setup +; This script creates a single-file installer that includes all dependencies + +#define MyAppName "FlowVision" +#define MyAppVersion "1.0.0" +#define MyAppPublisher "FlowVision" +#define MyAppExeName "FlowVision.exe" +#define MyAppURL "https://github.com/flowvision" + +[Setup] +; Basic installer settings +AppId={{F8E2D3A4-5B6C-7D8E-9F0A-1B2C3D4E5F6A} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +DefaultDirName={autopf}\{#MyAppName} +DefaultGroupName={#MyAppName} +AllowNoIcons=yes +; Output settings +OutputDir=installer +OutputBaseFilename=FlowVision-Setup-{#MyAppVersion} +; Compression - use LZMA2 for best compression of large files +Compression=lzma2/ultra64 +SolidCompression=yes +LZMAUseSeparateProcess=yes +LZMANumBlockThreads=4 +; UI settings +WizardStyle=modern +SetupIconFile=FlowVision\recursive-control-icon.ico +; Privileges +PrivilegesRequired=lowest +PrivilegesRequiredOverridesAllowed=dialog +; Architecture +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible +; Disk space info +DiskSpanning=no + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Files] +; Main executable (contains embedded managed DLLs and detection model) +Source: "FlowVision\bin\Release\FlowVision.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "FlowVision\bin\Release\FlowVision.exe.config"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist + +; Native DLLs (required - cannot be embedded in .NET exe) +Source: "FlowVision\bin\Release\onnxruntime.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "FlowVision\bin\Release\onnxruntime_providers_shared.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "FlowVision\bin\Release\tesseract50.dll"; DestDir: "{app}"; Flags: ignoreversion +Source: "FlowVision\bin\Release\leptonica-1.82.0.dll"; DestDir: "{app}"; Flags: ignoreversion + +; Any additional DLLs that weren't embedded +Source: "FlowVision\bin\Release\*.dll"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist + +; Tesseract OCR data +Source: "FlowVision\bin\Release\tessdata\*"; DestDir: "{app}\tessdata"; Flags: ignoreversion recursesubdirs createallsubdirs + +; Florence-2 Caption Models (large ONNX files) +Source: "FlowVision\bin\Release\models\*"; DestDir: "{app}\models"; Flags: ignoreversion recursesubdirs createallsubdirs + +; Playwright browser automation files +Source: "FlowVision\bin\Release\.playwright\*"; DestDir: "{app}\.playwright"; Flags: ignoreversion recursesubdirs createallsubdirs skipifsourcedoesntexist + +; Native x64/x86 libraries +Source: "FlowVision\bin\Release\x64\*"; DestDir: "{app}\x64"; Flags: ignoreversion recursesubdirs createallsubdirs skipifsourcedoesntexist +Source: "FlowVision\bin\Release\x86\*"; DestDir: "{app}\x86"; Flags: ignoreversion recursesubdirs createallsubdirs skipifsourcedoesntexist + +; Web UI files (HTML, CSS, JS) +Source: "FlowVision\bin\Release\*.html"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist +Source: "FlowVision\bin\Release\*.css"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist +Source: "FlowVision\bin\Release\*.js"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist + +[Icons] +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" +Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" +Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon + +[Run] +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent + +[Code] +function InitializeSetup(): Boolean; +begin + Result := True; +end; + diff --git a/FlowVision.Tests/FlowVision.Tests.csproj b/FlowVision.Tests/FlowVision.Tests.csproj new file mode 100644 index 0000000..494c5f1 --- /dev/null +++ b/FlowVision.Tests/FlowVision.Tests.csproj @@ -0,0 +1,14 @@ + + + net6.0 + false + + + + + + + + + + diff --git a/FlowVision.Tests/MultiAgentActionerTests.cs b/FlowVision.Tests/MultiAgentActionerTests.cs new file mode 100644 index 0000000..3e0dff5 --- /dev/null +++ b/FlowVision.Tests/MultiAgentActionerTests.cs @@ -0,0 +1,93 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using FlowVision.lib.Classes; +using System; +using System.Reflection; +using System.IO; +using System.Collections.Generic; + +namespace FlowVision.Tests +{ + [TestClass] + public class MultiAgentActionerTests + { + private static string CallExtract(string plan) + { + var actioner = new MultiAgentActioner(null); + var method = typeof(MultiAgentActioner).GetMethod("ExtractActionableStep", BindingFlags.NonPublic | BindingFlags.Instance); + return (string)method.Invoke(actioner, new object[] { plan }); + } + + [TestMethod] + public void ExtractActionableStep_ReturnsFirstActionableLine() + { + string plan = "1. Use WindowSelectionPlugin to list windows\n2. Use ScreenCapturePlugin to capture"; + string result = CallExtract(plan); + Assert.AreEqual("Use WindowSelectionPlugin to list windows", result); + } + + [TestMethod] + public void ExtractActionableStep_SingleLineActionable() + { + string plan = "Use MousePlugin to click start button"; + string result = CallExtract(plan); + Assert.AreEqual(plan, result); + } + + [TestMethod] + public void ExtractActionableStep_ReturnsNullForNonActionable() + { + string plan = "Hello there"; + string result = CallExtract(plan); + Assert.IsNull(result); + } + + private static string ConfigPath(string name) + { + return Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "FlowVision", "Config", $"{name}.json"); + } + + [TestMethod] + public void PlannerPrompt_ContainsTools_WhenDynamicPromptsEnabled() + { + var config = new ToolConfig(); + config.DynamicToolPrompts = true; + config.EnableCMDPlugin = true; // ensure at least one tool + config.SaveConfig("toolsconfig"); + + var actioner = new MultiAgentActioner(null); + actioner.SetChatHistory(new List()); + + var field = typeof(MultiAgentActioner).GetField("plannerHistory", BindingFlags.NonPublic | BindingFlags.Instance); + var history = field.GetValue(actioner); + var enumerator = ((System.Collections.IEnumerable)history).GetEnumerator(); + enumerator.MoveNext(); + var first = enumerator.Current; + string content = (string)first.GetType().GetProperty("Content").GetValue(first); + + Assert.IsTrue(content.Contains("You have access to the following tools")); + } + + [TestMethod] + public void PlannerPrompt_OmitsTools_WhenDynamicPromptsDisabled() + { + var config = new ToolConfig(); + config.DynamicToolPrompts = false; + config.EnableCMDPlugin = true; + config.SaveConfig("toolsconfig"); + + var actioner = new MultiAgentActioner(null); + actioner.SetChatHistory(new List()); + + var field = typeof(MultiAgentActioner).GetField("plannerHistory", BindingFlags.NonPublic | BindingFlags.Instance); + var history = field.GetValue(actioner); + var enumerator = ((System.Collections.IEnumerable)history).GetEnumerator(); + enumerator.MoveNext(); + var first = enumerator.Current; + string content = (string)first.GetType().GetProperty("Content").GetValue(first); + + Assert.IsFalse(content.Contains("You have access to the following tools")); + } + } +} diff --git a/FlowVision.sln b/FlowVision.sln index b1bc0ba..28b6026 100644 --- a/FlowVision.sln +++ b/FlowVision.sln @@ -1,10 +1,12 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.13.35828.75 d17.13 +VisualStudioVersion = 17.13.35828.75 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlowVision", "FlowVision\FlowVision.csproj", "{D0C80BFC-F9E8-4B7D-B3F0-C1A17C0DAB4C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlowVision.Tests", "FlowVision.Tests\FlowVision.Tests.csproj", "{5B7C3BAC-8534-42DC-A2B3-A5E4FEF49F1F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {D0C80BFC-F9E8-4B7D-B3F0-C1A17C0DAB4C}.Debug|Any CPU.Build.0 = Debug|Any CPU {D0C80BFC-F9E8-4B7D-B3F0-C1A17C0DAB4C}.Release|Any CPU.ActiveCfg = Release|Any CPU {D0C80BFC-F9E8-4B7D-B3F0-C1A17C0DAB4C}.Release|Any CPU.Build.0 = Release|Any CPU + {5B7C3BAC-8534-42DC-A2B3-A5E4FEF49F1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B7C3BAC-8534-42DC-A2B3-A5E4FEF49F1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B7C3BAC-8534-42DC-A2B3-A5E4FEF49F1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B7C3BAC-8534-42DC-A2B3-A5E4FEF49F1F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/FlowVision/AIProviderConfigForm.cs b/FlowVision/AIProviderConfigForm.cs new file mode 100644 index 0000000..659ffce --- /dev/null +++ b/FlowVision/AIProviderConfigForm.cs @@ -0,0 +1,789 @@ +using System; +using System.Drawing; +using System.Threading.Tasks; +using System.Windows.Forms; +using FlowVision.lib.Classes; + +namespace FlowVision +{ + /// + /// Unified AI Provider Configuration Form + /// Allows switching between Azure OpenAI, LM Studio, GitHub Models, etc. + /// + public partial class AIProviderConfigForm : Form + { + private string currentModel; + private ComboBox providerComboBox; + private Panel configPanel; + + // Azure OpenAI controls + private Panel azurePanel; + private TextBox azureDeploymentTextBox; + private TextBox azureEndpointTextBox; + private TextBox azureApiKeyTextBox; + private NumericUpDown azureTemperatureUpDown; // Added missing field + + // Gemini controls + private Panel geminiPanel; + private TextBox geminiApiKeyTextBox; + private TextBox geminiModelTextBox; + + // LM Studio controls + private Panel lmStudioPanel; + private TextBox lmStudioEndpointTextBox; + private TextBox lmStudioModelTextBox; + private NumericUpDown lmStudioTemperatureUpDown; + private NumericUpDown lmStudioMaxTokensUpDown; + + // Common controls + private Button saveButton; + private Button testButton; + private Label statusLabel; + private CheckBox enableProviderCheckBox; + + public AIProviderConfigForm(string model) + { + currentModel = model; + InitializeComponent(); + LoadConfiguration(); + } + + private void InitializeComponent() + { + this.Text = $"AI Provider Configuration - {currentModel}"; + this.Size = new Size(650, 600); + this.FormBorderStyle = FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.StartPosition = FormStartPosition.CenterParent; + + int y = 20; + int leftMargin = 20; + int labelWidth = 150; + int controlWidth = 450; + + // Title + var titleLabel = new Label + { + Text = "Choose Your AI Provider", + Font = new Font(this.Font.FontFamily, 14, FontStyle.Bold), + Location = new Point(leftMargin, y), + AutoSize = true + }; + this.Controls.Add(titleLabel); + y += 40; + + // Info + var infoLabel = new Label + { + Text = "Select which AI service to use for this agent. You can switch anytime!", + Location = new Point(leftMargin, y), + Size = new Size(580, 30), + ForeColor = Color.Gray + }; + this.Controls.Add(infoLabel); + y += 40; + + // Provider selector + var providerLabel = new Label + { + Text = "AI Provider:", + Location = new Point(leftMargin, y + 3), + Width = labelWidth + }; + this.Controls.Add(providerLabel); + + providerComboBox = new ComboBox + { + Location = new Point(leftMargin + labelWidth + 10, y), + Width = 250, + DropDownStyle = ComboBoxStyle.DropDownList + }; + providerComboBox.Items.AddRange(new object[] { + "Azure OpenAI (Cloud)", + "LM Studio (Local)", + "Google Gemini", + "GitHub Models (Free Tier)" + }); + providerComboBox.SelectedIndexChanged += ProviderComboBox_SelectedIndexChanged; + this.Controls.Add(providerComboBox); + y += 40; + + // Enable checkbox + enableProviderCheckBox = new CheckBox + { + Text = "Enable this provider (if unchecked, will use default Azure OpenAI)", + Location = new Point(leftMargin, y), + Width = 500, + Checked = true + }; + this.Controls.Add(enableProviderCheckBox); + y += 35; + + // Separator + var separator = new Label + { + BorderStyle = BorderStyle.Fixed3D, + Location = new Point(leftMargin, y), + Size = new Size(580, 2) + }; + this.Controls.Add(separator); + y += 15; + + // Config panel (will hold provider-specific controls) + configPanel = new Panel + { + Location = new Point(leftMargin, y), + Size = new Size(580, 300), + BorderStyle = BorderStyle.None + }; + this.Controls.Add(configPanel); + y += 310; + + // Status label + statusLabel = new Label + { + Location = new Point(leftMargin, y), + Size = new Size(580, 25), + ForeColor = Color.Blue, + Text = "" + }; + this.Controls.Add(statusLabel); + y += 30; + + // Buttons + testButton = new Button + { + Text = "Test Connection", + Location = new Point(leftMargin, y), + Width = 130, + Height = 32 + }; + testButton.Click += TestButton_Click; + this.Controls.Add(testButton); + + saveButton = new Button + { + Text = "Save Configuration", + Location = new Point(leftMargin + 450, y), + Width = 150, + Height = 32 + }; + saveButton.Click += SaveButton_Click; + this.Controls.Add(saveButton); + + var cancelButton = new Button + { + Text = "Cancel", + Location = new Point(leftMargin + 310, y), + Width = 130, + Height = 32, + DialogResult = DialogResult.Cancel + }; + cancelButton.Click += (s, e) => this.Close(); + this.Controls.Add(cancelButton); + + this.AcceptButton = saveButton; + this.CancelButton = cancelButton; + + // Create provider-specific panels + CreateAzurePanel(); + CreateLMStudioPanel(); + CreateGeminiPanel(); + } + + private void CreateGeminiPanel() + { + geminiPanel = new Panel + { + Location = new Point(0, 0), + Size = new Size(580, 300), + Visible = false + }; + + int y = 0; + int labelWidth = 150; + int controlWidth = 400; + + // API Key + var apiKeyLabel = new Label { Text = "Gemini API Key:", Location = new Point(0, y + 3), Width = labelWidth }; + geminiPanel.Controls.Add(apiKeyLabel); + geminiApiKeyTextBox = new TextBox + { + Location = new Point(labelWidth + 10, y), + Width = controlWidth, + UseSystemPasswordChar = true + }; + geminiPanel.Controls.Add(geminiApiKeyTextBox); + y += 35; + + // Model Name + var modelLabel = new Label { Text = "Model Name:", Location = new Point(0, y + 3), Width = labelWidth }; + geminiPanel.Controls.Add(modelLabel); + geminiModelTextBox = new TextBox + { + Location = new Point(labelWidth + 10, y), + Width = controlWidth, + Text = "gemini-1.5-flash" + }; + geminiPanel.Controls.Add(geminiModelTextBox); + y += 35; + + // Info + var helpLabel = new Label + { + Text = "Get your API Key from: https://aistudio.google.com/app/apikey\n" + + "Standard Endpoint: https://generativelanguage.googleapis.com/v1beta/openai/", + Location = new Point(0, y), + Size = new Size(550, 40), + ForeColor = Color.Gray + }; + geminiPanel.Controls.Add(helpLabel); + + configPanel.Controls.Add(geminiPanel); + } + + private void CreateAzurePanel() + { + azurePanel = new Panel + { + Location = new Point(0, 0), + Size = new Size(580, 300), + Visible = false + }; + + int y = 0; + int labelWidth = 150; + int controlWidth = 400; + + // Deployment Name + var deployLabel = new Label { Text = "Deployment Name:", Location = new Point(0, y + 3), Width = labelWidth }; + azurePanel.Controls.Add(deployLabel); + azureDeploymentTextBox = new TextBox { Location = new Point(labelWidth + 10, y), Width = controlWidth }; + azurePanel.Controls.Add(azureDeploymentTextBox); + y += 35; + + // Endpoint URL + var endpointLabel = new Label { Text = "Endpoint URL:", Location = new Point(0, y + 3), Width = labelWidth }; + azurePanel.Controls.Add(endpointLabel); + azureEndpointTextBox = new TextBox { Location = new Point(labelWidth + 10, y), Width = controlWidth }; + azurePanel.Controls.Add(azureEndpointTextBox); + y += 35; + + // API Key + var apiKeyLabel = new Label { Text = "API Key:", Location = new Point(0, y + 3), Width = labelWidth }; + azurePanel.Controls.Add(apiKeyLabel); + azureApiKeyTextBox = new TextBox + { + Location = new Point(labelWidth + 10, y), + Width = controlWidth, + UseSystemPasswordChar = true + }; + azurePanel.Controls.Add(azureApiKeyTextBox); + y += 35; + + // Temperature (Newly added) + var tempLabel = new Label { Text = "Temperature:", Location = new Point(0, y + 3), Width = labelWidth }; + azurePanel.Controls.Add(tempLabel); + azureTemperatureUpDown = new NumericUpDown + { + Location = new Point(labelWidth + 10, y), + Width = 100, + Minimum = 0, + Maximum = 2, + DecimalPlaces = 2, + Increment = 0.1M, + Value = 0.7M + }; + azurePanel.Controls.Add(azureTemperatureUpDown); + y += 35; + + // Help text + var helpLabel = new Label + { + Text = "Get your Azure OpenAI credentials from:\nhttps://portal.azure.com → Azure OpenAI → Keys and Endpoint", + Location = new Point(0, y), + Size = new Size(550, 40), + ForeColor = Color.Gray + }; + azurePanel.Controls.Add(helpLabel); + + configPanel.Controls.Add(azurePanel); + } + + private void CreateLMStudioPanel() + { + lmStudioPanel = new Panel + { + Location = new Point(0, 0), + Size = new Size(580, 300), + Visible = false + }; + + int y = 0; + int labelWidth = 150; + int controlWidth = 300; // Reduced width to make room for Auto-Detect button + + // Endpoint URL + var endpointLabel = new Label { Text = "Server Endpoint:", Location = new Point(0, y + 3), Width = labelWidth }; + lmStudioPanel.Controls.Add(endpointLabel); + + lmStudioEndpointTextBox = new TextBox + { + Location = new Point(labelWidth + 10, y), + Width = controlWidth, + Text = "http://localhost:1234/v1" + }; + lmStudioPanel.Controls.Add(lmStudioEndpointTextBox); + + // Auto-Detect Button + var autoDetectButton = new Button + { + Text = "Auto-Detect", + Location = new Point(labelWidth + controlWidth + 20, y - 1), + Width = 100, + Height = 23, + BackColor = Color.AliceBlue + }; + autoDetectButton.Click += AutoDetectButton_Click; + lmStudioPanel.Controls.Add(autoDetectButton); + + y += 35; + + // Model Name + var modelLabel = new Label { Text = "Model Name:", Location = new Point(0, y + 3), Width = labelWidth }; + lmStudioPanel.Controls.Add(modelLabel); + lmStudioModelTextBox = new TextBox + { + Location = new Point(labelWidth + 10, y), + Width = 400, + Text = "local-model" + }; + lmStudioPanel.Controls.Add(lmStudioModelTextBox); + y += 35; + + // Temperature + var tempLabel = new Label { Text = "Temperature:", Location = new Point(0, y + 3), Width = labelWidth }; + lmStudioPanel.Controls.Add(tempLabel); + lmStudioTemperatureUpDown = new NumericUpDown + { + Location = new Point(labelWidth + 10, y), + Width = 100, + Minimum = 1, // Fixed to 1 + Maximum = 1, // Fixed to 1 + DecimalPlaces = 1, // Fixed to 1 decimal place + Increment = 0.0M, // No increment as it's fixed + Value = 1.0M, // Fixed value + Enabled = false // Disable user input + }; + lmStudioPanel.Controls.Add(lmStudioTemperatureUpDown); + + // Add an info label for temperature + var tempInfoLabel = new Label + { + Text = "Fixed at 1.0 for LM Studio models.", + Location = new Point(labelWidth + 115, y + 3), + AutoSize = true, + ForeColor = Color.DarkGray + }; + lmStudioPanel.Controls.Add(tempInfoLabel); + y += 35; + + // Max Tokens + var tokensLabel = new Label { Text = "Max Tokens:", Location = new Point(0, y + 3), Width = labelWidth }; + lmStudioPanel.Controls.Add(tokensLabel); + lmStudioMaxTokensUpDown = new NumericUpDown + { + Location = new Point(labelWidth + 10, y), + Width = 100, + Minimum = 128, + Maximum = 1000000, // Increased to support large context models + Increment = 128, + Value = 2048 + }; + lmStudioPanel.Controls.Add(lmStudioMaxTokensUpDown); + y += 40; + + // Help text + var helpLabel = new Label + { + Text = "1. Download LM Studio from https://lmstudio.ai/\n" + + "2. Load a model (recommended: Hermes-2-Pro-Mistral-7B)\n" + + "3. Click 'Start Server' in LM Studio\n" + + "4. Make sure the endpoint matches (usually http://localhost:1234/v1)", + Location = new Point(0, y), + Size = new Size(550, 80), + ForeColor = Color.DarkGreen + }; + lmStudioPanel.Controls.Add(helpLabel); + + configPanel.Controls.Add(lmStudioPanel); + } + + private async void AutoDetectButton_Click(object sender, EventArgs e) + { + statusLabel.Text = "Searching for local AI server..."; + statusLabel.ForeColor = Color.Blue; + + string[] commonEndpoints = new[] + { + "http://localhost:1234/v1", // LM Studio default + "http://127.0.0.1:1234/v1", // LM Studio IP + "http://localhost:11434/v1", // Ollama default + "http://localhost:5000/v1", // LocalAI/Oobabooga default + "http://localhost:8080/v1" // Llama.cpp server + }; + + foreach (var endpoint in commonEndpoints) + { + try + { + var client = new OpenAI.OpenAIClient( + new System.ClientModel.ApiKeyCredential("any-key"), + new OpenAI.OpenAIClientOptions { Endpoint = new Uri(endpoint) }); + + // Just try to list models or verify endpoint validity + // Note: OpenAI client doesn't have a simple 'ping', so we assume if Uri creation works + // and we can create a client, it's a candidate. A real ping would require an API call. + // Let's try a lightweight API call to verify. + + // Create a dummy chat client to test connectivity + var chatClient = client.GetChatClient("test-model"); + var ichatClient = (Microsoft.Extensions.AI.IChatClient)(object)chatClient; + + var messages = new System.Collections.Generic.List + { + new Microsoft.Extensions.AI.ChatMessage(Microsoft.Extensions.AI.ChatRole.User, "hi") + }; + + // Set a short timeout for detection + // Note: .NET 4.8 async timeout cancellation is tricky, relying on fast failure + try { + // We don't actually wait for a full response, just seeing if connection is refused immediately + // If it hangs, it might be a valid server processing. + // For now, let's just assume the first valid URI that doesn't throw immediate connection refused is good. + await ichatClient.GetResponseAsync(messages); + } + catch (Exception ex) when (ex.Message.Contains("404") || !ex.Message.Contains("connection")) + { + // 404 means server is there but model not found - that's a success for finding the server! + // Not connection error means we reached something. + } + + lmStudioEndpointTextBox.Text = endpoint; + statusLabel.Text = $"✓ Found server at {endpoint}!"; + statusLabel.ForeColor = Color.Green; + return; + } + catch + { + // Continue to next endpoint + } + } + + statusLabel.Text = "✗ No local server found. Is LM Studio running?"; + statusLabel.ForeColor = Color.Red; + } + + private async Task TestLMStudioConnection() + { + try + { + if (!Uri.TryCreate(lmStudioEndpointTextBox.Text, UriKind.Absolute, out Uri result)) + { + throw new UriFormatException("Invalid Endpoint URL format. It should look like: http://localhost:1234/v1"); + } + + var client = new OpenAI.OpenAIClient( + new System.ClientModel.ApiKeyCredential("lm-studio"), + new OpenAI.OpenAIClientOptions { Endpoint = new Uri(lmStudioEndpointTextBox.Text) }); + + var chatClient = client.GetChatClient(lmStudioModelTextBox.Text); + + var messages = new System.Collections.Generic.List + { + new Microsoft.Extensions.AI.ChatMessage( + Microsoft.Extensions.AI.ChatRole.User, + "Say 'test' in one word") + }; + + // Cast to IChatClient - can't use AsIChatClient on OpenAI.ChatClient directly in .NET 4.8 + var ichatClient = (Microsoft.Extensions.AI.IChatClient)(object)chatClient; + var response = await ichatClient.GetResponseAsync(messages); + + statusLabel.Text = "✓ LM Studio connection successful!"; + statusLabel.ForeColor = Color.Green; + } + catch (Exception ex) + { + if (ex.Message.Contains("Connection refused") || ex.Message.Contains("No connection")) + { + statusLabel.Text = "✗ Cannot connect. Is LM Studio running with server started?"; + } + else + { + statusLabel.Text = $"✗ Connection failed: {ex.Message}"; + } + statusLabel.ForeColor = Color.Red; + } + } + + private void LoadConfiguration() + { + // Try to load existing configuration to determine current provider + var lmConfig = LMStudioConfig.LoadConfig(); + var azureConfig = APIConfig.LoadConfig(currentModel); + + // Load global tool config for syncing temperature + var toolConfig = ToolConfig.LoadConfig("toolsconfig"); + + if (lmConfig.Enabled) + { + // LM Studio is enabled + providerComboBox.SelectedIndex = 1; // LM Studio + enableProviderCheckBox.Checked = true; + + lmStudioEndpointTextBox.Text = lmConfig.EndpointURL; + lmStudioModelTextBox.Text = lmConfig.ModelName; + lmStudioTemperatureUpDown.Value = (decimal)lmConfig.Temperature; + + // Safely set max tokens + if (lmConfig.MaxTokens < lmStudioMaxTokensUpDown.Minimum) + lmStudioMaxTokensUpDown.Value = lmStudioMaxTokensUpDown.Minimum; + else if (lmConfig.MaxTokens > lmStudioMaxTokensUpDown.Maximum) + lmStudioMaxTokensUpDown.Value = lmStudioMaxTokensUpDown.Maximum; + else + lmStudioMaxTokensUpDown.Value = lmConfig.MaxTokens; + } + else if (azureConfig.ProviderType == "Gemini") + { + // Gemini is enabled + providerComboBox.SelectedIndex = 2; // Google Gemini + enableProviderCheckBox.Checked = true; + + geminiApiKeyTextBox.Text = azureConfig.APIKey; + geminiModelTextBox.Text = azureConfig.DeploymentName; + } + else + { + // Azure OpenAI (default) + providerComboBox.SelectedIndex = 0; // Azure OpenAI + enableProviderCheckBox.Checked = true; + + azureDeploymentTextBox.Text = azureConfig.DeploymentName; + azureEndpointTextBox.Text = azureConfig.EndpointURL; + azureApiKeyTextBox.Text = azureConfig.APIKey; + + // Init Azure temperature control (use ToolConfig temperature if available, else default) + azureTemperatureUpDown.Value = (decimal)toolConfig.Temperature; + } + } + + private void ProviderComboBox_SelectedIndexChanged(object sender, EventArgs e) + { + // Hide all panels + azurePanel.Visible = false; + lmStudioPanel.Visible = false; + geminiPanel.Visible = false; + + // Show selected panel + switch (providerComboBox.SelectedIndex) + { + case 0: // Azure OpenAI + azurePanel.Visible = true; + break; + case 1: // LM Studio + lmStudioPanel.Visible = true; + break; + case 2: // Google Gemini + geminiPanel.Visible = true; + break; + case 3: // GitHub Models + MessageBox.Show("GitHub Models support coming soon!\nFor now, configure as Azure OpenAI with GitHub endpoint.", + "Coming Soon", MessageBoxButtons.OK, MessageBoxIcon.Information); + providerComboBox.SelectedIndex = 0; + break; + } + } + + private async void TestButton_Click(object sender, EventArgs e) + { + statusLabel.Text = "Testing connection..."; + statusLabel.ForeColor = Color.Blue; + testButton.Enabled = false; + + try + { + if (providerComboBox.SelectedIndex == 1) // LM Studio + { + await TestLMStudioConnection(); + } + else if (providerComboBox.SelectedIndex == 2) // Gemini + { + var client = new OpenAI.OpenAIClient( + new System.ClientModel.ApiKeyCredential(geminiApiKeyTextBox.Text), + new OpenAI.OpenAIClientOptions { Endpoint = new Uri("https://generativelanguage.googleapis.com/v1beta/openai/") } + ); + var chatClient = client.GetChatClient(geminiModelTextBox.Text); + var ichatClient = (Microsoft.Extensions.AI.IChatClient)(object)chatClient; + await ichatClient.GetResponseAsync(new System.Collections.Generic.List{ + new Microsoft.Extensions.AI.ChatMessage(Microsoft.Extensions.AI.ChatRole.User, "hi") + }); + statusLabel.Text = "✓ Gemini connection successful!"; + statusLabel.ForeColor = Color.Green; + } + else // Azure OpenAI + { + await TestAzureConnection(); + } + } + catch (Exception ex) + { + statusLabel.Text = $"Test failed: {ex.Message}"; + statusLabel.ForeColor = Color.Red; + } + finally + { + testButton.Enabled = true; + } + } + + private async Task TestAzureConnection() + { + try + { + var client = new Azure.AI.OpenAI.AzureOpenAIClient( + new Uri(azureEndpointTextBox.Text), + new Azure.AzureKeyCredential(azureApiKeyTextBox.Text)); + + var chatClient = client.GetChatClient(azureDeploymentTextBox.Text); + // Use cast for .NET 4.8 compatibility + var ichatClient = (Microsoft.Extensions.AI.IChatClient)(object)chatClient; + + var messages = new System.Collections.Generic.List + { + new Microsoft.Extensions.AI.ChatMessage( + Microsoft.Extensions.AI.ChatRole.User, + "Say 'test' in one word") + }; + + var response = await ichatClient.GetResponseAsync(messages); + + statusLabel.Text = "✓ Azure OpenAI connection successful!"; + statusLabel.ForeColor = Color.Green; + } + catch (Exception ex) + { + statusLabel.Text = $"✗ Connection failed: {ex.Message}"; + statusLabel.ForeColor = Color.Red; + } + } + + private void SaveButton_Click(object sender, EventArgs e) + { + try + { + if (providerComboBox.SelectedIndex == 1) // LM Studio + { + SaveLMStudioConfig(); + } + else if (providerComboBox.SelectedIndex == 2) // Gemini + { + SaveGeminiConfig(); + } + else // Azure OpenAI + { + SaveAzureConfig(); + } + + MessageBox.Show( + $"Configuration saved successfully!\n\n" + + $"Provider: {providerComboBox.SelectedItem}\n" + + $"Status: {(enableProviderCheckBox.Checked ? "Enabled" : "Disabled")}\n\n" + + $"The new provider will be used immediately.", + "Configuration Saved", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + + this.DialogResult = DialogResult.OK; + this.Close(); + } + catch (Exception ex) + { + MessageBox.Show($"Error saving configuration: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void SaveGeminiConfig() + { + var config = new APIConfig + { + DeploymentName = geminiModelTextBox.Text, + EndpointURL = "https://generativelanguage.googleapis.com/v1beta/openai/", + APIKey = geminiApiKeyTextBox.Text, + ProviderType = "Gemini" + }; + config.SaveConfig(currentModel); + + // Disable LM Studio + var lmConfig = LMStudioConfig.LoadConfig(); + lmConfig.Enabled = false; + lmConfig.SaveConfig(); + } + + private void SaveAzureConfig() + { + var config = new APIConfig + { + DeploymentName = azureDeploymentTextBox.Text, + EndpointURL = azureEndpointTextBox.Text, + APIKey = azureApiKeyTextBox.Text, + ProviderType = "AzureOpenAI" + }; + config.SaveConfig(currentModel); + + // Disable LM Studio if Azure is being configured + var lmConfig = LMStudioConfig.LoadConfig(); + lmConfig.Enabled = false; + lmConfig.SaveConfig(); + + // Sync global tool config temperature + var toolConfig = ToolConfig.LoadConfig("toolsconfig"); + toolConfig.Temperature = (double)azureTemperatureUpDown.Value; + toolConfig.SaveConfig("toolsconfig"); + } + + private void SaveLMStudioConfig() + { + if (!Uri.TryCreate(lmStudioEndpointTextBox.Text, UriKind.Absolute, out Uri result)) + { + throw new UriFormatException("Invalid Endpoint URL format. It should look like: http://localhost:1234/v1"); + } + + var config = new LMStudioConfig + { + EndpointURL = lmStudioEndpointTextBox.Text, + ModelName = lmStudioModelTextBox.Text, + Temperature = (double)lmStudioTemperatureUpDown.Value, + MaxTokens = (int)lmStudioMaxTokensUpDown.Value, + Enabled = enableProviderCheckBox.Checked + }; + config.SaveConfig(); + + // Also save to Azure config for compatibility + var azureConfig = new APIConfig + { + DeploymentName = lmStudioModelTextBox.Text, + EndpointURL = lmStudioEndpointTextBox.Text, + APIKey = "lm-studio", + ProviderType = "LMStudio" + }; + azureConfig.SaveConfig(currentModel); + + // Sync global tool config temperature + var toolConfig = ToolConfig.LoadConfig("toolsconfig"); + toolConfig.Temperature = (double)lmStudioTemperatureUpDown.Value; + toolConfig.SaveConfig("toolsconfig"); + } + } +} diff --git a/FlowVision/ConfigForm.Designer.cs b/FlowVision/ConfigForm.Designer.cs index 8c8a065..353eecc 100644 --- a/FlowVision/ConfigForm.Designer.cs +++ b/FlowVision/ConfigForm.Designer.cs @@ -43,7 +43,7 @@ private void InitializeComponent() this.DeploymentNameLabel.Font = new System.Drawing.Font("Comic Sans MS", 14F); this.DeploymentNameLabel.Location = new System.Drawing.Point(12, 9); this.DeploymentNameLabel.Name = "DeploymentNameLabel"; - this.DeploymentNameLabel.Size = new System.Drawing.Size(170, 26); + this.DeploymentNameLabel.Size = new System.Drawing.Size(121, 26); this.DeploymentNameLabel.TabIndex = 0; this.DeploymentNameLabel.Text = "Model Name"; // @@ -115,10 +115,13 @@ private void InitializeComponent() this.Controls.Add(this.deploymentNameTextBox); this.Controls.Add(this.EndpointURLLabel); this.Controls.Add(this.DeploymentNameLabel); + this.MaximizeBox = false; this.MaximumSize = new System.Drawing.Size(700, 200); + this.MinimizeBox = false; this.MinimumSize = new System.Drawing.Size(700, 200); this.Name = "ConfigForm"; - this.Text = "ConfigForm"; + this.ShowIcon = false; + this.Text = "Model Config"; this.ResumeLayout(false); this.PerformLayout(); diff --git a/FlowVision/FlowVision.csproj b/FlowVision/FlowVision.csproj index 28aa502..82f8f5b 100644 --- a/FlowVision/FlowVision.csproj +++ b/FlowVision/FlowVision.csproj @@ -13,6 +13,7 @@ 512 true true + latest @@ -25,6 +26,7 @@ DEBUG;TRACE prompt 4 + true AnyCPU @@ -34,6 +36,10 @@ TRACE prompt 4 + true + + + recursive-control-icon.ico @@ -45,12 +51,6 @@ ..\packages\Azure.Core.1.45.0\lib\net472\Azure.Core.dll - - ..\packages\CefSharp.Common.135.0.170\lib\net462\CefSharp.dll - - - ..\packages\CefSharp.Common.135.0.170\lib\net462\CefSharp.Core.dll - ..\packages\Costura.Fody.6.0.0\lib\netstandard2.0\Costura.dll @@ -75,6 +75,9 @@ ..\packages\Microsoft.Extensions.AI.AzureAIInference.9.4.0-preview.1.25207.5\lib\net462\Microsoft.Extensions.AI.AzureAIInference.dll + + ..\packages\Microsoft.Extensions.AI.OpenAI.9.4.0-preview.1.25207.5\lib\net462\Microsoft.Extensions.AI.OpenAI.dll + ..\packages\Microsoft.Extensions.Caching.Abstractions.10.0.0-preview.3.25171.5\lib\net462\Microsoft.Extensions.Caching.Abstractions.dll @@ -93,6 +96,9 @@ ..\packages\Microsoft.Extensions.VectorData.Abstractions.9.0.0-preview.1.25161.1\lib\net462\Microsoft.Extensions.VectorData.Abstractions.dll + + ..\packages\Microsoft.Playwright.1.52.0\lib\netstandard2.0\Microsoft.Playwright.dll + ..\packages\Microsoft.SemanticKernel.1.47.0\lib\netstandard2.0\Microsoft.SemanticKernel.dll @@ -124,6 +130,10 @@ ..\packages\System.ClientModel.1.4.0-beta.4\lib\netstandard2.0\System.ClientModel.dll + + ..\packages\System.ComponentModel.Annotations.5.0.0\lib\net461\System.ComponentModel.Annotations.dll + + ..\packages\System.Diagnostics.DiagnosticSource.10.0.0-preview.3.25171.5\lib\net462\System.Diagnostics.DiagnosticSource.dll @@ -147,6 +157,7 @@ ..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll + ..\packages\System.Text.Encodings.Web.10.0.0-preview.3.25171.5\lib\net462\System.Text.Encodings.Web.dll @@ -159,6 +170,10 @@ ..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll + + ..\packages\Tesseract.5.2.0\lib\net48\Tesseract.dll + True + @@ -168,23 +183,49 @@ + + + C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.19041.0\Windows.winmd + + + Form + Form ConfigForm.cs + + Form + Form Form1.cs + + + + + + + + - + + + + + + + + + Form @@ -194,20 +235,23 @@ - - + + + + + UserControl + + + UserControl + + + - - Form - - - OmniParserForm.cs - @@ -217,9 +261,6 @@ Form1.cs - - OmniParserForm.cs - ResXFileCodeGenerator Resources.Designer.cs @@ -234,6 +275,7 @@ ToolConfigForm.cs Designer + SettingsSingleFileGenerator @@ -245,7 +287,9 @@ True - + + + @@ -255,8 +299,19 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/FlowVision/FodyWeavers.xml b/FlowVision/FodyWeavers.xml index 5029e70..a14d1b0 100644 --- a/FlowVision/FodyWeavers.xml +++ b/FlowVision/FodyWeavers.xml @@ -1,3 +1,25 @@  - + + + true + + + + leptonica-1.82.0 + tesseract50 + + + + leptonica-1.82.0 + tesseract50 + + + + leptonica-1.82.0 + tesseract50 + + + + $(MSBuildThisFileDirectory)..\packages\Tesseract.5.2.0\x64\ + \ No newline at end of file diff --git a/FlowVision/Form1.Designer.cs b/FlowVision/Form1.Designer.cs index 38b5339..b9bd0a0 100644 --- a/FlowVision/Form1.Designer.cs +++ b/FlowVision/Form1.Designer.cs @@ -17,6 +17,13 @@ protected override void Dispose(bool disposing) { components.Dispose(); } + + // Clean up speech recognition resources + if (speechRecognition != null) + { + speechRecognition.Dispose(); + } + base.Dispose(disposing); } @@ -32,13 +39,26 @@ private void InitializeComponent() this.filesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.newChatToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.exportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.exportToJSONToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.exportToMarkdownToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.exportDebugLogToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.copyToClipboardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.agentsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.actionerAgentToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.plannerAgentToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.coordinatorAgentToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.githubAgentToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.multiAgentModeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.visionToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.omniParserToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.lLMToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.configureToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.azureOpenAIToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.githubToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.reasonToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.viewToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.activityMonitorToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.executionVisualizerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.documentationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.mainPanel = new System.Windows.Forms.Panel(); this.menuStrip2.SuspendLayout(); this.SuspendLayout(); @@ -47,9 +67,9 @@ private void InitializeComponent() // this.menuStrip2.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.filesToolStripMenuItem, - this.visionToolStripMenuItem, - this.lLMToolStripMenuItem, - this.reasonToolStripMenuItem}); + this.settingsToolStripMenuItem, + this.viewToolStripMenuItem, + this.helpToolStripMenuItem}); this.menuStrip2.Location = new System.Drawing.Point(0, 0); this.menuStrip2.Name = "menuStrip2"; this.menuStrip2.Size = new System.Drawing.Size(367, 24); @@ -60,7 +80,8 @@ private void InitializeComponent() // this.filesToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.toolsToolStripMenuItem, - this.newChatToolStripMenuItem}); + this.newChatToolStripMenuItem, + this.exportToolStripMenuItem}); this.filesToolStripMenuItem.Name = "filesToolStripMenuItem"; this.filesToolStripMenuItem.Size = new System.Drawing.Size(37, 20); this.filesToolStripMenuItem.Text = "File"; @@ -68,69 +89,176 @@ private void InitializeComponent() // toolsToolStripMenuItem // this.toolsToolStripMenuItem.Name = "toolsToolStripMenuItem"; - this.toolsToolStripMenuItem.Size = new System.Drawing.Size(126, 22); + this.toolsToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.toolsToolStripMenuItem.Text = "Tools"; this.toolsToolStripMenuItem.Click += new System.EventHandler(this.toolsToolStripMenuItem_Click); // // newChatToolStripMenuItem // this.newChatToolStripMenuItem.Name = "newChatToolStripMenuItem"; - this.newChatToolStripMenuItem.Size = new System.Drawing.Size(126, 22); + this.newChatToolStripMenuItem.Size = new System.Drawing.Size(180, 22); this.newChatToolStripMenuItem.Text = "New Chat"; this.newChatToolStripMenuItem.Click += new System.EventHandler(this.newChatToolStripMenuItem_Click); // + // exportToolStripMenuItem + // + this.exportToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.exportToJSONToolStripMenuItem, + this.exportToMarkdownToolStripMenuItem, + this.exportDebugLogToolStripMenuItem, + this.copyToClipboardToolStripMenuItem}); + this.exportToolStripMenuItem.Name = "exportToolStripMenuItem"; + this.exportToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.exportToolStripMenuItem.Text = "Export Chat"; + // + // exportToJSONToolStripMenuItem + // + this.exportToJSONToolStripMenuItem.Name = "exportToJSONToolStripMenuItem"; + this.exportToJSONToolStripMenuItem.Size = new System.Drawing.Size(220, 22); + this.exportToJSONToolStripMenuItem.Text = "Export to JSON"; + this.exportToJSONToolStripMenuItem.Click += new System.EventHandler(this.exportToJSONToolStripMenuItem_Click); + // + // exportToMarkdownToolStripMenuItem + // + this.exportToMarkdownToolStripMenuItem.Name = "exportToMarkdownToolStripMenuItem"; + this.exportToMarkdownToolStripMenuItem.Size = new System.Drawing.Size(220, 22); + this.exportToMarkdownToolStripMenuItem.Text = "Export to Markdown"; + this.exportToMarkdownToolStripMenuItem.Click += new System.EventHandler(this.exportToMarkdownToolStripMenuItem_Click); + // + // exportDebugLogToolStripMenuItem + // + this.exportDebugLogToolStripMenuItem.Name = "exportDebugLogToolStripMenuItem"; + this.exportDebugLogToolStripMenuItem.Size = new System.Drawing.Size(220, 22); + this.exportDebugLogToolStripMenuItem.Text = "Export Debug Log (with Tools)"; + this.exportDebugLogToolStripMenuItem.Click += new System.EventHandler(this.exportDebugLogToolStripMenuItem_Click); + // + // copyToClipboardToolStripMenuItem + // + this.copyToClipboardToolStripMenuItem.Name = "copyToClipboardToolStripMenuItem"; + this.copyToClipboardToolStripMenuItem.Size = new System.Drawing.Size(220, 22); + this.copyToClipboardToolStripMenuItem.Text = "Copy to Clipboard"; + this.copyToClipboardToolStripMenuItem.Click += new System.EventHandler(this.copyToClipboardToolStripMenuItem_Click); + // // visionToolStripMenuItem // this.visionToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.omniParserToolStripMenuItem}); this.visionToolStripMenuItem.Name = "visionToolStripMenuItem"; - this.visionToolStripMenuItem.Size = new System.Drawing.Size(51, 20); - this.visionToolStripMenuItem.Text = "Vision"; + this.visionToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.visionToolStripMenuItem.Text = "🔭 Vision Tools"; // // omniParserToolStripMenuItem // this.omniParserToolStripMenuItem.Name = "omniParserToolStripMenuItem"; - this.omniParserToolStripMenuItem.Size = new System.Drawing.Size(136, 22); - this.omniParserToolStripMenuItem.Text = "OmniParser"; + this.omniParserToolStripMenuItem.Size = new System.Drawing.Size(200, 22); + this.omniParserToolStripMenuItem.Text = "📸 OmniParser Config"; this.omniParserToolStripMenuItem.Click += new System.EventHandler(this.omniParserToolStripMenuItem_Click); // - // lLMToolStripMenuItem + // settingsToolStripMenuItem + // + this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.toolsToolStripMenuItem, + this.agentsToolStripMenuItem, + this.visionToolStripMenuItem, + this.multiAgentModeToolStripMenuItem}); + this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem"; + this.settingsToolStripMenuItem.Size = new System.Drawing.Size(61, 20); + this.settingsToolStripMenuItem.Text = "⚙️ Setup"; + // + // agentsToolStripMenuItem + // + this.agentsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.actionerAgentToolStripMenuItem, + this.plannerAgentToolStripMenuItem, + this.coordinatorAgentToolStripMenuItem, + this.githubAgentToolStripMenuItem}); + this.agentsToolStripMenuItem.Name = "agentsToolStripMenuItem"; + this.agentsToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.agentsToolStripMenuItem.Text = "🤖 AI Agents"; + // + // actionerAgentToolStripMenuItem + // + this.actionerAgentToolStripMenuItem.Name = "actionerAgentToolStripMenuItem"; + this.actionerAgentToolStripMenuItem.Size = new System.Drawing.Size(250, 22); + this.actionerAgentToolStripMenuItem.Text = "⚡ Actioner Agent (Primary)"; + this.actionerAgentToolStripMenuItem.Click += new System.EventHandler(this.actionerAgentToolStripMenuItem_Click); + // + // plannerAgentToolStripMenuItem + // + this.plannerAgentToolStripMenuItem.Name = "plannerAgentToolStripMenuItem"; + this.plannerAgentToolStripMenuItem.Size = new System.Drawing.Size(250, 22); + this.plannerAgentToolStripMenuItem.Text = "📋 Planner Agent"; + this.plannerAgentToolStripMenuItem.Click += new System.EventHandler(this.plannerAgentToolStripMenuItem_Click); + // + // coordinatorAgentToolStripMenuItem + // + this.coordinatorAgentToolStripMenuItem.Name = "coordinatorAgentToolStripMenuItem"; + this.coordinatorAgentToolStripMenuItem.Size = new System.Drawing.Size(250, 22); + this.coordinatorAgentToolStripMenuItem.Text = "🎯 Coordinator Agent"; + this.coordinatorAgentToolStripMenuItem.Click += new System.EventHandler(this.coordinatorAgentToolStripMenuItem_Click); + // + // githubAgentToolStripMenuItem + // + this.githubAgentToolStripMenuItem.Name = "githubAgentToolStripMenuItem"; + this.githubAgentToolStripMenuItem.Size = new System.Drawing.Size(250, 22); + this.githubAgentToolStripMenuItem.Text = "🐙 GitHub Agent"; + this.githubAgentToolStripMenuItem.Click += new System.EventHandler(this.githubAgentToolStripMenuItem_Click); + // + // multiAgentModeToolStripMenuItem + // + this.multiAgentModeToolStripMenuItem.CheckOnClick = true; + this.multiAgentModeToolStripMenuItem.Name = "multiAgentModeToolStripMenuItem"; + this.multiAgentModeToolStripMenuItem.Size = new System.Drawing.Size(180, 22); + this.multiAgentModeToolStripMenuItem.Text = "🔀 Multi-Agent Mode"; + this.multiAgentModeToolStripMenuItem.Click += new System.EventHandler(this.multiAgentModeToolStripMenuItem_Click); + // + // viewToolStripMenuItem + // + this.viewToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.activityMonitorToolStripMenuItem, + this.executionVisualizerToolStripMenuItem}); + this.viewToolStripMenuItem.Name = "viewToolStripMenuItem"; + this.viewToolStripMenuItem.Size = new System.Drawing.Size(44, 20); + this.viewToolStripMenuItem.Text = "👁️ View"; + // + // activityMonitorToolStripMenuItem // - this.lLMToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.configureToolStripMenuItem}); - this.lLMToolStripMenuItem.Name = "lLMToolStripMenuItem"; - this.lLMToolStripMenuItem.Size = new System.Drawing.Size(42, 20); - this.lLMToolStripMenuItem.Text = "LLM"; + this.activityMonitorToolStripMenuItem.CheckOnClick = true; + this.activityMonitorToolStripMenuItem.Name = "activityMonitorToolStripMenuItem"; + this.activityMonitorToolStripMenuItem.Size = new System.Drawing.Size(200, 22); + this.activityMonitorToolStripMenuItem.Text = "📊 Activity Monitor"; + this.activityMonitorToolStripMenuItem.Click += new System.EventHandler(this.activityMonitorToolStripMenuItem_Click); // - // configureToolStripMenuItem + // executionVisualizerToolStripMenuItem // - this.configureToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.azureOpenAIToolStripMenuItem, - this.githubToolStripMenuItem}); - this.configureToolStripMenuItem.Name = "configureToolStripMenuItem"; - this.configureToolStripMenuItem.Size = new System.Drawing.Size(104, 22); - this.configureToolStripMenuItem.Text = "Setup"; - this.configureToolStripMenuItem.Click += new System.EventHandler(this.configureToolStripMenuItem_Click); + this.executionVisualizerToolStripMenuItem.CheckOnClick = true; + this.executionVisualizerToolStripMenuItem.Name = "executionVisualizerToolStripMenuItem"; + this.executionVisualizerToolStripMenuItem.Size = new System.Drawing.Size(200, 22); + this.executionVisualizerToolStripMenuItem.Text = "🎯 Execution Visualizer"; + this.executionVisualizerToolStripMenuItem.Click += new System.EventHandler(this.executionVisualizerToolStripMenuItem_Click); // - // azureOpenAIToolStripMenuItem + // helpToolStripMenuItem // - this.azureOpenAIToolStripMenuItem.Name = "azureOpenAIToolStripMenuItem"; - this.azureOpenAIToolStripMenuItem.Size = new System.Drawing.Size(147, 22); - this.azureOpenAIToolStripMenuItem.Text = "Azure OpenAI"; - this.azureOpenAIToolStripMenuItem.Click += new System.EventHandler(this.azureOpenAIToolStripMenuItem_Click); + this.helpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.aboutToolStripMenuItem, + this.documentationToolStripMenuItem}); + this.helpToolStripMenuItem.Name = "helpToolStripMenuItem"; + this.helpToolStripMenuItem.Size = new System.Drawing.Size(44, 20); + this.helpToolStripMenuItem.Text = "❓ Help"; // - // githubToolStripMenuItem + // aboutToolStripMenuItem // - this.githubToolStripMenuItem.Name = "githubToolStripMenuItem"; - this.githubToolStripMenuItem.Size = new System.Drawing.Size(147, 22); - this.githubToolStripMenuItem.Text = "Github"; - this.githubToolStripMenuItem.Click += new System.EventHandler(this.githubToolStripMenuItem_Click); + this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; + this.aboutToolStripMenuItem.Size = new System.Drawing.Size(200, 22); + this.aboutToolStripMenuItem.Text = "ℹ️ About"; + this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click); // - // reasonToolStripMenuItem + // documentationToolStripMenuItem // - this.reasonToolStripMenuItem.Name = "reasonToolStripMenuItem"; - this.reasonToolStripMenuItem.Size = new System.Drawing.Size(57, 20); - this.reasonToolStripMenuItem.Text = "Reason"; + this.documentationToolStripMenuItem.Name = "documentationToolStripMenuItem"; + this.documentationToolStripMenuItem.Size = new System.Drawing.Size(200, 22); + this.documentationToolStripMenuItem.Text = "📚 Documentation"; + this.documentationToolStripMenuItem.Click += new System.EventHandler(this.documentationToolStripMenuItem_Click); // // mainPanel // @@ -161,16 +289,29 @@ private void InitializeComponent() #endregion private System.Windows.Forms.MenuStrip menuStrip2; private System.Windows.Forms.ToolStripMenuItem filesToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem settingsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem agentsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem actionerAgentToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem plannerAgentToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem coordinatorAgentToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem githubAgentToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem visionToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem lLMToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem reasonToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem configureToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem azureOpenAIToolStripMenuItem; private System.Windows.Forms.Panel mainPanel; - private System.Windows.Forms.ToolStripMenuItem githubToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem omniParserToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem toolsToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem newChatToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem exportToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem exportToJSONToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem exportToMarkdownToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem exportDebugLogToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem copyToClipboardToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem multiAgentModeToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem viewToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem activityMonitorToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem executionVisualizerToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem documentationToolStripMenuItem; } } diff --git a/FlowVision/Form1.cs b/FlowVision/Form1.cs index f2849bd..cbe789e 100644 --- a/FlowVision/Form1.cs +++ b/FlowVision/Form1.cs @@ -8,6 +8,10 @@ using System.Threading.Tasks; using System.Windows.Forms; using FlowVision.lib.Classes; +using Microsoft.Extensions.AI; +// Add System.Speech namespace +using System.Speech.Recognition; +using FlowVision.lib.UI; namespace FlowVision { @@ -16,7 +20,12 @@ public partial class Form1 : Form private FlowLayoutPanel messagesPanel; private RichTextBox userInputTextBox; private Button sendButton; - private List chatHistory = new List(); + private List chatHistory = new List(); + private Button microphoneButton; // New microphone button + // Speech recognition components + private SpeechRecognitionService speechRecognition; + private bool isListening = false; + private ThemeManager _themeManager; // Add ThemeManager field declaration // Add a delegate for handling plugin output messages public delegate void PluginOutputHandler(string message); @@ -26,29 +35,74 @@ public Form1() InitializeComponent(); } - private void toolStripMenuItem1_Click(object sender, EventArgs e) + private void azureOpenAIToolStripMenuItem_Click(object sender, EventArgs e) { - + // Use the new unified AI Provider config form + if (Application.OpenForms.OfType().Count() == 1) + { + Application.OpenForms.OfType().First().BringToFront(); + } + else + { + AIProviderConfigForm configForm = new AIProviderConfigForm("actioner"); + configForm.ShowDialog(); + } } - private void configureToolStripMenuItem_Click(object sender, EventArgs e) + private void ApplyTheme(string themeName = null) // Fix ApplyTheme method to handle parameter { + // Initialize ThemeManager if it doesn't exist yet + if (_themeManager == null) + { + _themeManager = new ThemeManager(); + } - } + // Use provided theme name or current theme from ThemeManager + string theme = themeName ?? _themeManager.CurrentTheme; - private void azureOpenAIToolStripMenuItem_Click(object sender, EventArgs e) - { - // Check if the config form is already open - if (Application.OpenForms.OfType().Count() == 1) + if (theme == "Dark") { - // If it is, bring it to the front - Application.OpenForms.OfType().First().BringToFront(); + ApplyDarkTheme(); } else { - // If it isn't, create a new instance of the form - ConfigForm configForm = new ConfigForm("actioner"); - configForm.Show(); + ApplyLightTheme(); + } + + // Apply theme to all dynamically created controls + _themeManager.ApplyThemeToControls(this); + } + + private void ApplyLightTheme() + { + // Let ThemeManager handle applying the light theme to all controls + _themeManager.ApplyThemeToControls(this); + + // Update status indicator + UpdateStatusIndicators(); + } + + private void ApplyDarkTheme() + { + // Let ThemeManager handle applying the dark theme to all controls + _themeManager.ApplyThemeToControls(this); + + // Apply specific overrides for any controls that need special handling + + // Update status indicator + UpdateStatusIndicators(); + } + + private void UpdateStatusIndicators() // Add missing UpdateStatusIndicators method + { + // Update any status indicators based on current state + // This can be extended later if more indicators are added + + // Update microphone button appearance based on isListening state + if (microphoneButton != null) + { + microphoneButton.Text = isListening ? "⏹️" : "🎤"; + microphoneButton.BackColor = isListening ? Color.Red : SystemColors.Control; } } @@ -56,13 +110,27 @@ private void Form1_Load(object sender, EventArgs e) { // Check if tools are configured, if not, create default configuration string toolConfigName = "toolsconfig"; - if (!ToolConfig.IsConfigured(toolConfigName)) + try + { + if (!ToolConfig.IsConfigured(toolConfigName)) + { + // Create and save default configuration with mouse and screen capture disabled + var defaultConfig = new ToolConfig(); + defaultConfig.SaveConfig(toolConfigName); + } + + // Load current configuration and update UI + var toolConfig = ToolConfig.LoadConfig(toolConfigName); + multiAgentModeToolStripMenuItem.Checked = toolConfig.EnableMultiAgentMode; + } + catch (Exception ex) { - // Create and save default configuration with mouse and screen capture disabled - var defaultConfig = new ToolConfig(); - defaultConfig.SaveConfig(toolConfigName); + MessageBox.Show($"Error configuring tools: {ex.Message}", "Configuration Error"); } - + + // Add this call after initializing UI components + ApplyTheme(); + // Create messages panel as a FlowLayoutPanel with auto-scroll messagesPanel = new FlowLayoutPanel { @@ -104,6 +172,20 @@ private void Form1_Load(object sender, EventArgs e) sendButton.Click += SendButton_Click; inputPanel.Controls.Add(sendButton); + // Create microphone button + microphoneButton = new Button + { + Dock = DockStyle.Right, + Text = "🎤", + Width = 40, + Font = new Font("Segoe UI", 12F) + }; + microphoneButton.Click += MicrophoneButton_Click; + inputPanel.Controls.Add(microphoneButton); + + // Initialize speech recognition service + InitializeSpeechRecognition(); + // Handle window resize to adjust message widths this.Resize += (s, args) => { @@ -131,15 +213,165 @@ private void Form1_Load(object sender, EventArgs e) AddMessage("AI Assistant", "Welcome! How can I help you today?", true); } + private void InitializeSpeechRecognition() + { + try + { + var toolConfig = ToolConfig.LoadConfig("toolsconfig"); + + // Only initialize if speech recognition is enabled in config + if (toolConfig.EnableSpeechRecognition) + { + speechRecognition = new SpeechRecognitionService(); + speechRecognition.SpeechRecognized += (sender, result) => + { + // Use Invoke to update UI from a different thread + if (this.InvokeRequired) + { + this.Invoke(new Action((text) => + { + userInputTextBox.Text = text; + }), result); + } + else + { + userInputTextBox.Text = result; + } + }; + + // Add handler for voice commands + if (toolConfig.EnableVoiceCommands) + { + speechRecognition.CommandRecognized += (sender, command) => + { + if (this.InvokeRequired) + { + this.Invoke(new Action(() => + { + if (!string.IsNullOrWhiteSpace(userInputTextBox.Text)) + { + AddMessage("System", $"Voice command recognized: \"{command}\"", true); + SendButton_Click(this, EventArgs.Empty); + } + })); + } + else + { + if (!string.IsNullOrWhiteSpace(userInputTextBox.Text)) + { + AddMessage("System", $"Voice command recognized: \"{command}\"", true); + SendButton_Click(this, EventArgs.Empty); + } + } + }; + + // Start continuous listening if voice commands are enabled + if (toolConfig.EnableVoiceCommands) + { + //speechRecognition.StartListening(); + } + } + } + else + { + microphoneButton.Enabled = false; + } + } + catch (Exception ex) + { + MessageBox.Show($"Error initializing speech recognition: {ex.Message}", "Speech Recognition Error"); + microphoneButton.Enabled = false; + } + } + + private void MicrophoneButton_Click(object sender, EventArgs e) + { + if (speechRecognition == null) + { + MessageBox.Show("Speech recognition is not available.", "Feature Not Available"); + return; + } + + if (isListening) + { + StopListening(); + } + else + { + StartListening(); + } + } + + /// + /// Starts listening for voice input and updates UI accordingly. + /// + private void StartListening() + { + try + { + var toolConfig = ToolConfig.LoadConfig("toolsconfig"); + + // Clear existing text before starting to listen + userInputTextBox.Text = ""; + + // Change button appearance to indicate recording + microphoneButton.Text = "⏹️"; + microphoneButton.BackColor = Color.Red; + isListening = true; + + // Start listening + speechRecognition.StartListening(); + + // Add temporary message to indicate we're listening + AddMessage("System", "Listening... Speak now." + + (toolConfig.EnableVoiceCommands ? + $" Say \"{toolConfig.VoiceCommandPhrase}\" to send your message." : ""), true); + } + catch (Exception ex) + { + MessageBox.Show($"Error starting voice recognition: {ex.Message}", "Voice Recognition Error"); + StopListening(); + } + } + + private void StopListening() + { + try + { + // Change button appearance back to normal + microphoneButton.Text = "🎤"; + microphoneButton.BackColor = SystemColors.Control; + isListening = false; + + // Stop listening + speechRecognition.StopListening(); + + // Remove the listening message + RemoveMessagesByAuthor("System"); + + // If we have recognized text, send it automatically + if (!string.IsNullOrWhiteSpace(userInputTextBox.Text)) + { + SendButton_Click(this, EventArgs.Empty); + } + } + catch (Exception ex) + { + MessageBox.Show($"Error stopping voice recognition: {ex.Message}", "Voice Recognition Error"); + } + } + private void allowUserInput(bool enable) { - userInputTextBox.Enabled = enable; - sendButton.Enabled = enable; + userInputTextBox.Enabled = enable; + sendButton.Enabled = enable; + microphoneButton.Enabled = enable && speechRecognition != null; } private async void SendButton_Click(object sender, EventArgs e) { allowUserInput(false); + var toolConfig = ToolConfig.LoadConfig("toolsconfig"); // Check if the user input is empty string userInput = userInputTextBox.Text; @@ -152,14 +384,12 @@ private async void SendButton_Click(object sender, EventArgs e) // Add user message to UI AddMessage("You", userInput, false); - try { string aiResponse = await GetAIResponseAsync(userInput); AddMessage("AI", aiResponse, true); - + // Check if we should retain chat history - var toolConfig = ToolConfig.LoadConfig("toolsconfig"); if (!toolConfig.RetainChatHistory) { // Keep only the latest exchange in chat history @@ -172,11 +402,13 @@ private async void SendButton_Click(object sender, EventArgs e) catch (Exception ex) { MessageBox.Show($"Error communicating with AI: {ex.Message}", "Error"); + } + finally + { + userInputTextBox.Clear(); + RemoveMessagesByAuthor("System"); allowUserInput(true); } - - userInputTextBox.Clear(); - allowUserInput(true); } private async Task GetAIResponseAsync(string userInput) @@ -184,39 +416,98 @@ private async Task GetAIResponseAsync(string userInput) // Get the current config to determine which model to use var actionerConfig = APIConfig.LoadConfig("actioner"); var githubConfig = APIConfig.LoadConfig("github"); - var toolConfig = ToolConfig.LoadConfig("toolsconfig"); // Added to ensure we have the latest config // Create a StringBuilder to collect plugin output StringBuilder pluginOutput = new StringBuilder(); - + // Define a plugin output handler that adds messages to the plugin output - PluginOutputHandler outputHandler = (message) => { + PluginOutputHandler outputHandler = (message) => + { pluginOutput.AppendLine(message); }; - // Use Actioner model - passing down the toolConfig to ensure chat history setting is properly applied - Actioner actioner = new Actioner(outputHandler); - if(toolConfig.RetainChatHistory) + // Show a pre-execution notification to the user in the UI + AddMessage("System", "Your request is being processed. Please wait...", true); + + string aiResponse = string.Empty; + + // Use TaskNotifier to wrap the action execution with notifications + try { - actioner.SetChatHistory(chatHistory); + await TaskNotifier.RunWithNotificationAsync( + "Request Processing", + "Processing your request and preparing tools", + async () => + { + var toolConfig = ToolConfig.LoadConfig("toolsconfig"); + + // Use Actioner model with the notification infrastructure + Actioner actioner = new Actioner(outputHandler); + + // Set multi-agent mode based on tool configuration + actioner.SetMultiAgentMode(toolConfig.EnableMultiAgentMode); + + if (toolConfig.RetainChatHistory) + { + actioner.SetChatHistory(chatHistory); + } + + // Execute the action and get the AI response + aiResponse = await actioner.ExecuteAction(userInput); + }); + + // If there's plugin output, prepend it to the AI response + if (pluginOutput.Length > 0) + { + aiResponse = $"{pluginOutput}\n\n{aiResponse}"; + } + + // Remove the "processing" message from the UI + RemoveMessageIfSystem(); + return aiResponse; } - - // Execute the action and get the AI response - string aiResponse = await actioner.ExecuteAction(userInput); - - // If there's plugin output, prepend it to the AI response - if (pluginOutput.Length > 0) + catch (Exception ex) { - return $"{pluginOutput}\n\n{aiResponse}"; + // Remove the "processing" message from the UI + RemoveMessageIfSystem(); + + return $"Error processing request: {ex.Message}"; } - - return aiResponse; } - private void AddMessage(string author, string message, bool isInbound) + private void RemoveMessageIfSystem() // Add missing RemoveMessageIfSystem method + { + // Find and remove any "System" messages that indicate processing + for (int i = messagesPanel.Controls.Count - 1; i >= 0; i--) + { + Control c = messagesPanel.Controls[i]; + if (c is Panel bubble) + { + Label authorLabel = bubble.Controls.OfType