feat: Implement a modular skill system with hotword detection, streaming text output, and enhanced logging.
This commit is contained in:
15
Core/Logger.cs
Normal file
15
Core/Logger.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Toak.Core;
|
||||
|
||||
public static class Logger
|
||||
{
|
||||
public static bool Verbose { get; set; } = false;
|
||||
|
||||
public static void LogDebug(string message)
|
||||
{
|
||||
if (Verbose)
|
||||
{
|
||||
var logLine = $"[DEBUG] {DateTime.Now:HH:mm:ss.fff} - {message}";
|
||||
Console.WriteLine(logLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ public static class PromptBuilder
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("FORMATTING RULES:");
|
||||
sb.AppendLine("- CRITICAL: If the <transcript> contains nothing, or very short gibberish, output NOTHING AT ALL (an empty string).");
|
||||
sb.AppendLine("- LANGUAGE DETECT: The transcript may be in English or a different language (e.g., Hungarian, Spanish). Detect the language and ensure your output and grammar corrections are STRICTLY in that same language.");
|
||||
|
||||
|
||||
|
||||
|
||||
13
Core/Skills/ISkill.cs
Normal file
13
Core/Skills/ISkill.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Toak.Core.Skills;
|
||||
|
||||
public interface ISkill
|
||||
{
|
||||
string Name { get; }
|
||||
string Description { get; }
|
||||
string[] Hotwords { get; }
|
||||
|
||||
bool HandlesExecution { get; }
|
||||
|
||||
string GetSystemPrompt(string rawTranscript);
|
||||
void Execute(string llmResult);
|
||||
}
|
||||
29
Core/Skills/SkillRegistry.cs
Normal file
29
Core/Skills/SkillRegistry.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace Toak.Core.Skills;
|
||||
|
||||
public static class SkillRegistry
|
||||
{
|
||||
public static readonly ISkill[] AllSkills = new ISkill[]
|
||||
{
|
||||
new TerminalSkill(),
|
||||
new TranslateSkill()
|
||||
};
|
||||
|
||||
public static ISkill? DetectSkill(string transcript, IEnumerable<string> activeSkillNames)
|
||||
{
|
||||
var activeSkills = AllSkills.Where(s => activeSkillNames.Contains(s.Name, StringComparer.OrdinalIgnoreCase)).ToList();
|
||||
|
||||
string normalizedTranscript = transcript.Trim();
|
||||
|
||||
foreach (var skill in activeSkills)
|
||||
{
|
||||
foreach (var hotword in skill.Hotwords)
|
||||
{
|
||||
if (normalizedTranscript.StartsWith(hotword, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return skill;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
41
Core/Skills/TerminalSkill.cs
Normal file
41
Core/Skills/TerminalSkill.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Toak.Core.Skills;
|
||||
|
||||
public class TerminalSkill : ISkill
|
||||
{
|
||||
public string Name => "Terminal";
|
||||
public string Description => "Translates an intent into a bash command and runs it in the background.";
|
||||
public string[] Hotwords => new[] { "System terminal", "System command" };
|
||||
|
||||
public bool HandlesExecution => true;
|
||||
|
||||
public string GetSystemPrompt(string rawTranscript)
|
||||
{
|
||||
return @"You are a command-line assistant. The user will ask you to perform a task.
|
||||
Translate the request into a single bash command.
|
||||
Output ONLY the raw bash command to achieve this task. Do not include markdown formatting, backticks, or explanations.";
|
||||
}
|
||||
|
||||
public void Execute(string llmResult)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"[TerminalSkill] Executing: {llmResult}");
|
||||
var escapedCmd = llmResult.Replace("\"", "\\\"");
|
||||
var pInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "bash",
|
||||
Arguments = $"-c \"{escapedCmd}\"",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
Process.Start(pInfo);
|
||||
IO.Notifications.Notify("Toak Terminal Executed", llmResult);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[TerminalSkill Error] {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Core/Skills/TranslateSkill.cs
Normal file
23
Core/Skills/TranslateSkill.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace Toak.Core.Skills;
|
||||
|
||||
public class TranslateSkill : ISkill
|
||||
{
|
||||
public string Name => "Translate";
|
||||
public string Description => "Translates the spoken text into another language on the fly.";
|
||||
public string[] Hotwords => new[] { "System translate to", "System translate into" };
|
||||
|
||||
public bool HandlesExecution => false;
|
||||
|
||||
public string GetSystemPrompt(string rawTranscript)
|
||||
{
|
||||
return @"You are an expert translator. The user wants to translate the following text.
|
||||
The first few words identify the target language (e.g. 'Translate to Spanish:', 'Translate into Hungarian:').
|
||||
Translate the REST of the transcript into that target language.
|
||||
Output ONLY the final translated text. Do not include markdown, explanations, or quotes.";
|
||||
}
|
||||
|
||||
public void Execute(string llmResult)
|
||||
{
|
||||
// Not used since HandlesExecution is false
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ public static class StateTracker
|
||||
|
||||
public static void SetRecording(int ffmpegPid)
|
||||
{
|
||||
Logger.LogDebug($"Setting recording state with PID {ffmpegPid}");
|
||||
File.WriteAllText(StateFilePath, ffmpegPid.ToString());
|
||||
}
|
||||
|
||||
@@ -21,6 +22,7 @@ public static class StateTracker
|
||||
var content = File.ReadAllText(StateFilePath).Trim();
|
||||
if (int.TryParse(content, out var pid))
|
||||
{
|
||||
Logger.LogDebug($"Read recording PID {pid} from state file");
|
||||
return pid;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +33,7 @@ public static class StateTracker
|
||||
{
|
||||
if (File.Exists(StateFilePath))
|
||||
{
|
||||
Logger.LogDebug("Clearing recording state file");
|
||||
File.Delete(StateFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user