feat: upload audio, get a private Swedish transcript, discuss it (#33, ADR-033) #39

Merged
mathias merged 299 commits from feat/33-audio-upload-transcript into main 2026-10-03 14:13:37 +00:00
Showing only changes of commit 500e11962b - Show all commits
+59 -1
View File
@@ -121,7 +121,7 @@ func TestGateway_NormalisesTheVerboseJSONResponse(t *testing.T) {
got, err := g.Transcribe(context.Background(), wavFile(t, "x"))
require.NoError(t, err)
assert.Equal(t, "God morgon allihopa.\n Vi går igenom läget.", got.Text)
assert.Equal(t, "God morgon allihopa. Vi går igenom läget.", got.Text)
assert.Equal(t, "iguana/kb-whisper", got.Endpoint, "the serving endpoint is part of the result")
assert.InDelta(t, 4.25, got.AudioSeconds, 1e-9)
assert.Equal(t, []domain.Segment{
@@ -172,3 +172,61 @@ func TestGateway_MissingAudioFileIsAnError(t *testing.T) {
assert.Error(t, err)
}
// whisper.cpp wraps its output at ~60 characters, and a wrap can land INSIDE a word:
// the continuation segment has no leading space, the `text` field joins segments
// with newlines, and a segment can be a lone ".". Measured on real gateway output
// 2026-10-01: 7% of a one-hour recording's segments were such fragments. Joining by
// newline hands the chat model broken words ("nyt\nt"), so Text is rebuilt from the
// segments: a segment that begins with a space starts a new word, one that does not
// continues the previous one.
func TestGateway_RejoinsWordsWhisperCppSplitAcrossSegments(t *testing.T) {
reply := `{
"text": " Vi ligger just nu 12 procent över den ursprungliga kalkylen\n.\n Det kräver ett nyt\nt godkännande från beställaren.\n Framförallt på arbetspl\natsen.\n",
"language": "swedish", "duration": 20,
"segments": [
{"start": 0, "end": 4, "text": " Vi ligger just nu 12 procent över den ursprungliga kalkylen"},
{"start": 4, "end": 5, "text": "."},
{"start": 5, "end": 8, "text": " Det kräver ett nyt"},
{"start": 8, "end": 11, "text": "t godkännande från beställaren."},
{"start": 11, "end": 14, "text": " Framförallt på arbetspl"},
{"start": 14, "end": 16, "text": "atsen."}
]}`
srv := serve(t, 200, reply, nil)
g := transcribe.NewGateway(srv.URL+"/v1", "k", "iguana/kb-whisper", srv.Client())
got, err := g.Transcribe(context.Background(), wavFile(t, "x"))
require.NoError(t, err)
assert.Equal(t, "Vi ligger just nu 12 procent över den ursprungliga kalkylen. "+
"Det kräver ett nytt godkännande från beställaren. Framförallt på arbetsplatsen.", got.Text)
assert.Len(t, got.Segments, 6, "the timed segments are kept as the backend gave them (trimmed)")
assert.Equal(t, ".", got.Segments[1].Text)
}
func TestGateway_SentenceLevelSegmentsWithNoLeadingSpaceAreJoinedWithASpace(t *testing.T) {
// berget/kb-whisper returns whole sentences with no leading space; rebuilding by
// concatenation would glue them ("allihopa!Vi").
reply := `{"text": "God morgon allihopa!\nVi går igenom läget.", "language": "sv", "duration": 5,
"segments": [
{"start": 0.05, "end": 1.2, "text": "God morgon allihopa!"},
{"start": 1.6, "end": 4.2, "text": "Vi går igenom läget."}
]}`
srv := serve(t, 200, reply, nil)
g := transcribe.NewGateway(srv.URL+"/v1", "k", "berget/kb-whisper", srv.Client())
got, err := g.Transcribe(context.Background(), wavFile(t, "x"))
require.NoError(t, err)
assert.Equal(t, "God morgon allihopa! Vi går igenom läget.", got.Text)
}
func TestGateway_WithNoSegmentsTheTextFieldIsUsedWithWhitespaceCollapsed(t *testing.T) {
srv := serve(t, 200, `{"text": " Hej allihopa.\n Vi börjar. ", "duration": 3, "segments": []}`, nil)
g := transcribe.NewGateway(srv.URL+"/v1", "k", "m", srv.Client())
got, err := g.Transcribe(context.Background(), wavFile(t, "x"))
require.NoError(t, err)
assert.Equal(t, "Hej allihopa. Vi börjar.", got.Text)
}