Skip to content

Commit 56ac831

Browse files
authored
Implement live chunked STT runtime
Implement live chunked local STT runtime with chunk metadata, retry/resume support, bounded live audio queues, and progress reporting.
1 parent fc0daef commit 56ac831

10 files changed

Lines changed: 1517 additions & 131 deletions

File tree

apps/desktop/main.cjs

Lines changed: 331 additions & 33 deletions
Large diffs are not rendered by default.

apps/desktop/main/native-launch.node-test.cjs

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,97 @@ const assert = require("node:assert/strict");
22

33
process.env.MIRROR_NOTE_TEST_EXPORTS = "1";
44

5-
const { nativeHelperWorkingDirectory } = require("../main.cjs");
5+
const {
6+
latestFailedSTTChunks,
7+
latestRetryableSTTChunks,
8+
nativeHelperWorkingDirectory,
9+
normalizeTranscriptSegments,
10+
parseSTTChunkLedgerJSONL,
11+
parseTranscriptJSONL,
12+
serializeTranscriptJSONL,
13+
} = require("../main.cjs");
614

715
assert.equal(nativeHelperWorkingDirectory(), process.cwd());
16+
17+
const runtimeSegment = {
18+
id: "seg-1",
19+
sessionID: "session-a",
20+
chunkID: "session-a:chunk:0",
21+
eventType: "final_segment",
22+
retryState: "completed",
23+
startTimeSeconds: 1,
24+
endTimeSeconds: 2,
25+
text: "hello",
26+
source: "microphone",
27+
};
28+
assert.deepEqual(normalizeTranscriptSegments([runtimeSegment])[0], runtimeSegment);
29+
assert.deepEqual(parseTranscriptJSONL(serializeTranscriptJSONL([runtimeSegment]))[0], runtimeSegment);
30+
31+
const ledgerRecords = parseSTTChunkLedgerJSONL([
32+
JSON.stringify({
33+
sessionID: "session-a",
34+
chunkID: "session-a:microphone:0",
35+
source: "microphone",
36+
chunkIndex: 0,
37+
status: "queued",
38+
startTimeSeconds: 1,
39+
endTimeSeconds: 2,
40+
sampleCount: 16000,
41+
}),
42+
JSON.stringify({
43+
sessionID: "session-a",
44+
chunkID: "session-a:microphone:0",
45+
source: "microphone",
46+
chunkIndex: 0,
47+
status: "failed",
48+
startTimeSeconds: 1,
49+
endTimeSeconds: 2,
50+
sampleCount: 16000,
51+
error: "boom",
52+
}),
53+
JSON.stringify({
54+
sessionID: "session-a",
55+
chunkID: "session-a:system:1",
56+
source: "system",
57+
chunkIndex: 1,
58+
status: "completed",
59+
startTimeSeconds: 3,
60+
endTimeSeconds: 4,
61+
sampleCount: 16000,
62+
segmentCount: 1,
63+
}),
64+
].join("\n"));
65+
assert.deepEqual(latestFailedSTTChunks(ledgerRecords).map((record) => record.chunkID), [
66+
"session-a:microphone:0",
67+
]);
68+
assert.deepEqual(
69+
latestRetryableSTTChunks(ledgerRecords, new Set(["session-a:system:1"])).map((record) => record.chunkID),
70+
["session-a:system:1"],
71+
);
72+
73+
const interruptedLedgerRecords = parseSTTChunkLedgerJSONL([
74+
JSON.stringify({
75+
sessionID: "session-a",
76+
chunkID: "session-a:microphone:2",
77+
source: "microphone",
78+
chunkIndex: 2,
79+
status: "running",
80+
startTimeSeconds: 5,
81+
endTimeSeconds: 10,
82+
sampleCount: 80000,
83+
}),
84+
JSON.stringify({
85+
sessionID: "session-a",
86+
chunkID: "session-a:system:3",
87+
source: "system",
88+
chunkIndex: 3,
89+
status: "queued",
90+
startTimeSeconds: 12,
91+
endTimeSeconds: 17,
92+
sampleCount: 80000,
93+
}),
94+
].join("\n"));
95+
assert.deepEqual(latestRetryableSTTChunks(interruptedLedgerRecords, new Set()).map((record) => record.chunkID), [
96+
"session-a:microphone:2",
97+
"session-a:system:3",
98+
]);

apps/desktop/src/App.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -704,8 +704,18 @@ export function App() {
704704
const percentLabel = Number.isFinite(percentage)
705705
? `${Math.round(Math.max(0, Math.min(1, percentage)) * 100)}%`
706706
: "";
707+
const completedChunks = Number(event.transcriptionProgress.completedChunkCount || 0);
708+
const runningChunks = Number(event.transcriptionProgress.runningChunkCount || 0);
709+
const failedChunks = Number(event.transcriptionProgress.failedChunkCount || 0);
710+
const chunkLabel = completedChunks > 0 || runningChunks > 0 || failedChunks > 0
711+
? ` · ${t("status.chunkProgress", {
712+
completed: completedChunks,
713+
running: runningChunks,
714+
failed: failedChunks,
715+
})}`
716+
: "";
707717
const finalizingTranscript = t("status.finalizingTranscript");
708-
setCaptureDetail(percentLabel ? `${finalizingTranscript} ${percentLabel}` : finalizingTranscript);
718+
setCaptureDetail(percentLabel ? `${finalizingTranscript} ${percentLabel}${chunkLabel}` : `${finalizingTranscript}${chunkLabel}`);
709719
return;
710720
}
711721
if (event.kind === "finished") {

apps/desktop/src/i18n.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ type I18nKey =
7979
| "sidebar.templates"
8080
| "status.creatingNote"
8181
| "status.deletingNote"
82+
| "status.chunkProgress"
8283
| "status.finalizingTranscript"
8384
| "status.generatingSummary"
8485
| "status.loadingNotes"
@@ -246,6 +247,7 @@ const translations: Record<AppLanguage, Record<I18nKey, string>> = {
246247
"sidebar.templates": "Templates",
247248
"status.creatingNote": "Creating note...",
248249
"status.deletingNote": "Deleting note...",
250+
"status.chunkProgress": "chunks {completed} done, {running} running, {failed} failed",
249251
"status.finalizingTranscript": "Finalizing transcript...",
250252
"status.generatingSummary": "Generating summary...",
251253
"status.loadingNotes": "Loading notes...",
@@ -412,6 +414,7 @@ const translations: Record<AppLanguage, Record<I18nKey, string>> = {
412414
"sidebar.templates": "템플릿",
413415
"status.creatingNote": "노트 생성 중...",
414416
"status.deletingNote": "노트 삭제 중...",
417+
"status.chunkProgress": "청크 {completed} 완료, {running} 실행 중, {failed} 실패",
415418
"status.finalizingTranscript": "전사 마무리 중...",
416419
"status.generatingSummary": "요약 생성 중...",
417420
"status.loadingNotes": "노트 불러오는 중...",

apps/desktop/src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ export type CaptureEvent =
180180
processedSeconds?: number;
181181
totalSeconds?: number;
182182
percentage?: number;
183+
queuedChunkCount?: number;
184+
runningChunkCount?: number;
185+
completedChunkCount?: number;
186+
failedChunkCount?: number;
183187
};
184188
}
185189
| { kind: "finished" };

0 commit comments

Comments
 (0)