using System; using System.Diagnostics; using System.IO; using System.Threading.Tasks; using Spectre.Console; using Toak.Audio; using Toak.Configuration; using Toak.Api; using Toak.Core; using Toak.IO; namespace Toak.Commands; public static class ToggleCommand { public static async Task ExecuteAsync(bool pipeToStdout, bool copyToClipboard, bool verbose) { Logger.Verbose = verbose; if (StateTracker.IsRecording()) { var config = ConfigManager.LoadConfig(); Notifications.PlaySound(config.StopSoundPath); if (!pipeToStdout) AnsiConsole.MarkupLine("[yellow]Stopping recording and transcribing...[/]"); if (!pipeToStdout) Notifications.Notify("Toak", "Transcribing..."); AudioRecorder.StopRecording(); Logger.LogDebug($"Loaded configuration: LLM={config.LlmModel}, Whisper={config.WhisperModel}, Typing={config.TypingBackend}"); if (string.IsNullOrWhiteSpace(config.GroqApiKey)) { Notifications.Notify("Toak Error", "Groq API Key is not configured. Run 'toak onboard'."); AnsiConsole.MarkupLine("[red]Groq API Key is not configured.[/] Run 'toak onboard'."); return; } var groq = new GroqApiClient(config.GroqApiKey); var wavPath = AudioRecorder.GetWavPath(); if (!File.Exists(wavPath) || new FileInfo(wavPath).Length == 0) { if (!pipeToStdout) Notifications.Notify("Toak", "No audio recorded."); return; } try { var stopWatch = Stopwatch.StartNew(); // 1. STT Logger.LogDebug($"Starting STT transcription via Whisper for {wavPath}..."); string transcript = string.Empty; if (!pipeToStdout) { await AnsiConsole.Status().StartAsync("Transcribing...", async ctx => { transcript = await groq.TranscribeAsync(wavPath, config.WhisperLanguage, config.WhisperModel); }); } else { transcript = await groq.TranscribeAsync(wavPath, config.WhisperLanguage, config.WhisperModel); } Logger.LogDebug($"Raw transcript received: '{transcript}'"); if (string.IsNullOrWhiteSpace(transcript)) { if (!pipeToStdout) Notifications.Notify("Toak", "No speech detected."); return; } // 2. LLM Refinement var detectedSkill = Toak.Core.Skills.SkillRegistry.DetectSkill(transcript, config.ActiveSkills); string systemPrompt; if (detectedSkill != null) { Logger.LogDebug($"Skill detected: {detectedSkill.Name}"); if (!pipeToStdout) Notifications.Notify("Toak Skill Detected", detectedSkill.Name); systemPrompt = detectedSkill.GetSystemPrompt(transcript); } else { systemPrompt = PromptBuilder.BuildPrompt(config); } bool isExecutionSkill = detectedSkill != null && detectedSkill.HandlesExecution; // 3. Output if (isExecutionSkill || pipeToStdout || copyToClipboard) { Logger.LogDebug("Starting LLM text refinement (synchronous)..."); string finalText = string.Empty; if (!pipeToStdout) { await AnsiConsole.Status().StartAsync("Refining text...", async ctx => { finalText = await groq.RefineTextAsync(transcript, systemPrompt, config.LlmModel); }); } else { finalText = await groq.RefineTextAsync(transcript, systemPrompt, config.LlmModel); } Logger.LogDebug($"Refined text received: '{finalText}'"); if (string.IsNullOrWhiteSpace(finalText)) { if (!pipeToStdout) Notifications.Notify("Toak", "Dropped short or empty audio."); return; } if (isExecutionSkill) { detectedSkill!.Execute(finalText); stopWatch.Stop(); Notifications.Notify("Toak", $"Skill executed in {stopWatch.ElapsedMilliseconds}ms"); } else if (pipeToStdout) { Console.WriteLine(finalText); } else { ClipboardManager.Copy(finalText); stopWatch.Stop(); Notifications.Notify("Toak", $"Copied to clipboard in {stopWatch.ElapsedMilliseconds}ms"); } } else { Logger.LogDebug("Starting LLM text refinement (streaming)..."); var tokenStream = groq.RefineTextStreamAsync(transcript, systemPrompt, config.LlmModel); Logger.LogDebug("Starting to inject text..."); await TextInjector.InjectStreamAsync(tokenStream, config.TypingBackend); stopWatch.Stop(); Notifications.Notify("Toak", $"Done in {stopWatch.ElapsedMilliseconds}ms"); } } catch (Exception ex) { if (!pipeToStdout) Notifications.Notify("Toak Error", ex.Message); if (!pipeToStdout) AnsiConsole.MarkupLine($"[red]Error:[/] {ex.Message}"); } finally { if (File.Exists(wavPath)) File.Delete(wavPath); } } else { // Start recording if (!pipeToStdout) AnsiConsole.MarkupLine("[green]Starting recording...[/]"); var config = ConfigManager.LoadConfig(); Notifications.PlaySound(config.StartSoundPath); AudioRecorder.StartRecording(); } } }