Files
sttlab-tech 3525e6b8fb initial commit: live meeting transcription pipeline
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>
2026-08-09 13:18:57 +02:00

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)
}