feat: Implement history tracking with new CLI commands for viewing past transcripts and usage statistics.
This commit is contained in:
92
Commands/HistoryCommand.cs
Normal file
92
Commands/HistoryCommand.cs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.CommandLine;
|
||||||
|
using Spectre.Console;
|
||||||
|
using Toak.Core;
|
||||||
|
|
||||||
|
namespace Toak.Commands;
|
||||||
|
|
||||||
|
public static class HistoryCommand
|
||||||
|
{
|
||||||
|
public static async Task ExecuteAsync(int count, string grep, string export, bool shred, bool verbose)
|
||||||
|
{
|
||||||
|
Logger.Verbose = verbose;
|
||||||
|
|
||||||
|
if (shred)
|
||||||
|
{
|
||||||
|
HistoryManager.Shred();
|
||||||
|
AnsiConsole.MarkupLine("[green]History successfully shredded.[/]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entries = HistoryManager.LoadEntries();
|
||||||
|
if (entries.Count == 0)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine("[yellow]No history found.[/]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply grep filter
|
||||||
|
if (!string.IsNullOrWhiteSpace(grep))
|
||||||
|
{
|
||||||
|
entries = entries.Where(e =>
|
||||||
|
e.RawTranscript.Contains(grep, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
e.RefinedText.Contains(grep, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (entries.Count == 0)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine($"[yellow]No history entries match '{grep}'.[/]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get last N
|
||||||
|
entries = entries.OrderBy(e => e.Timestamp).TakeLast(count).ToList();
|
||||||
|
|
||||||
|
// Export
|
||||||
|
if (!string.IsNullOrWhiteSpace(export))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var writer = new StreamWriter(export);
|
||||||
|
writer.WriteLine($"# Toak Transcriptions - {DateTime.Now:yyyy-MM-dd}");
|
||||||
|
writer.WriteLine();
|
||||||
|
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
writer.WriteLine($"## {entry.Timestamp.ToLocalTime():HH:mm:ss}");
|
||||||
|
writer.WriteLine(entry.RefinedText);
|
||||||
|
writer.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
AnsiConsole.MarkupLine($"[green]Successfully exported {entries.Count} entries to {export}[/]");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine($"[red]Error exporting history:[/] {ex.Message}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display
|
||||||
|
var table = new Table().Border(TableBorder.Rounded);
|
||||||
|
table.AddColumn("Time");
|
||||||
|
table.AddColumn("Skill");
|
||||||
|
table.AddColumn("Text");
|
||||||
|
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
table.AddRow(
|
||||||
|
$"[dim]{entry.Timestamp.ToLocalTime():HH:mm:ss}[/]",
|
||||||
|
entry.SkillName != null ? $"[blue]{entry.SkillName}[/]" : "-",
|
||||||
|
entry.RefinedText.Replace("[", "[[").Replace("]", "]]") // Escape spectre markup
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AnsiConsole.Write(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
Commands/StatsCommand.cs
Normal file
56
Commands/StatsCommand.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.CommandLine;
|
||||||
|
using Spectre.Console;
|
||||||
|
using Toak.Core;
|
||||||
|
|
||||||
|
namespace Toak.Commands;
|
||||||
|
|
||||||
|
public static class StatsCommand
|
||||||
|
{
|
||||||
|
public static async Task ExecuteAsync(bool verbose)
|
||||||
|
{
|
||||||
|
Logger.Verbose = verbose;
|
||||||
|
|
||||||
|
var entries = HistoryManager.LoadEntries();
|
||||||
|
if (entries.Count == 0)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine("[yellow]No history found. Cannot generate statistics.[/]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalCount = entries.Count;
|
||||||
|
var totalDuration = TimeSpan.FromMilliseconds(entries.Sum(e => e.DurationMs));
|
||||||
|
var avgDuration = TimeSpan.FromMilliseconds(entries.Average(e => e.DurationMs));
|
||||||
|
|
||||||
|
var mostActiveDay = entries
|
||||||
|
.GroupBy(e => e.Timestamp.Date)
|
||||||
|
.OrderByDescending(g => g.Count())
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
var topWords = entries
|
||||||
|
.SelectMany(e => e.RefinedText.Split(new[] { ' ', '.', ',', '!', '?' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
.Where(w => w.Length > 3) // Exclude short common words
|
||||||
|
.GroupBy(w => w.ToLowerInvariant())
|
||||||
|
.OrderByDescending(g => g.Count())
|
||||||
|
.Take(5)
|
||||||
|
.Select(g => g.Key)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
AnsiConsole.MarkupLine("[bold blue]Toak Usage Statistics[/]");
|
||||||
|
AnsiConsole.MarkupLine($"[dim]Total recordings:[/] {totalCount}");
|
||||||
|
AnsiConsole.MarkupLine($"[dim]Total duration:[/] {totalDuration.TotalMinutes:F1}m");
|
||||||
|
AnsiConsole.MarkupLine($"[dim]Average processing latency:[/] {avgDuration.TotalSeconds:F2}s");
|
||||||
|
|
||||||
|
if (mostActiveDay != null)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine($"[dim]Most active day:[/] {mostActiveDay.Key:yyyy-MM-dd} ({mostActiveDay.Count()} recordings)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topWords.Count > 0)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine($"[dim]Top spoken words (>3 chars):[/] {string.Join(", ", topWords)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -169,6 +169,7 @@ public static class DaemonService
|
|||||||
{
|
{
|
||||||
detectedSkill!.Execute(finalText);
|
detectedSkill!.Execute(finalText);
|
||||||
stopWatch.Stop();
|
stopWatch.Stop();
|
||||||
|
HistoryManager.SaveEntry(transcript, finalText, detectedSkill.Name, stopWatch.ElapsedMilliseconds);
|
||||||
Notifications.Notify("Toak", $"Skill executed in {stopWatch.ElapsedMilliseconds}ms");
|
Notifications.Notify("Toak", $"Skill executed in {stopWatch.ElapsedMilliseconds}ms");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,11 +195,13 @@ public static class DaemonService
|
|||||||
ClipboardManager.Copy(fullText);
|
ClipboardManager.Copy(fullText);
|
||||||
Notifications.Notify("Toak", $"Copied to clipboard in {stopWatch.ElapsedMilliseconds}ms");
|
Notifications.Notify("Toak", $"Copied to clipboard in {stopWatch.ElapsedMilliseconds}ms");
|
||||||
}
|
}
|
||||||
|
HistoryManager.SaveEntry(transcript, fullText, detectedSkill?.Name, stopWatch.ElapsedMilliseconds);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await TextInjector.InjectStreamAsync(tokenStream, config.TypingBackend);
|
string fullText = await TextInjector.InjectStreamAsync(tokenStream, config.TypingBackend);
|
||||||
stopWatch.Stop();
|
stopWatch.Stop();
|
||||||
|
HistoryManager.SaveEntry(transcript, fullText, detectedSkill?.Name, stopWatch.ElapsedMilliseconds);
|
||||||
Notifications.Notify("Toak", $"Done in {stopWatch.ElapsedMilliseconds}ms");
|
Notifications.Notify("Toak", $"Done in {stopWatch.ElapsedMilliseconds}ms");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
Core/HistoryEntry.cs
Normal file
12
Core/HistoryEntry.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Toak.Core;
|
||||||
|
|
||||||
|
public class HistoryEntry
|
||||||
|
{
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
|
public string RawTranscript { get; set; } = string.Empty;
|
||||||
|
public string RefinedText { get; set; } = string.Empty;
|
||||||
|
public string? SkillName { get; set; }
|
||||||
|
public long DurationMs { get; set; }
|
||||||
|
}
|
||||||
102
Core/HistoryManager.cs
Normal file
102
Core/HistoryManager.cs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Toak.Serialization;
|
||||||
|
|
||||||
|
namespace Toak.Core;
|
||||||
|
|
||||||
|
public static class HistoryManager
|
||||||
|
{
|
||||||
|
private static readonly string HistoryDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "toak");
|
||||||
|
private static readonly string HistoryFile = Path.Combine(HistoryDir, "history.jsonl");
|
||||||
|
|
||||||
|
public static void SaveEntry(string rawTranscript, string refinedText, string? skillName, long durationMs)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(HistoryDir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(HistoryDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry = new HistoryEntry
|
||||||
|
{
|
||||||
|
Timestamp = DateTime.UtcNow,
|
||||||
|
RawTranscript = rawTranscript,
|
||||||
|
RefinedText = refinedText,
|
||||||
|
SkillName = skillName,
|
||||||
|
DurationMs = durationMs
|
||||||
|
};
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(entry, AppJsonSerializerContext.Default.HistoryEntry);
|
||||||
|
|
||||||
|
// Thread-safe append
|
||||||
|
lock (HistoryFile)
|
||||||
|
{
|
||||||
|
File.AppendAllLines(HistoryFile, new[] { json });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogDebug($"Failed to save history: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<HistoryEntry> LoadEntries()
|
||||||
|
{
|
||||||
|
var entries = new List<HistoryEntry>();
|
||||||
|
if (!File.Exists(HistoryFile)) return entries;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string[] lines;
|
||||||
|
lock (HistoryFile)
|
||||||
|
{
|
||||||
|
lines = File.ReadAllLines(HistoryFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||||
|
var entry = JsonSerializer.Deserialize(line, AppJsonSerializerContext.Default.HistoryEntry);
|
||||||
|
if (entry != null)
|
||||||
|
{
|
||||||
|
entries.Add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogDebug($"Failed to load history: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Shred()
|
||||||
|
{
|
||||||
|
if (File.Exists(HistoryFile))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (HistoryFile)
|
||||||
|
{
|
||||||
|
// Securely delete
|
||||||
|
var len = new FileInfo(HistoryFile).Length;
|
||||||
|
using (var fs = new FileStream(HistoryFile, FileMode.Open, FileAccess.Write))
|
||||||
|
{
|
||||||
|
var blank = new byte[len];
|
||||||
|
fs.Write(blank, 0, blank.Length);
|
||||||
|
}
|
||||||
|
File.Delete(HistoryFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogDebug($"Failed to shred history: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,8 +46,9 @@ public static class TextInjector
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task InjectStreamAsync(IAsyncEnumerable<string> tokenStream, string backend)
|
public static async Task<string> InjectStreamAsync(IAsyncEnumerable<string> tokenStream, string backend)
|
||||||
{
|
{
|
||||||
|
string fullText = string.Empty;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ProcessStartInfo pInfo;
|
ProcessStartInfo pInfo;
|
||||||
@@ -77,13 +78,14 @@ public static class TextInjector
|
|||||||
}
|
}
|
||||||
|
|
||||||
using var process = Process.Start(pInfo);
|
using var process = Process.Start(pInfo);
|
||||||
if (process == null) return;
|
if (process == null) return string.Empty;
|
||||||
|
|
||||||
Logger.LogDebug("Started stream injection process, waiting for tokens...");
|
Logger.LogDebug("Started stream injection process, waiting for tokens...");
|
||||||
|
|
||||||
await foreach (var token in tokenStream)
|
await foreach (var token in tokenStream)
|
||||||
{
|
{
|
||||||
Logger.LogDebug($"Injecting token: '{token}'");
|
Logger.LogDebug($"Injecting token: '{token}'");
|
||||||
|
fullText += token;
|
||||||
await process.StandardInput.WriteAsync(token);
|
await process.StandardInput.WriteAsync(token);
|
||||||
await process.StandardInput.FlushAsync();
|
await process.StandardInput.FlushAsync();
|
||||||
}
|
}
|
||||||
@@ -97,5 +99,6 @@ public static class TextInjector
|
|||||||
Console.WriteLine($"[TextInjector] Error injecting text stream: {ex.Message}");
|
Console.WriteLine($"[TextInjector] Error injecting text stream: {ex.Message}");
|
||||||
Notifications.Notify("Injection Error", "Could not type text stream into window.");
|
Notifications.Notify("Injection Error", "Could not type text stream into window.");
|
||||||
}
|
}
|
||||||
|
return fullText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
Program.cs
18
Program.cs
@@ -58,6 +58,24 @@ public class Program
|
|||||||
configCmd.SetHandler(ConfigUpdaterCommand.ExecuteAsync, keyArg, valArg, verboseOption);
|
configCmd.SetHandler(ConfigUpdaterCommand.ExecuteAsync, keyArg, valArg, verboseOption);
|
||||||
rootCommand.AddCommand(configCmd);
|
rootCommand.AddCommand(configCmd);
|
||||||
|
|
||||||
|
// Stats Command
|
||||||
|
var statsCmd = new Command("stats", "Display usage statistics and analytics");
|
||||||
|
statsCmd.SetHandler(StatsCommand.ExecuteAsync, verboseOption);
|
||||||
|
rootCommand.AddCommand(statsCmd);
|
||||||
|
|
||||||
|
// History Command
|
||||||
|
var historyCmd = new Command("history", "Display recent transcriptions with timestamps");
|
||||||
|
var numArg = new Option<int>(new[] { "-n", "--num" }, () => 10, "Number of recent entries to show");
|
||||||
|
var grepArg = new Option<string>("--grep", "Search through transcription history");
|
||||||
|
var exportArg = new Option<string>("--export", "Export transcription history to a Markdown file");
|
||||||
|
var shredArg = new Option<bool>("--shred", "Securely delete transcription history");
|
||||||
|
historyCmd.AddOption(numArg);
|
||||||
|
historyCmd.AddOption(grepArg);
|
||||||
|
historyCmd.AddOption(exportArg);
|
||||||
|
historyCmd.AddOption(shredArg);
|
||||||
|
historyCmd.SetHandler(HistoryCommand.ExecuteAsync, numArg, grepArg, exportArg, shredArg, verboseOption);
|
||||||
|
rootCommand.AddCommand(historyCmd);
|
||||||
|
|
||||||
// Skill Command
|
// Skill Command
|
||||||
rootCommand.AddCommand(SkillCommand.CreateCommand(verboseOption));
|
rootCommand.AddCommand(SkillCommand.CreateCommand(verboseOption));
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ To remove Toak from your system, simply run:
|
|||||||
- **`toak show`**: Displays your current configuration in a clean table.
|
- **`toak show`**: Displays your current configuration in a clean table.
|
||||||
- **`toak config <key> <value>`**: Quickly update a specific setting (e.g., `toak config whisper whisper-large-v3-turbo`).
|
- **`toak config <key> <value>`**: Quickly update a specific setting (e.g., `toak config whisper whisper-large-v3-turbo`).
|
||||||
- **`toak skill`**: Manage dynamic JSON skills via `list`, `add`, or `remove` subcommands.
|
- **`toak skill`**: Manage dynamic JSON skills via `list`, `add`, or `remove` subcommands.
|
||||||
|
- **`toak history`**: Display your recent dictation history (`-n <count>`, `--grep <pattern>`, `--export <file>`, `--shred`).
|
||||||
|
- **`toak stats`**: Display usage statistics and analytics like most active day and top words.
|
||||||
|
|
||||||
### Flags
|
### Flags
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace Toak.Serialization;
|
|||||||
[JsonSerializable(typeof(LlamaStreamDelta))]
|
[JsonSerializable(typeof(LlamaStreamDelta))]
|
||||||
[JsonSerializable(typeof(LlamaStreamChoice[]))]
|
[JsonSerializable(typeof(LlamaStreamChoice[]))]
|
||||||
[JsonSerializable(typeof(Toak.Core.Skills.SkillDefinition))]
|
[JsonSerializable(typeof(Toak.Core.Skills.SkillDefinition))]
|
||||||
|
[JsonSerializable(typeof(Toak.Core.HistoryEntry))]
|
||||||
internal partial class AppJsonSerializerContext : JsonSerializerContext
|
internal partial class AppJsonSerializerContext : JsonSerializerContext
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
9
_toak
9
_toak
@@ -20,6 +20,8 @@ _toak() {
|
|||||||
'show:Show current configuration'
|
'show:Show current configuration'
|
||||||
'config:Update a specific configuration setting'
|
'config:Update a specific configuration setting'
|
||||||
'skill:Manage dynamic skills (list, add, remove)'
|
'skill:Manage dynamic skills (list, add, remove)'
|
||||||
|
'history:Display recent transcriptions with timestamps'
|
||||||
|
'stats:Display usage statistics and analytics'
|
||||||
)
|
)
|
||||||
|
|
||||||
_arguments -C \
|
_arguments -C \
|
||||||
@@ -56,6 +58,13 @@ _toak() {
|
|||||||
)
|
)
|
||||||
_describe -t commands 'skill command' skill_cmds
|
_describe -t commands 'skill command' skill_cmds
|
||||||
;;
|
;;
|
||||||
|
history)
|
||||||
|
_arguments \
|
||||||
|
'(-n --num)'{-n,--num}'[Number of recent entries to show]:count:(5 10 20 50)' \
|
||||||
|
'--grep[Search through transcription history]:pattern:' \
|
||||||
|
'--export[Export transcription history to a Markdown file]:file:_files' \
|
||||||
|
'--shred[Securely delete transcription history]'
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
_message "no more arguments"
|
_message "no more arguments"
|
||||||
;;
|
;;
|
||||||
|
|||||||
43
docs/HISTORY_AND_STATS.md
Normal file
43
docs/HISTORY_AND_STATS.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# History and Stats Implementation Plan
|
||||||
|
|
||||||
|
This document outlines the design and implementation of the `history` and `stats` features in Toak.
|
||||||
|
|
||||||
|
## Data Storage
|
||||||
|
All transcriptions will be stored in a JSON Lines (`.jsonl`) file located at `~/.local/share/toak/history.jsonl`.
|
||||||
|
Since Toak uses Native AOT and JSON serialization needs source generation, we'll keep the model simple.
|
||||||
|
|
||||||
|
**Entry Model:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Timestamp": "2025-01-15T09:23:00Z",
|
||||||
|
"RawTranscript": "hello world",
|
||||||
|
"RefinedText": "Hello world.",
|
||||||
|
"SkillName": "Professional", // null if default type/script
|
||||||
|
"DurationMs": 1500 // time taken for STT + LLM
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `toak history` Command
|
||||||
|
Provides access to past dictations.
|
||||||
|
|
||||||
|
- `toak history` - Shows the last 10 entries.
|
||||||
|
- `toak history -n <count>` - Shows the last `<count>` entries.
|
||||||
|
- `toak history --grep <pattern>` - Filters the history entries matching the given keyword in the RefinedText (case-insensitive).
|
||||||
|
- `toak history --export <file>` - Writes the output as a Markdown file.
|
||||||
|
- `toak history --shred` - Deletes the `history.jsonl` file entirely.
|
||||||
|
|
||||||
|
## `toak stats` Command
|
||||||
|
Reads the `history.jsonl` file and outputs usage analytics using `Spectre.Console`.
|
||||||
|
|
||||||
|
**Metrics:**
|
||||||
|
- Total recording count
|
||||||
|
- Total processing duration (sum of `DurationMs`)
|
||||||
|
- Average processing duration
|
||||||
|
- Most active day
|
||||||
|
- Most frequently used skill (if any)
|
||||||
|
|
||||||
|
## Architecture Changes
|
||||||
|
1. **`HistoryManager.cs`**: Handles thread-safe appending `HistoryEntry` to the `.jsonl` file, reading, and clearing.
|
||||||
|
2. **`DaemonService.cs`**: Calls `HistoryManager.SaveEntry` during the `ProcessStopRecordingAsync` method after text is finalized.
|
||||||
|
3. **`HistoryCommand.cs` & `StatsCommand.cs`**: CLI command definitions.
|
||||||
|
4. **`AppJsonSerializerContext.cs`**: Needs `[JsonSerializable(typeof(HistoryEntry))]`.
|
||||||
@@ -23,13 +23,17 @@ Toak/
|
|||||||
│ ├── ConfigUpdaterCommand.cs # Direct configuration modifications
|
│ ├── ConfigUpdaterCommand.cs # Direct configuration modifications
|
||||||
│ ├── ShowCommand.cs # Display current configuration
|
│ ├── ShowCommand.cs # Display current configuration
|
||||||
│ ├── SkillCommand.cs # CLI controller for discovering and adding Dynamic JSON Skills
|
│ ├── SkillCommand.cs # CLI controller for discovering and adding Dynamic JSON Skills
|
||||||
│ └── LatencyTestCommand.cs # Benchmark tool for API calls
|
│ ├── LatencyTestCommand.cs # Benchmark tool for API calls
|
||||||
|
│ ├── HistoryCommand.cs # CLI interface to query, export, or shred past transcripts
|
||||||
|
│ └── StatsCommand.cs # CLI interface to calculate analytics from history
|
||||||
├── Configuration/
|
├── Configuration/
|
||||||
│ ├── ConfigManager.cs # Loads and saves JSON configuration from the user's home folder
|
│ ├── ConfigManager.cs # Loads and saves JSON configuration from the user's home folder
|
||||||
│ └── ToakConfig.cs # Data model for user preferences
|
│ └── ToakConfig.cs # Data model for user preferences
|
||||||
├── Core/
|
├── Core/
|
||||||
│ ├── DaemonService.cs # The background daemon maintaining the socket server and handling states
|
│ ├── DaemonService.cs # The background daemon maintaining the socket server and handling states
|
||||||
│ ├── Logger.cs # Logging utility (verbose logging)
|
│ ├── Logger.cs # Logging utility (verbose logging)
|
||||||
|
│ ├── HistoryManager.cs # Manages appending and reading the local history.jsonl
|
||||||
|
│ ├── HistoryEntry.cs # The data model for transcription history
|
||||||
│ ├── PromptBuilder.cs # Constructs the system prompts for the LLM based on user settings
|
│ ├── PromptBuilder.cs # Constructs the system prompts for the LLM based on user settings
|
||||||
│ ├── StateTracker.cs # Tracks the current application state (e.g. is recording active?)
|
│ ├── StateTracker.cs # Tracks the current application state (e.g. is recording active?)
|
||||||
│ └── Skills/ # Data-driven JSON skill integrations
|
│ └── Skills/ # Data-driven JSON skill integrations
|
||||||
|
|||||||
Reference in New Issue
Block a user