3525e6b8fb
Go orchestrator: spawns audiotee for capture (system audio + mic, two tracks), segments with an energy-threshold VAD, transcribes each segment via qwen-asr (subprocess per segment, vendored as a pinned git submodule in third_party/qwen-asr), and serves the live transcript over SSE while also writing it durably to a text file. Includes a glossary/prompt-leakage guard (internal/asr/leak.go) that discards a segment if the model echoes the biasing prompt instead of transcribing. cmd/transcriptor-ai is the main orchestrator; cmd/transcript-tail is a minimal terminal SSE client. See CLAUDE.md for the full architecture and the reasoning behind each choice (Go over Python/Swift/Rust, SSE over WebSocket, why the VAD is a hand-rolled heuristic, etc). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
67 lines
1.9 KiB
Go
67 lines
1.9 KiB
Go
// Command transcript-tail is a minimal terminal client for transcriptor-ai's
|
|
// live SSE transcript endpoint — connects, parses each `data: {...}` event,
|
|
// and prints just the text, instead of raw JSON (`curl -N` works too, but
|
|
// isn't pleasant to read live). See CLAUDE.md for the SSE message format.
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/stephanetailland/transcriptor-ai/internal/transcript"
|
|
)
|
|
|
|
func main() {
|
|
if err := run(); err != nil {
|
|
fmt.Fprintln(os.Stderr, "transcript-tail:", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func run() error {
|
|
url := flag.String("url", "http://localhost:8420/events", "transcriptor-ai SSE endpoint to follow")
|
|
flag.Parse()
|
|
|
|
resp, err := http.Get(*url)
|
|
if err != nil {
|
|
return fmt.Errorf("connect: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("unexpected status: %s", resp.Status)
|
|
}
|
|
|
|
scanner := bufio.NewScanner(resp.Body)
|
|
for scanner.Scan() {
|
|
data, ok := strings.CutPrefix(scanner.Text(), "data: ")
|
|
if !ok {
|
|
continue // SSE keep-alive/blank lines, comments, etc.
|
|
}
|
|
var msg transcript.Message
|
|
if err := json.Unmarshal([]byte(data), &msg); err != nil {
|
|
continue
|
|
}
|
|
printMessage(msg)
|
|
}
|
|
// The connection ending here — whether cleanly or as a network-level
|
|
// error — almost always just means the transcriptor-ai server stopped.
|
|
// We can't reliably tell that apart from a real connection problem, so
|
|
// don't be alarmist about it: this isn't a failure of transcript-tail
|
|
// itself, the way a startup connection error (above) is.
|
|
fmt.Fprintln(os.Stderr, "transcript-tail: stream ended (server stopped?)")
|
|
return nil
|
|
}
|
|
|
|
func printMessage(msg transcript.Message) {
|
|
marker := " "
|
|
if !msg.IsFinal {
|
|
marker = "…" // still a partial hypothesis, may be revised
|
|
}
|
|
fmt.Printf("[%s%s] %s\n", msg.Track, marker, msg.Text)
|
|
}
|