@dxos/pipeline-transcription 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/lib/neutral/chunk-Y44GOMGN.mjs +22 -0
  2. package/dist/lib/neutral/chunk-Y44GOMGN.mjs.map +7 -0
  3. package/dist/lib/neutral/index.mjs +886 -0
  4. package/dist/lib/neutral/index.mjs.map +7 -0
  5. package/dist/lib/neutral/meta.json +1 -0
  6. package/dist/lib/neutral/testing/index.mjs +192 -0
  7. package/dist/lib/neutral/testing/index.mjs.map +7 -0
  8. package/dist/types/src/PipelineConfig.d.ts +49 -0
  9. package/dist/types/src/PipelineConfig.d.ts.map +1 -0
  10. package/dist/types/src/PipelineConfig.test.d.ts +2 -0
  11. package/dist/types/src/PipelineConfig.test.d.ts.map +1 -0
  12. package/dist/types/src/PipelineRuntime.d.ts +46 -0
  13. package/dist/types/src/PipelineRuntime.d.ts.map +1 -0
  14. package/dist/types/src/PipelineRuntime.test.d.ts +2 -0
  15. package/dist/types/src/PipelineRuntime.test.d.ts.map +1 -0
  16. package/dist/types/src/Stage.d.ts +80 -0
  17. package/dist/types/src/Stage.d.ts.map +1 -0
  18. package/dist/types/src/TranscriptEvent.d.ts +23 -0
  19. package/dist/types/src/TranscriptEvent.d.ts.map +1 -0
  20. package/dist/types/src/asr/audio-recorder.d.ts +47 -0
  21. package/dist/types/src/asr/audio-recorder.d.ts.map +1 -0
  22. package/dist/types/src/asr/index.d.ts +5 -0
  23. package/dist/types/src/asr/index.d.ts.map +1 -0
  24. package/dist/types/src/asr/pipeline.d.ts +42 -0
  25. package/dist/types/src/asr/pipeline.d.ts.map +1 -0
  26. package/dist/types/src/asr/sentence-buffer.d.ts +25 -0
  27. package/dist/types/src/asr/sentence-buffer.d.ts.map +1 -0
  28. package/dist/types/src/asr/sentence-buffer.test.d.ts +2 -0
  29. package/dist/types/src/asr/sentence-buffer.test.d.ts.map +1 -0
  30. package/dist/types/src/asr/transcriber.d.ts +115 -0
  31. package/dist/types/src/asr/transcriber.d.ts.map +1 -0
  32. package/dist/types/src/asr/transcriber.test.d.ts +2 -0
  33. package/dist/types/src/asr/transcriber.test.d.ts.map +1 -0
  34. package/dist/types/src/asr/util.d.ts +2 -0
  35. package/dist/types/src/asr/util.d.ts.map +1 -0
  36. package/dist/types/src/dispatch.d.ts +19 -0
  37. package/dist/types/src/dispatch.d.ts.map +1 -0
  38. package/dist/types/src/dispatch.test.d.ts +2 -0
  39. package/dist/types/src/dispatch.test.d.ts.map +1 -0
  40. package/dist/types/src/index.d.ts +11 -0
  41. package/dist/types/src/index.d.ts.map +1 -0
  42. package/dist/types/src/live.d.ts +24 -0
  43. package/dist/types/src/live.d.ts.map +1 -0
  44. package/dist/types/src/live.test.d.ts +2 -0
  45. package/dist/types/src/live.test.d.ts.map +1 -0
  46. package/dist/types/src/lookup.d.ts +35 -0
  47. package/dist/types/src/lookup.d.ts.map +1 -0
  48. package/dist/types/src/lookup.test.d.ts +2 -0
  49. package/dist/types/src/lookup.test.d.ts.map +1 -0
  50. package/dist/types/src/model-routing.d.ts +12 -0
  51. package/dist/types/src/model-routing.d.ts.map +1 -0
  52. package/dist/types/src/pipeline-integration.test.d.ts +2 -0
  53. package/dist/types/src/pipeline-integration.test.d.ts.map +1 -0
  54. package/dist/types/src/stages/correction-llm.d.ts +14 -0
  55. package/dist/types/src/stages/correction-llm.d.ts.map +1 -0
  56. package/dist/types/src/stages/correction-llm.test.d.ts +2 -0
  57. package/dist/types/src/stages/correction-llm.test.d.ts.map +1 -0
  58. package/dist/types/src/stages/correction.d.ts +29 -0
  59. package/dist/types/src/stages/correction.d.ts.map +1 -0
  60. package/dist/types/src/stages/correction.test.d.ts +2 -0
  61. package/dist/types/src/stages/correction.test.d.ts.map +1 -0
  62. package/dist/types/src/stages/diarization.d.ts +11 -0
  63. package/dist/types/src/stages/diarization.d.ts.map +1 -0
  64. package/dist/types/src/stages/extraction.d.ts +40 -0
  65. package/dist/types/src/stages/extraction.d.ts.map +1 -0
  66. package/dist/types/src/stages/extraction.test.d.ts +2 -0
  67. package/dist/types/src/stages/extraction.test.d.ts.map +1 -0
  68. package/dist/types/src/stages/index.d.ts +18 -0
  69. package/dist/types/src/stages/index.d.ts.map +1 -0
  70. package/dist/types/src/stages/summarization.d.ts +12 -0
  71. package/dist/types/src/stages/summarization.d.ts.map +1 -0
  72. package/dist/types/src/stages/translation.d.ts +11 -0
  73. package/dist/types/src/stages/translation.d.ts.map +1 -0
  74. package/dist/types/src/testing/conversation-parser.d.ts +26 -0
  75. package/dist/types/src/testing/conversation-parser.d.ts.map +1 -0
  76. package/dist/types/src/testing/conversation.test.d.ts +2 -0
  77. package/dist/types/src/testing/conversation.test.d.ts.map +1 -0
  78. package/dist/types/src/testing/fixtures.d.ts +6 -0
  79. package/dist/types/src/testing/fixtures.d.ts.map +1 -0
  80. package/dist/types/src/testing/index.d.ts +5 -0
  81. package/dist/types/src/testing/index.d.ts.map +1 -0
  82. package/dist/types/src/testing/scripted-source.d.ts +16 -0
  83. package/dist/types/src/testing/scripted-source.d.ts.map +1 -0
  84. package/dist/types/src/testing/stream-simulator.d.ts +38 -0
  85. package/dist/types/src/testing/stream-simulator.d.ts.map +1 -0
  86. package/dist/types/tsconfig.tsbuildinfo +1 -0
  87. package/package.json +61 -0
  88. package/src/PipelineConfig.test.ts +39 -0
  89. package/src/PipelineConfig.ts +71 -0
  90. package/src/PipelineRuntime.test.ts +74 -0
  91. package/src/PipelineRuntime.ts +192 -0
  92. package/src/Stage.ts +86 -0
  93. package/src/TranscriptEvent.ts +23 -0
  94. package/src/asr/audio-recorder.ts +56 -0
  95. package/src/asr/index.ts +8 -0
  96. package/src/asr/pipeline.ts +105 -0
  97. package/src/asr/sentence-buffer.test.ts +53 -0
  98. package/src/asr/sentence-buffer.ts +88 -0
  99. package/src/asr/transcriber.test.ts +104 -0
  100. package/src/asr/transcriber.ts +337 -0
  101. package/src/asr/util.ts +14 -0
  102. package/src/dispatch.test.ts +37 -0
  103. package/src/dispatch.ts +56 -0
  104. package/src/index.ts +14 -0
  105. package/src/live.test.ts +63 -0
  106. package/src/live.ts +56 -0
  107. package/src/lookup.test.ts +41 -0
  108. package/src/lookup.ts +60 -0
  109. package/src/model-routing.ts +25 -0
  110. package/src/pipeline-integration.test.ts +70 -0
  111. package/src/stages/correction-llm.test.ts +46 -0
  112. package/src/stages/correction-llm.ts +40 -0
  113. package/src/stages/correction.test.ts +26 -0
  114. package/src/stages/correction.ts +72 -0
  115. package/src/stages/diarization.ts +24 -0
  116. package/src/stages/extraction.test.ts +79 -0
  117. package/src/stages/extraction.ts +151 -0
  118. package/src/stages/index.ts +44 -0
  119. package/src/stages/summarization.ts +37 -0
  120. package/src/stages/translation.ts +26 -0
  121. package/src/testing/conversation-parser.ts +101 -0
  122. package/src/testing/conversation.test.ts +53 -0
  123. package/src/testing/conversation.txt +23 -0
  124. package/src/testing/fixtures.ts +18 -0
  125. package/src/testing/index.ts +8 -0
  126. package/src/testing/scripted-source.ts +35 -0
  127. package/src/testing/stream-simulator.ts +125 -0
@@ -0,0 +1,125 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ // Turns a parsed conversation into a chronologically-ordered sequence of Whisper-shaped
6
+ // transcript batches, one per (speaker, utterance) pair. Each batch carries a virtual
7
+ // timestamp (ms since the conversation start) and the speaker label so consumers can fan
8
+ // out per-stream — analogous to two microphones being transcribed in parallel.
9
+ //
10
+ // The simulator does NOT do any wall-clock waiting. Tests drive virtual time via
11
+ // `vi.useFakeTimers()` or step through the batches directly; production code wouldn't use
12
+ // this module at all.
13
+
14
+ import { type ContentBlock } from '@dxos/types';
15
+
16
+ import type { ConversationUtterance, ParsedConversation } from './conversation-parser';
17
+
18
+ export type StreamSimulatorOptions = {
19
+ /**
20
+ * Average speaking rate, words per second. Used to assign a virtual duration to each
21
+ * utterance so consecutive utterances land at realistic offsets.
22
+ * @default 2.5 (≈150 wpm — typical conversational pace)
23
+ */
24
+ wordsPerSecond?: number;
25
+
26
+ /**
27
+ * Gap between consecutive utterances in seconds. Models a brief pause for turn-taking
28
+ * even when speakers don't actually overlap.
29
+ * @default 0.5
30
+ */
31
+ gapSeconds?: number;
32
+
33
+ /**
34
+ * Whisper batches typically arrive in chunks of N seconds rather than per-utterance.
35
+ * When set, utterances shorter than `batchSeconds` are merged into the same batch as
36
+ * the following utterance from the same speaker. `0` disables merging — every utterance
37
+ * is its own batch.
38
+ * @default 0
39
+ */
40
+ batchSeconds?: number;
41
+ };
42
+
43
+ export type SimulatedBatch = {
44
+ /** Speaker label from the fixture (`A`, `B`, …). Maps to a virtual `identityDid`. */
45
+ speaker: string;
46
+ /** ms offset from the conversation start when this batch is delivered. */
47
+ offsetMs: number;
48
+ /** Whisper-shaped transcript blocks (raw text, no enrichment). */
49
+ blocks: ContentBlock.Transcript[];
50
+ };
51
+
52
+ /**
53
+ * Build the chronological batch schedule for a parsed conversation. The result is a
54
+ * pure data structure — consumers iterate and drive virtual time themselves.
55
+ */
56
+ export const simulateStream = (
57
+ conversation: ParsedConversation,
58
+ options: StreamSimulatorOptions = {},
59
+ ): SimulatedBatch[] => {
60
+ const wordsPerSecond = options.wordsPerSecond ?? 2.5;
61
+ const gapSeconds = options.gapSeconds ?? 0.5;
62
+ const batchSeconds = options.batchSeconds ?? 0;
63
+
64
+ // Guard against divide-by-zero / NaN / negative inputs — `wordsPerSecond` divides into a
65
+ // duration below, and the other knobs feed into offsets that must be finite.
66
+ if (!Number.isFinite(wordsPerSecond) || wordsPerSecond <= 0) {
67
+ throw new Error(`simulateStream: wordsPerSecond must be a finite number > 0 (got ${wordsPerSecond}).`);
68
+ }
69
+ if (!Number.isFinite(gapSeconds) || gapSeconds < 0) {
70
+ throw new Error(`simulateStream: gapSeconds must be a finite number >= 0 (got ${gapSeconds}).`);
71
+ }
72
+ if (!Number.isFinite(batchSeconds) || batchSeconds < 0) {
73
+ throw new Error(`simulateStream: batchSeconds must be a finite number >= 0 (got ${batchSeconds}).`);
74
+ }
75
+
76
+ const conversationStart = Date.UTC(2024, 0, 1, 12, 0, 0); // deterministic, ignored by callers
77
+ let cursorMs = 0;
78
+ const batches: SimulatedBatch[] = [];
79
+
80
+ // Optionally merge same-speaker consecutive utterances until they reach `batchSeconds`.
81
+ const grouped: { speaker: string; utterances: ConversationUtterance[] }[] = [];
82
+ if (batchSeconds === 0) {
83
+ for (const u of conversation.utterances) {
84
+ grouped.push({ speaker: u.speaker, utterances: [u] });
85
+ }
86
+ } else {
87
+ let pending: { speaker: string; utterances: ConversationUtterance[]; words: number } | undefined;
88
+ for (const u of conversation.utterances) {
89
+ const words = utteranceWordCount(u.text);
90
+ if (pending && pending.speaker === u.speaker && pending.words / wordsPerSecond < batchSeconds) {
91
+ pending.utterances.push(u);
92
+ pending.words += words;
93
+ } else {
94
+ if (pending) {
95
+ grouped.push({ speaker: pending.speaker, utterances: pending.utterances });
96
+ }
97
+ pending = { speaker: u.speaker, utterances: [u], words };
98
+ }
99
+ }
100
+ if (pending) {
101
+ grouped.push({ speaker: pending.speaker, utterances: pending.utterances });
102
+ }
103
+ }
104
+
105
+ for (const group of grouped) {
106
+ const startMs = cursorMs;
107
+ const blocks: ContentBlock.Transcript[] = [];
108
+ let segmentCursorMs = cursorMs;
109
+ for (const u of group.utterances) {
110
+ const durationMs = Math.max(500, (utteranceWordCount(u.text) / wordsPerSecond) * 1_000);
111
+ blocks.push({
112
+ _tag: 'transcript',
113
+ started: new Date(conversationStart + segmentCursorMs).toISOString(),
114
+ text: u.text,
115
+ });
116
+ segmentCursorMs += durationMs;
117
+ }
118
+ batches.push({ speaker: group.speaker, offsetMs: startMs, blocks });
119
+ cursorMs = segmentCursorMs + gapSeconds * 1_000;
120
+ }
121
+
122
+ return batches;
123
+ };
124
+
125
+ const utteranceWordCount = (text: string): number => text.trim().split(/\s+/).filter(Boolean).length;