1
0

feat: Implement history tracking with new CLI commands for viewing past transcripts and usage statistics.

This commit is contained in:
2026-02-28 14:06:58 +01:00
parent eadbd8d46d
commit a08838fbc4
12 changed files with 349 additions and 4 deletions

View 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
View 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)}");
}
}
}