1
0

refactor: centralize common strings, paths, and default values into a new Constants class.

This commit is contained in:
2026-02-28 15:41:40 +01:00
parent 0577640da9
commit 96ccf0ea9a
13 changed files with 80 additions and 38 deletions

View File

@@ -20,7 +20,7 @@ public class GroqApiClient : ISpeechClient, ILlmClient
_httpClient.BaseAddress = new Uri("https://api.groq.com/openai/v1/"); _httpClient.BaseAddress = new Uri("https://api.groq.com/openai/v1/");
} }
public async Task<string> TranscribeAsync(string filePath, string language = "", string model = "whisper-large-v3-turbo") public async Task<string> TranscribeAsync(string filePath, string language = "", string model = Toak.Core.Constants.Defaults.WhisperModel)
{ {
using var content = new MultipartFormDataContent(); using var content = new MultipartFormDataContent();
using var fileStream = File.OpenRead(filePath); using var fileStream = File.OpenRead(filePath);
@@ -29,7 +29,7 @@ public class GroqApiClient : ISpeechClient, ILlmClient
streamContent.Headers.ContentType = new MediaTypeHeaderValue("audio/wav"); // or mpeg streamContent.Headers.ContentType = new MediaTypeHeaderValue("audio/wav"); // or mpeg
content.Add(streamContent, "file", Path.GetFileName(filePath)); content.Add(streamContent, "file", Path.GetFileName(filePath));
string modelToUse = string.IsNullOrWhiteSpace(model) ? "whisper-large-v3-turbo" : model; string modelToUse = string.IsNullOrWhiteSpace(model) ? Toak.Core.Constants.Defaults.WhisperModel : model;
// according to docs whisper-large-v3-turbo requires the language to be provided if it is to be translated later potentially or if we need the most accurate behavior // according to docs whisper-large-v3-turbo requires the language to be provided if it is to be translated later potentially or if we need the most accurate behavior
// Actually, if we want language param, we can pass it to either model // Actually, if we want language param, we can pass it to either model
@@ -56,11 +56,11 @@ public class GroqApiClient : ISpeechClient, ILlmClient
return result?.Text ?? string.Empty; return result?.Text ?? string.Empty;
} }
public async Task<string> RefineTextAsync(string rawTranscript, string systemPrompt, string model = "openai/gpt-oss-20b") public async Task<string> RefineTextAsync(string rawTranscript, string systemPrompt, string model = Toak.Core.Constants.Defaults.LlmModel)
{ {
var requestBody = new LlamaRequest var requestBody = new LlamaRequest
{ {
Model = string.IsNullOrWhiteSpace(model) ? "openai/gpt-oss-20b" : model, Model = string.IsNullOrWhiteSpace(model) ? Toak.Core.Constants.Defaults.LlmModel : model,
Temperature = 0.0, Temperature = 0.0,
Messages = new[] Messages = new[]
{ {
@@ -87,11 +87,11 @@ public class GroqApiClient : ISpeechClient, ILlmClient
return result?.Choices?.FirstOrDefault()?.Message?.Content ?? string.Empty; return result?.Choices?.FirstOrDefault()?.Message?.Content ?? string.Empty;
} }
public async IAsyncEnumerable<string> RefineTextStreamAsync(string rawTranscript, string systemPrompt, string model = "openai/gpt-oss-20b") public async IAsyncEnumerable<string> RefineTextStreamAsync(string rawTranscript, string systemPrompt, string model = Toak.Core.Constants.Defaults.LlmModel)
{ {
var requestBody = new LlamaRequest var requestBody = new LlamaRequest
{ {
Model = string.IsNullOrWhiteSpace(model) ? "openai/gpt-oss-20b" : model, Model = string.IsNullOrWhiteSpace(model) ? Toak.Core.Constants.Defaults.LlmModel : model,
Temperature = 0.0, Temperature = 0.0,
Stream = true, Stream = true,
Messages = new[] Messages = new[]

View File

@@ -9,7 +9,7 @@ namespace Toak.Audio;
public class AudioRecorder : IAudioRecorder public class AudioRecorder : IAudioRecorder
{ {
private readonly string WavPath = Path.Combine(Path.GetTempPath(), "toak_recording.wav"); private readonly string WavPath = Constants.Paths.RecordingWavFile;
private readonly IRecordingStateTracker _stateTracker; private readonly IRecordingStateTracker _stateTracker;
private readonly INotifications _notifications; private readonly INotifications _notifications;
@@ -33,7 +33,7 @@ public class AudioRecorder : IAudioRecorder
var pInfo = new ProcessStartInfo var pInfo = new ProcessStartInfo
{ {
FileName = "pw-record", FileName = Constants.Commands.AudioRecord,
Arguments = $"--rate=16000 --channels=1 --format=s16 \"{WavPath}\"", Arguments = $"--rate=16000 --channels=1 --format=s16 \"{WavPath}\"",
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true, CreateNoWindow = true,
@@ -63,7 +63,7 @@ public class AudioRecorder : IAudioRecorder
// Gracefully stop pw-record using SIGINT to ensure WAV headers are finalizing cleanly // Gracefully stop pw-record using SIGINT to ensure WAV headers are finalizing cleanly
Process.Start(new ProcessStartInfo Process.Start(new ProcessStartInfo
{ {
FileName = "kill", FileName = Constants.Commands.ProcessKill,
Arguments = $"-INT {pid.Value}", Arguments = $"-INT {pid.Value}",
CreateNoWindow = true, CreateNoWindow = true,
UseShellExecute = false UseShellExecute = false

View File

@@ -22,11 +22,11 @@ public static class LatencyTestCommand
} }
AnsiConsole.MarkupLine("Generating 1-second silent audio file for testing..."); AnsiConsole.MarkupLine("Generating 1-second silent audio file for testing...");
var testWavPath = Path.Combine(Path.GetTempPath(), "toak_latency_test.wav"); var testWavPath = Constants.Paths.LatencyTestWavFile;
var pInfo = new ProcessStartInfo var pInfo = new ProcessStartInfo
{ {
FileName = "ffmpeg", FileName = Constants.Commands.AudioFfmpeg,
Arguments = $"-f lavfi -i anullsrc=r=44100:cl=mono -t 1 -y {testWavPath}", Arguments = $"-f lavfi -i anullsrc=r=44100:cl=mono -t 1 -y {testWavPath}",
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true, CreateNoWindow = true,

View File

@@ -1,6 +1,7 @@
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Toak.Core;
using Toak.Serialization; using Toak.Serialization;
using Toak.Core.Interfaces; using Toak.Core.Interfaces;
@@ -8,12 +9,11 @@ namespace Toak.Configuration;
public class ConfigManager : IConfigProvider public class ConfigManager : IConfigProvider
{ {
private readonly string ConfigDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", "toak"); private readonly string ConfigDir = Constants.Paths.ConfigDir;
private readonly string ConfigPath; private readonly string ConfigPath = Constants.Paths.ConfigFile;
public ConfigManager() public ConfigManager()
{ {
ConfigPath = Path.Combine(ConfigDir, "config.json");
} }
public ToakConfig LoadConfig() public ToakConfig LoadConfig()

View File

@@ -8,8 +8,8 @@ public class ToakConfig
public bool ModuleTechnicalSanitization { get; set; } = true; public bool ModuleTechnicalSanitization { get; set; } = true;
public string WhisperLanguage { get; set; } = string.Empty; public string WhisperLanguage { get; set; } = string.Empty;
public string LlmModel { get; set; } = "openai/gpt-oss-20b"; public string LlmModel { get; set; } = Toak.Core.Constants.Defaults.LlmModel;
public string WhisperModel { get; set; } = "whisper-large-v3-turbo"; public string WhisperModel { get; set; } = Toak.Core.Constants.Defaults.WhisperModel;
public string StartSoundPath { get; set; } = "Assets/Audio/beep.wav"; public string StartSoundPath { get; set; } = "Assets/Audio/beep.wav";
public string StopSoundPath { get; set; } = "Assets/Audio/beep.wav"; public string StopSoundPath { get; set; } = "Assets/Audio/beep.wav";
public List<string> ActiveSkills { get; set; } = new List<string> { "Terminal", "Translate" }; public List<string> ActiveSkills { get; set; } = new List<string> { "Terminal", "Translate" };

48
Core/Constants.cs Normal file
View File

@@ -0,0 +1,48 @@
using System;
using System.IO;
namespace Toak.Core;
public static class Constants
{
public const string AppName = "toak";
public static class Paths
{
public static readonly string AppDataDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), AppName);
public static readonly string ConfigDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", AppName);
public static readonly string ConfigFile = Path.Combine(ConfigDir, "config.json");
public static readonly string HistoryFile = Path.Combine(AppDataDir, "history.jsonl");
public static readonly string DaemonLockFile = Path.Combine(AppDataDir, "daemon.lock");
public static readonly string StateFile = Path.Combine(Path.GetTempPath(), "toak_state.pid");
public static readonly string RecordingWavFile = Path.Combine(Path.GetTempPath(), "toak_recording.wav");
public static readonly string LatencyTestWavFile = Path.Combine(Path.GetTempPath(), "toak_latency_test.wav");
public static string GetSocketPath()
{
var runtimeDir = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR");
return Path.Combine(string.IsNullOrEmpty(runtimeDir) ? Path.GetTempPath() : runtimeDir, "toak.sock");
}
}
public static class Commands
{
public const string AudioRecord = "pw-record";
public const string AudioFfmpeg = "ffmpeg";
public const string ProcessKill = "kill";
public const string TypeX11 = "xdotool";
public const string TypeWayland = "wtype";
public const string Notify = "notify-send";
public const string PlaySound = "paplay";
public const string ClipboardX11 = "xclip";
public const string ClipboardWayland = "wl-copy";
}
public static class Defaults
{
public const string LlmModel = "openai/gpt-oss-20b";
public const string WhisperModel = "whisper-large-v3-turbo";
}
}

View File

@@ -15,19 +15,14 @@ public static class DaemonService
{ {
public static string GetSocketPath() public static string GetSocketPath()
{ {
var runtimeDir = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); return Constants.Paths.GetSocketPath();
if (string.IsNullOrEmpty(runtimeDir))
{
runtimeDir = Path.GetTempPath();
}
return Path.Combine(runtimeDir, "toak.sock");
} }
private static FileStream? _lockFile; private static FileStream? _lockFile;
public static async Task StartAsync(bool verbose) public static async Task StartAsync(bool verbose)
{ {
var lockPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "toak", "daemon.lock"); var lockPath = Constants.Paths.DaemonLockFile;
try try
{ {
Directory.CreateDirectory(Path.GetDirectoryName(lockPath)!); Directory.CreateDirectory(Path.GetDirectoryName(lockPath)!);

View File

@@ -10,12 +10,11 @@ namespace Toak.Core;
public class HistoryManager : IHistoryManager public class HistoryManager : IHistoryManager
{ {
private readonly string HistoryDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "toak"); private readonly string HistoryDir = Constants.Paths.AppDataDir;
private readonly string HistoryFile; private readonly string HistoryFile = Constants.Paths.HistoryFile;
public HistoryManager() public HistoryManager()
{ {
HistoryFile = Path.Combine(HistoryDir, "history.jsonl");
} }
public void SaveEntry(string rawTranscript, string refinedText, string? skillName, long durationMs) public void SaveEntry(string rawTranscript, string refinedText, string? skillName, long durationMs)

View File

@@ -13,13 +13,13 @@ public interface IConfigProvider
public interface ISpeechClient public interface ISpeechClient
{ {
Task<string> TranscribeAsync(string filePath, string language = "", string model = "whisper-large-v3-turbo"); Task<string> TranscribeAsync(string filePath, string language = "", string model = Toak.Core.Constants.Defaults.WhisperModel);
} }
public interface ILlmClient public interface ILlmClient
{ {
Task<string> RefineTextAsync(string rawTranscript, string systemPrompt, string model = "openai/gpt-oss-20b"); Task<string> RefineTextAsync(string rawTranscript, string systemPrompt, string model = Toak.Core.Constants.Defaults.LlmModel);
IAsyncEnumerable<string> RefineTextStreamAsync(string rawTranscript, string systemPrompt, string model = "openai/gpt-oss-20b"); IAsyncEnumerable<string> RefineTextStreamAsync(string rawTranscript, string systemPrompt, string model = Toak.Core.Constants.Defaults.LlmModel);
} }
public interface IAudioRecorder public interface IAudioRecorder

View File

@@ -4,7 +4,7 @@ namespace Toak.Core;
public class StateTracker : IRecordingStateTracker public class StateTracker : IRecordingStateTracker
{ {
private readonly string StateFilePath = Path.Combine(Path.GetTempPath(), "toak_state.pid"); private readonly string StateFilePath = Constants.Paths.StateFile;
public bool IsRecording() public bool IsRecording()
{ {

View File

@@ -25,7 +25,7 @@ public class ClipboardManager : IClipboardManager
{ {
pInfo = new ProcessStartInfo pInfo = new ProcessStartInfo
{ {
FileName = "wl-copy", FileName = Toak.Core.Constants.Commands.ClipboardWayland,
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true, CreateNoWindow = true,
RedirectStandardInput = true RedirectStandardInput = true
@@ -35,7 +35,7 @@ public class ClipboardManager : IClipboardManager
{ {
pInfo = new ProcessStartInfo pInfo = new ProcessStartInfo
{ {
FileName = "xclip", FileName = Toak.Core.Constants.Commands.ClipboardX11,
Arguments = "-selection clipboard", Arguments = "-selection clipboard",
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true, CreateNoWindow = true,

View File

@@ -16,7 +16,7 @@ public class Notifications : INotifications
{ {
var pInfo = new ProcessStartInfo var pInfo = new ProcessStartInfo
{ {
FileName = "notify-send", FileName = Toak.Core.Constants.Commands.Notify,
Arguments = $"-a \"Toak\" \"{summary}\" \"{body}\"", Arguments = $"-a \"Toak\" \"{summary}\" \"{body}\"",
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true CreateNoWindow = true
@@ -66,7 +66,7 @@ public class Notifications : INotifications
var pInfo = new ProcessStartInfo var pInfo = new ProcessStartInfo
{ {
FileName = "paplay", FileName = Toak.Core.Constants.Commands.PlaySound,
Arguments = $"\"{absolutePath}\"", Arguments = $"\"{absolutePath}\"",
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true CreateNoWindow = true

View File

@@ -28,7 +28,7 @@ public class TextInjector : ITextInjector
Logger.LogDebug($"Injecting text using wtype..."); Logger.LogDebug($"Injecting text using wtype...");
pInfo = new ProcessStartInfo pInfo = new ProcessStartInfo
{ {
FileName = "wtype", FileName = Toak.Core.Constants.Commands.TypeWayland,
Arguments = $"\"{text.Replace("\"", "\\\"")}\"", Arguments = $"\"{text.Replace("\"", "\\\"")}\"",
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true CreateNoWindow = true
@@ -39,7 +39,7 @@ public class TextInjector : ITextInjector
Logger.LogDebug($"Injecting text using xdotool..."); Logger.LogDebug($"Injecting text using xdotool...");
pInfo = new ProcessStartInfo pInfo = new ProcessStartInfo
{ {
FileName = "xdotool", FileName = Toak.Core.Constants.Commands.TypeX11,
Arguments = $"type --clearmodifiers --delay 0 \"{text.Replace("\"", "\\\"")}\"", Arguments = $"type --clearmodifiers --delay 0 \"{text.Replace("\"", "\\\"")}\"",
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true CreateNoWindow = true
@@ -67,7 +67,7 @@ public class TextInjector : ITextInjector
Logger.LogDebug($"Setting up stream injection using wtype..."); Logger.LogDebug($"Setting up stream injection using wtype...");
pInfo = new ProcessStartInfo pInfo = new ProcessStartInfo
{ {
FileName = "wtype", FileName = Toak.Core.Constants.Commands.TypeWayland,
Arguments = "-", Arguments = "-",
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true, CreateNoWindow = true,
@@ -79,7 +79,7 @@ public class TextInjector : ITextInjector
Logger.LogDebug($"Setting up stream injection using xdotool..."); Logger.LogDebug($"Setting up stream injection using xdotool...");
pInfo = new ProcessStartInfo pInfo = new ProcessStartInfo
{ {
FileName = "xdotool", FileName = Toak.Core.Constants.Commands.TypeX11,
Arguments = "type --clearmodifiers --delay 0 --file -", Arguments = "type --clearmodifiers --delay 0 --file -",
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true, CreateNoWindow = true,