@dxos/plugin-transcription 0.8.4-main.2e9d522 → 0.8.4-main.406dc2a
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.
- package/dist/lib/browser/TranscriptContainer-QN5WUMWJ.mjs +11 -0
- package/dist/lib/browser/blueprint-definition-GO6GTEBT.mjs +150 -0
- package/dist/lib/browser/blueprint-definition-GO6GTEBT.mjs.map +7 -0
- package/dist/lib/browser/{chunk-4FJIYH7T.mjs → chunk-77NWYLC6.mjs} +2 -2
- package/dist/lib/browser/{chunk-5WH45KBY.mjs → chunk-7LNCQDTT.mjs} +9 -4
- package/dist/lib/browser/chunk-7LNCQDTT.mjs.map +7 -0
- package/dist/lib/browser/{chunk-FFEPEPCU.mjs → chunk-COBQIZ6B.mjs} +85 -94
- package/dist/lib/browser/chunk-COBQIZ6B.mjs.map +7 -0
- package/dist/lib/browser/chunk-GGHMOEKZ.mjs +63 -0
- package/dist/lib/browser/chunk-GGHMOEKZ.mjs.map +7 -0
- package/dist/lib/browser/{chunk-U6QBOGTN.mjs → chunk-LCK3YRQF.mjs} +5 -5
- package/dist/lib/browser/chunk-LCK3YRQF.mjs.map +7 -0
- package/dist/lib/browser/{chunk-5KPYIC3N.mjs → chunk-ZS2FXKEF.mjs} +410 -418
- package/dist/lib/browser/chunk-ZS2FXKEF.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +22 -18
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/intent-resolver-52EPFRMR.mjs +25 -0
- package/dist/lib/browser/intent-resolver-52EPFRMR.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{react-surface-E5D5AE3F.mjs → react-surface-LCP2D2MF.mjs} +10 -10
- package/dist/lib/browser/react-surface-LCP2D2MF.mjs.map +7 -0
- package/dist/lib/browser/{transcriber-PXT4QBFY.mjs → transcriber-NG5MRCP6.mjs} +7 -7
- package/dist/lib/browser/transcriber-NG5MRCP6.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +6 -8
- package/dist/lib/node-esm/{TranscriptContainer-VBAOM5DG.mjs → TranscriptContainer-WCUWVVSD.mjs} +4 -4
- package/dist/lib/node-esm/blueprint-definition-6SU7GLBR.mjs +151 -0
- package/dist/lib/node-esm/blueprint-definition-6SU7GLBR.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-HT3W7LDP.mjs → chunk-A6SB234F.mjs} +85 -94
- package/dist/lib/node-esm/chunk-A6SB234F.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-FTTZ4YSX.mjs +64 -0
- package/dist/lib/node-esm/chunk-FTTZ4YSX.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-XMUCZBP3.mjs → chunk-KLTPFUKN.mjs} +8 -4
- package/dist/lib/node-esm/chunk-KLTPFUKN.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-Y2B4HWSS.mjs → chunk-N5Z4SKPH.mjs} +2 -2
- package/dist/lib/node-esm/{chunk-TJKAO7VR.mjs → chunk-PEZRQYKO.mjs} +410 -418
- package/dist/lib/node-esm/chunk-PEZRQYKO.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-RHF2ALML.mjs → chunk-RWRG3S2O.mjs} +5 -5
- package/dist/lib/node-esm/chunk-RWRG3S2O.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +22 -18
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/intent-resolver-DZBQQRHN.mjs +26 -0
- package/dist/lib/node-esm/intent-resolver-DZBQQRHN.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/{react-surface-QRDJOHN5.mjs → react-surface-7UC2YXX2.mjs} +10 -10
- package/dist/lib/node-esm/react-surface-7UC2YXX2.mjs.map +7 -0
- package/dist/lib/node-esm/{transcriber-YUTEVANE.mjs → transcriber-SMU7HD6F.mjs} +7 -7
- package/dist/lib/node-esm/transcriber-SMU7HD6F.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +6 -8
- package/dist/types/src/TranscriptionPlugin.d.ts +1 -1
- package/dist/types/src/TranscriptionPlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/blueprint-definition.d.ts +5 -0
- package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -0
- package/dist/types/src/capabilities/capabilities.d.ts +1 -1
- package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +3 -2
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/intent-resolver.d.ts +1 -1
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +1 -1
- package/dist/types/src/capabilities/transcriber.d.ts +1 -1
- package/dist/types/src/capabilities/transcriber.d.ts.map +1 -1
- package/dist/types/src/components/Transcript/Transcript.d.ts +5 -5
- package/dist/types/src/components/Transcript/Transcript.d.ts.map +1 -1
- package/dist/types/src/components/Transcript/Transcript.stories.d.ts +31 -16
- package/dist/types/src/components/Transcript/Transcript.stories.d.ts.map +1 -1
- package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts +3 -3
- package/dist/types/src/components/Transcript/deprecated/OldTranscript.d.ts.map +1 -1
- package/dist/types/src/components/Transcript/transcript-extension.d.ts +1 -5
- package/dist/types/src/components/Transcript/transcript-extension.d.ts.map +1 -1
- package/dist/types/src/components/TranscriptContainer.d.ts +2 -2
- package/dist/types/src/components/TranscriptContainer.d.ts.map +1 -1
- package/dist/types/src/components/stories/FileTranscription.stories.d.ts +8 -4
- package/dist/types/src/components/stories/FileTranscription.stories.d.ts.map +1 -1
- package/dist/types/src/components/stories/KeyWordRecognition.stories.d.ts +13 -6
- package/dist/types/src/components/stories/KeyWordRecognition.stories.d.ts.map +1 -1
- package/dist/types/src/components/stories/MicrophoneTranscription.stories.d.ts +10 -6
- package/dist/types/src/components/stories/MicrophoneTranscription.stories.d.ts.map +1 -1
- package/dist/types/src/components/stories/TranscriptionStory.d.ts +2 -1
- package/dist/types/src/components/stories/TranscriptionStory.d.ts.map +1 -1
- package/dist/types/src/functions/index.d.ts +3 -0
- package/dist/types/src/functions/index.d.ts.map +1 -0
- package/dist/types/src/functions/open.d.ts +7 -0
- package/dist/types/src/functions/open.d.ts.map +1 -0
- package/dist/types/src/functions/summarize.d.ts +11 -0
- package/dist/types/src/functions/summarize.d.ts.map +1 -0
- package/dist/types/src/hooks/useAudioFile.d.ts.map +1 -1
- package/dist/types/src/hooks/useVoiceInput.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +0 -1
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/segments-normalization/normalization.d.ts +376 -325
- package/dist/types/src/segments-normalization/normalization.d.ts.map +1 -1
- package/dist/types/src/testing/testing.d.ts +2 -2
- package/dist/types/src/testing/testing.d.ts.map +1 -1
- package/dist/types/src/transcriber/media-stream-recorder.d.ts +1 -1
- package/dist/types/src/transcriber/media-stream-recorder.d.ts.map +1 -1
- package/dist/types/src/transcriber/transcriber.d.ts.map +1 -1
- package/dist/types/src/transcriber/transcription-manager.d.ts +3 -7
- package/dist/types/src/transcriber/transcription-manager.d.ts.map +1 -1
- package/dist/types/src/types/Transcript.d.ts +30 -0
- package/dist/types/src/types/Transcript.d.ts.map +1 -0
- package/dist/types/src/types/TranscriptAction.d.ts +23 -0
- package/dist/types/src/types/TranscriptAction.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +2 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/types/types.d.ts +0 -26
- package/dist/types/src/types/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +63 -62
- package/src/TranscriptionPlugin.tsx +57 -53
- package/src/capabilities/blueprint-definition.ts +31 -0
- package/src/capabilities/capabilities.ts +6 -6
- package/src/capabilities/index.ts +1 -0
- package/src/capabilities/intent-resolver.ts +5 -11
- package/src/capabilities/react-surface.tsx +2 -2
- package/src/capabilities/transcriber.ts +3 -2
- package/src/components/Transcript/Transcript.stories.tsx +34 -31
- package/src/components/Transcript/Transcript.tsx +20 -27
- package/src/components/Transcript/deprecated/OldTranscript.tsx +9 -9
- package/src/components/Transcript/transcript-extension.ts +2 -39
- package/src/components/TranscriptContainer.tsx +7 -6
- package/src/components/stories/FileTranscription.stories.tsx +49 -36
- package/src/components/stories/KeyWordRecognition.stories.tsx +17 -10
- package/src/components/stories/MicrophoneTranscription.stories.tsx +43 -38
- package/src/components/stories/TranscriptionStory.tsx +10 -6
- package/src/functions/index.ts +6 -0
- package/src/functions/open.ts +39 -0
- package/src/functions/summarize.ts +103 -0
- package/src/hooks/useAudioFile.ts +49 -53
- package/src/hooks/useVoiceInput.ts +3 -2
- package/src/meta.ts +1 -3
- package/src/model/model.test.ts +1 -1
- package/src/segments-normalization/message-normalizer.ts +1 -1
- package/src/segments-normalization/normalization.test.ts +5 -5
- package/src/segments-normalization/normalization.ts +38 -37
- package/src/testing/testing.ts +7 -7
- package/src/transcriber/media-stream-recorder.ts +1 -1
- package/src/transcriber/transcriber.browser.test.ts +2 -1
- package/src/transcriber/transcriber.ts +2 -1
- package/src/transcriber/transcription-manager.ts +17 -38
- package/src/types/{schema.ts → Transcript.ts} +12 -9
- package/src/types/TranscriptAction.ts +24 -0
- package/src/types/index.ts +2 -1
- package/src/types/types.ts +0 -23
- package/dist/lib/browser/TranscriptContainer-6WCKCXQ6.mjs +0 -11
- package/dist/lib/browser/chunk-5KPYIC3N.mjs.map +0 -7
- package/dist/lib/browser/chunk-5WH45KBY.mjs.map +0 -7
- package/dist/lib/browser/chunk-FFEPEPCU.mjs.map +0 -7
- package/dist/lib/browser/chunk-R7INNNUQ.mjs +0 -55
- package/dist/lib/browser/chunk-R7INNNUQ.mjs.map +0 -7
- package/dist/lib/browser/chunk-U6QBOGTN.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-3O33MMP5.mjs +0 -30
- package/dist/lib/browser/intent-resolver-3O33MMP5.mjs.map +0 -7
- package/dist/lib/browser/react-surface-E5D5AE3F.mjs.map +0 -7
- package/dist/lib/browser/transcriber-PXT4QBFY.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HT3W7LDP.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-RHF2ALML.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-TJKAO7VR.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-U65ZWK3L.mjs +0 -56
- package/dist/lib/node-esm/chunk-U65ZWK3L.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-XMUCZBP3.mjs.map +0 -7
- package/dist/lib/node-esm/intent-resolver-YMN5SXNC.mjs +0 -31
- package/dist/lib/node-esm/intent-resolver-YMN5SXNC.mjs.map +0 -7
- package/dist/lib/node-esm/react-surface-QRDJOHN5.mjs.map +0 -7
- package/dist/lib/node-esm/transcriber-YUTEVANE.mjs.map +0 -7
- package/dist/types/src/types/schema.d.ts +0 -41
- package/dist/types/src/types/schema.d.ts.map +0 -1
- /package/dist/lib/browser/{TranscriptContainer-6WCKCXQ6.mjs.map → TranscriptContainer-QN5WUMWJ.mjs.map} +0 -0
- /package/dist/lib/browser/{chunk-4FJIYH7T.mjs.map → chunk-77NWYLC6.mjs.map} +0 -0
- /package/dist/lib/node-esm/{TranscriptContainer-VBAOM5DG.mjs.map → TranscriptContainer-WCUWVVSD.mjs.map} +0 -0
- /package/dist/lib/node-esm/{chunk-Y2B4HWSS.mjs.map → chunk-N5Z4SKPH.mjs.map} +0 -0
|
@@ -12,14 +12,15 @@ import { ScrollContainer } from '@dxos/react-ui-components';
|
|
|
12
12
|
import { type DataType } from '@dxos/schema';
|
|
13
13
|
|
|
14
14
|
import { type SerializationModel } from '../../model';
|
|
15
|
-
import {
|
|
15
|
+
import { TranscriptView } from '../Transcript';
|
|
16
16
|
|
|
17
17
|
export const TranscriptionStory: FC<{
|
|
18
18
|
model: SerializationModel<DataType.Message>;
|
|
19
|
+
disabled?: boolean;
|
|
19
20
|
running: boolean;
|
|
20
21
|
onRunningChange: Dispatch<SetStateAction<boolean>>;
|
|
21
|
-
audioRef?: RefObject<HTMLAudioElement>;
|
|
22
|
-
}> = ({ model, running, onRunningChange, audioRef }) => {
|
|
22
|
+
audioRef?: RefObject<HTMLAudioElement | null>;
|
|
23
|
+
}> = ({ model, running, onRunningChange, audioRef, disabled }) => {
|
|
23
24
|
const space = useSpace();
|
|
24
25
|
|
|
25
26
|
return (
|
|
@@ -28,14 +29,17 @@ export const TranscriptionStory: FC<{
|
|
|
28
29
|
<Toolbar.Root>
|
|
29
30
|
<IconButton
|
|
30
31
|
iconOnly
|
|
32
|
+
disabled={disabled}
|
|
31
33
|
icon={running ? 'ph--pause--regular' : 'ph--play--regular'}
|
|
32
34
|
label={running ? 'Pause' : 'Play'}
|
|
33
35
|
onClick={() => onRunningChange((running) => !running)}
|
|
34
36
|
/>
|
|
35
37
|
</Toolbar.Root>
|
|
36
|
-
<ScrollContainer>
|
|
37
|
-
<
|
|
38
|
-
|
|
38
|
+
<ScrollContainer.Root pin>
|
|
39
|
+
<ScrollContainer.Content>
|
|
40
|
+
<TranscriptView space={space} model={model} />
|
|
41
|
+
</ScrollContainer.Content>
|
|
42
|
+
</ScrollContainer.Root>
|
|
39
43
|
</div>
|
|
40
44
|
);
|
|
41
45
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
|
|
8
|
+
import { ArtifactId } from '@dxos/assistant';
|
|
9
|
+
import { Obj } from '@dxos/echo';
|
|
10
|
+
import { DatabaseService, QueueService, defineFunction } from '@dxos/functions';
|
|
11
|
+
import { DataType } from '@dxos/schema';
|
|
12
|
+
|
|
13
|
+
import { renderByline } from '../components';
|
|
14
|
+
import { Transcript } from '../types';
|
|
15
|
+
|
|
16
|
+
export default defineFunction({
|
|
17
|
+
key: 'dxos.org/function/transcription/open',
|
|
18
|
+
name: 'Open',
|
|
19
|
+
description: 'Opens and reads the contents of a transcription object.',
|
|
20
|
+
inputSchema: Schema.Struct({
|
|
21
|
+
id: ArtifactId.annotations({
|
|
22
|
+
description: 'The ID of the transcription object.',
|
|
23
|
+
}),
|
|
24
|
+
}),
|
|
25
|
+
outputSchema: Schema.Struct({
|
|
26
|
+
content: Schema.String,
|
|
27
|
+
}),
|
|
28
|
+
handler: Effect.fn(function* ({ data: { id } }) {
|
|
29
|
+
const transcript = yield* DatabaseService.resolve(ArtifactId.toDXN(id), Transcript.Transcript);
|
|
30
|
+
const { dxn } = yield* Effect.promise(() => transcript.queue.load());
|
|
31
|
+
const queue = yield* QueueService.getQueue(dxn);
|
|
32
|
+
yield* Effect.promise(() => queue?.queryObjects());
|
|
33
|
+
const content = queue?.objects
|
|
34
|
+
.filter((message) => Obj.instanceOf(DataType.Message, message))
|
|
35
|
+
.flatMap((message, index) => renderByline([])(message, index))
|
|
36
|
+
.join('\n\n');
|
|
37
|
+
return { content };
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Array from 'effect/Array';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Function from 'effect/Function';
|
|
8
|
+
import * as Layer from 'effect/Layer';
|
|
9
|
+
import * as Option from 'effect/Option';
|
|
10
|
+
import * as Schema from 'effect/Schema';
|
|
11
|
+
|
|
12
|
+
import { AiService, ConsolePrinter, ToolExecutionService, ToolResolverService } from '@dxos/ai';
|
|
13
|
+
import { AiSession, GenerationObserver } from '@dxos/assistant';
|
|
14
|
+
import { TracingService, defineFunction } from '@dxos/functions';
|
|
15
|
+
import { trim } from '@dxos/util';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Summarize a transcript of a meeting.
|
|
19
|
+
*/
|
|
20
|
+
export default defineFunction({
|
|
21
|
+
key: 'dxos.org/function/transcription/summarize',
|
|
22
|
+
name: 'Summarize',
|
|
23
|
+
description: 'Summarize a transcript of a meeting.',
|
|
24
|
+
inputSchema: Schema.Struct({
|
|
25
|
+
transcript: Schema.String.annotations({
|
|
26
|
+
description: 'The transcript of the meeting.',
|
|
27
|
+
}),
|
|
28
|
+
notes: Schema.optional(Schema.String).annotations({
|
|
29
|
+
description: 'Additional notes from the participants.',
|
|
30
|
+
}),
|
|
31
|
+
}),
|
|
32
|
+
outputSchema: Schema.Struct({
|
|
33
|
+
summary: Schema.String.annotations({
|
|
34
|
+
description: 'The summary of the transcript.',
|
|
35
|
+
}),
|
|
36
|
+
}),
|
|
37
|
+
handler: Effect.fnUntraced(
|
|
38
|
+
function* ({ data: { transcript, notes } }) {
|
|
39
|
+
const result = yield* new AiSession().run({
|
|
40
|
+
prompt: `Transcript: ${transcript}\n\nNotes: ${notes}`,
|
|
41
|
+
history: [],
|
|
42
|
+
system: systemPrompt,
|
|
43
|
+
observer: GenerationObserver.fromPrinter(new ConsolePrinter({ tag: 'summarize' })),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const summary = Function.pipe(
|
|
47
|
+
result,
|
|
48
|
+
Array.findLast((msg) => msg.sender.role === 'assistant' && msg.blocks.some((block) => block._tag === 'text')),
|
|
49
|
+
Option.flatMap((msg) =>
|
|
50
|
+
Function.pipe(
|
|
51
|
+
msg.blocks,
|
|
52
|
+
Array.findLast((block) => block._tag === 'text'),
|
|
53
|
+
Option.map((block) => block.text),
|
|
54
|
+
),
|
|
55
|
+
),
|
|
56
|
+
Option.getOrThrowWith(() => new Error('No summary found')),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return { summary };
|
|
60
|
+
},
|
|
61
|
+
Effect.provide(
|
|
62
|
+
Layer.mergeAll(
|
|
63
|
+
AiService.model('@anthropic/claude-sonnet-4-0'),
|
|
64
|
+
ToolResolverService.layerEmpty,
|
|
65
|
+
ToolExecutionService.layerEmpty,
|
|
66
|
+
TracingService.layerNoop,
|
|
67
|
+
),
|
|
68
|
+
),
|
|
69
|
+
),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const systemPrompt = trim`
|
|
73
|
+
You are a helpful assistant that summarizes transcripts of meetings.
|
|
74
|
+
|
|
75
|
+
# Goal
|
|
76
|
+
Create a markdown summary of the meeting transcript with text notes provided.
|
|
77
|
+
Notes are very important so make sure to include them in the summary if they contain meaningful information.
|
|
78
|
+
|
|
79
|
+
# Formatting
|
|
80
|
+
- Format the summary as a markdown document without extra comments like "Here is the summary of the transcript:".
|
|
81
|
+
- Use markdown formatting for headings and bullet points.
|
|
82
|
+
- Format the summary as a list of key points and takeaways.
|
|
83
|
+
- All names of people should be in bold.
|
|
84
|
+
|
|
85
|
+
# Note Taking
|
|
86
|
+
- Correlate items in the summary with the person of origin to build a coherent narrative.
|
|
87
|
+
- Include short quotes verbatim where appropriate. Especially when concerned with design decisions and problem descriptions.
|
|
88
|
+
|
|
89
|
+
# Tasks
|
|
90
|
+
At the end of the summary include tasks.
|
|
91
|
+
Extract only the tasks that are:
|
|
92
|
+
- Directly actionable.
|
|
93
|
+
- Clearly assigned to a person or team (or can easily be inferred).
|
|
94
|
+
- Strongly implied by the conversation and/or user note (no speculative tasks).
|
|
95
|
+
- Specific enough that someone reading them would know exactly what to do next.
|
|
96
|
+
|
|
97
|
+
Format all tasks as markdown checkboxes using the syntax:
|
|
98
|
+
- [ ] Task description.
|
|
99
|
+
|
|
100
|
+
Additional information can be included (indented).
|
|
101
|
+
|
|
102
|
+
If no actionable tasks are found, omit this tasks section.
|
|
103
|
+
`;
|
|
@@ -20,67 +20,63 @@ export const useAudioFile = (audioUrl: string, constraints?: MediaTrackConstrain
|
|
|
20
20
|
const ctx = new Context();
|
|
21
21
|
|
|
22
22
|
scheduleTask(ctx, async () => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
URL.
|
|
28
|
-
|
|
23
|
+
// TODO(wittjosiah): Fetch to external url fails in headless storybook test.
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(audioUrl);
|
|
26
|
+
const blob = await response.blob();
|
|
27
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
28
|
+
ctx.onDispose(() => {
|
|
29
|
+
URL.revokeObjectURL(objectUrl);
|
|
30
|
+
});
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
const audio = new Audio();
|
|
33
|
+
audio.src = objectUrl;
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
log.info('starting...');
|
|
46
|
-
try {
|
|
47
|
-
// Try to play the audio
|
|
48
|
-
await audio.play();
|
|
35
|
+
await new Promise<void>((resolve, reject) => {
|
|
36
|
+
audio.addEventListener(
|
|
37
|
+
'error',
|
|
38
|
+
(err) => {
|
|
39
|
+
log.error('error', { err });
|
|
40
|
+
reject(err);
|
|
41
|
+
},
|
|
42
|
+
{ once: true },
|
|
43
|
+
);
|
|
44
|
+
audio.addEventListener(
|
|
45
|
+
'canplay',
|
|
46
|
+
async () => {
|
|
49
47
|
resolve();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
},
|
|
56
|
-
{ once: true },
|
|
57
|
-
);
|
|
58
|
-
audio.load();
|
|
59
|
-
});
|
|
48
|
+
},
|
|
49
|
+
{ once: true },
|
|
50
|
+
);
|
|
51
|
+
audio.load();
|
|
52
|
+
});
|
|
60
53
|
|
|
61
|
-
|
|
54
|
+
const audioCtx = new AudioContext();
|
|
62
55
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
56
|
+
// Resume AudioContext if it's suspended.
|
|
57
|
+
if (audioCtx.state === 'suspended') {
|
|
58
|
+
await audioCtx.resume();
|
|
59
|
+
}
|
|
67
60
|
|
|
68
|
-
|
|
69
|
-
|
|
61
|
+
const destination = audioCtx.createMediaStreamDestination();
|
|
62
|
+
destination.channelCount = 1;
|
|
70
63
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
64
|
+
const source = audioCtx.createMediaElementSource(audio);
|
|
65
|
+
source.connect(destination);
|
|
66
|
+
const track = destination.stream.getAudioTracks()[0];
|
|
67
|
+
if (constraints) {
|
|
68
|
+
await track.applyConstraints(constraints).catch((err) => log.catch(err));
|
|
69
|
+
}
|
|
77
70
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
71
|
+
// Also connect to speakers so audio is audible.
|
|
72
|
+
setStream({
|
|
73
|
+
audio,
|
|
74
|
+
stream: destination.stream,
|
|
75
|
+
track: destination.stream.getAudioTracks()[0],
|
|
76
|
+
});
|
|
77
|
+
} catch (err) {
|
|
78
|
+
log.catch(err);
|
|
79
|
+
}
|
|
84
80
|
});
|
|
85
81
|
|
|
86
82
|
return () => {
|
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
6
6
|
|
|
7
7
|
import { scheduleMicroTask } from '@dxos/async';
|
|
8
8
|
import { Context } from '@dxos/context';
|
|
9
9
|
import { log } from '@dxos/log';
|
|
10
10
|
import { useSoundEffect } from '@dxos/react-ui-sfx';
|
|
11
11
|
|
|
12
|
+
import { type TranscriberParams } from '../transcriber';
|
|
13
|
+
|
|
12
14
|
import { useAudioTrack } from './useAudioTrack';
|
|
13
15
|
import { useTranscriber } from './useTranscriber';
|
|
14
|
-
import { type TranscriberParams } from '../transcriber';
|
|
15
16
|
|
|
16
17
|
export type UseVoiceInputProps = {
|
|
17
18
|
active?: boolean;
|
package/src/meta.ts
CHANGED
|
@@ -4,10 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { type PluginMeta } from '@dxos/app-framework';
|
|
6
6
|
|
|
7
|
-
export const TRANSCRIPTION_PLUGIN = 'dxos.org/plugin/transcription';
|
|
8
|
-
|
|
9
7
|
export const meta: PluginMeta = {
|
|
10
|
-
id:
|
|
8
|
+
id: 'dxos.org/plugin/transcription',
|
|
11
9
|
name: 'Transcription',
|
|
12
10
|
description: 'Voice transcription.',
|
|
13
11
|
icon: 'ph--microphone--regular',
|
package/src/model/model.test.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { Obj } from '@dxos/echo';
|
|
|
9
9
|
import type { ObjectId } from '@dxos/keys';
|
|
10
10
|
import { DataType } from '@dxos/schema';
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { type ChunkRenderer, DocumentAdapter, SerializationModel } from './model';
|
|
13
13
|
|
|
14
14
|
const blockToMarkdown: ChunkRenderer<DataType.Message> = (
|
|
15
15
|
message: DataType.Message,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { effect } from '@preact/signals-core';
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { DeferredTask, asyncTimeout } from '@dxos/async';
|
|
12
12
|
import { LifecycleState, Resource } from '@dxos/context';
|
|
13
13
|
import { type Queue } from '@dxos/echo-db';
|
|
14
14
|
import { type FunctionExecutor } from '@dxos/functions';
|
|
@@ -8,8 +8,8 @@ import { describe, test } from 'vitest';
|
|
|
8
8
|
import { scheduleTaskInterval } from '@dxos/async';
|
|
9
9
|
import { Context } from '@dxos/context';
|
|
10
10
|
import { Obj } from '@dxos/echo';
|
|
11
|
+
import { createQueueDXN } from '@dxos/echo/internal';
|
|
11
12
|
import { MemoryQueue } from '@dxos/echo-db';
|
|
12
|
-
import { createQueueDXN } from '@dxos/echo-schema';
|
|
13
13
|
import { FunctionExecutor, ServiceContainer } from '@dxos/functions';
|
|
14
14
|
import { log } from '@dxos/log';
|
|
15
15
|
import { DataType } from '@dxos/schema';
|
|
@@ -49,14 +49,13 @@ const messages: MessageWithRangeId[] = [
|
|
|
49
49
|
'in classical physics objects have well-defined properties such as position speed and momentum',
|
|
50
50
|
].map((string, index) =>
|
|
51
51
|
Obj.make(DataType.Message, {
|
|
52
|
-
created: new Date(Date.now() +
|
|
52
|
+
created: new Date(Date.now() + 1_000 * index).toISOString(),
|
|
53
53
|
sender,
|
|
54
|
-
blocks: [{ _tag: 'transcript', started: new Date(Date.now() +
|
|
54
|
+
blocks: [{ _tag: 'transcript', started: new Date(Date.now() + 1_000 * index).toISOString(), text: string }],
|
|
55
55
|
}),
|
|
56
56
|
);
|
|
57
57
|
|
|
58
|
-
//
|
|
59
|
-
const _REMOTE_AI = true;
|
|
58
|
+
// const REMOTE_AI = true;
|
|
60
59
|
|
|
61
60
|
describe.skip('SentenceNormalization', () => {
|
|
62
61
|
const getExecutor = () => {
|
|
@@ -98,6 +97,7 @@ describe.skip('SentenceNormalization', () => {
|
|
|
98
97
|
buffer = sentences.slice(activeSentenceIndex);
|
|
99
98
|
}
|
|
100
99
|
}
|
|
100
|
+
|
|
101
101
|
log.info('sentences', {
|
|
102
102
|
originalMessages: JSON.stringify(messages, null, 2),
|
|
103
103
|
sentences: JSON.stringify(sentences, null, 2),
|
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// ISSUE(burdon): defineFunction
|
|
6
6
|
// @ts-nocheck
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import * as Schema from 'effect/Schema';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { AiService, DEFAULT_EDGE_MODEL } from '@dxos/ai';
|
|
11
11
|
import { AiSession } from '@dxos/assistant';
|
|
12
12
|
import { Obj } from '@dxos/echo';
|
|
13
13
|
import { defineFunction } from '@dxos/functions';
|
|
14
14
|
import { ObjectId } from '@dxos/keys';
|
|
15
15
|
import { log } from '@dxos/log';
|
|
16
16
|
import { DataType } from '@dxos/schema';
|
|
17
|
+
import { trim } from '@dxos/util';
|
|
17
18
|
|
|
18
19
|
const MessageWithRangeId = Schema.extend(
|
|
19
20
|
DataType.Message,
|
|
@@ -42,40 +43,6 @@ export const NormalizationOutput = Schema.Struct({
|
|
|
42
43
|
});
|
|
43
44
|
export interface NormalizationOutput extends Schema.Schema.Type<typeof NormalizationOutput> {}
|
|
44
45
|
|
|
45
|
-
const prompt = `
|
|
46
|
-
You are observing a real-time transcript of a single person speaking.
|
|
47
|
-
The transcription is delivered in chunks of 10 seconds or less. As a result, individual sentences may be split across multiple messages, or multiple sentences may appear within a single message. Additionally, because this is real-time transcription, punctuation and capitalization may be incorrect or missing.
|
|
48
|
-
|
|
49
|
-
# Task Description:
|
|
50
|
-
- Your task is to detect and reconstruct broken or incomplete sentences by merging segments into coherent, grammatically correct sentences where appropriate.
|
|
51
|
-
|
|
52
|
-
# Input Format:
|
|
53
|
-
- The input is an array of messages that contain transcription blocks.
|
|
54
|
-
- Each message is a DataType.Message, as described in the output format.
|
|
55
|
-
|
|
56
|
-
# Output Format:
|
|
57
|
-
- You have been provided with the tool that defines the output format; make sure to query it.
|
|
58
|
-
- Do not output anything other than the expected format.
|
|
59
|
-
|
|
60
|
-
# Segment Handling Rules:
|
|
61
|
-
- Sort messages by timestamp.
|
|
62
|
-
- Leave ID of the first message in a sentence.
|
|
63
|
-
- Each divided sentence should be added to the output as a separate message or block within existing messages, preserving the original order.
|
|
64
|
-
- If one message contains multiple sentences, you should not split them.
|
|
65
|
-
- The last sentence may be incomplete; it should still be output as a separate block or message.
|
|
66
|
-
- Keep track of the original timestamps of the messages and blocks and, use the timestamp of the first message/block of a sentence as the 'started' field of the merged message.
|
|
67
|
-
- The 'rangeId' field of the merged message should include 'messageId'-s and 'rangeId'-s of all messages that have been used to construct this message.
|
|
68
|
-
|
|
69
|
-
# Punctuation and Capitalization:
|
|
70
|
-
- Do not rely on the original punctuation and capitalization—they may be incorrect.
|
|
71
|
-
- Use logical reasoning to apply appropriate punctuation (e.g., period, comma, question mark, exclamation mark) and capitalization.
|
|
72
|
-
|
|
73
|
-
# Restrictions:
|
|
74
|
-
- Do not alter the order of the original messages; maintain the natural flow of speech.
|
|
75
|
-
- Do not interpret or infer meaning beyond reconstructing sentences.
|
|
76
|
-
- Do not add or remove any words or phrases.
|
|
77
|
-
`;
|
|
78
|
-
|
|
79
46
|
export const sentenceNormalization = defineFunction<NormalizationInput, NormalizationOutput>({
|
|
80
47
|
description: 'Post process of transcription for sentence normalization',
|
|
81
48
|
inputSchema: NormalizationInput,
|
|
@@ -112,3 +79,37 @@ export const sentenceNormalization = defineFunction<NormalizationInput, Normaliz
|
|
|
112
79
|
return response;
|
|
113
80
|
},
|
|
114
81
|
});
|
|
82
|
+
|
|
83
|
+
const prompt = trim`
|
|
84
|
+
You are observing a real-time transcript of a single person speaking.
|
|
85
|
+
The transcription is delivered in chunks of 10 seconds or less. As a result, individual sentences may be split across multiple messages, or multiple sentences may appear within a single message. Additionally, because this is real-time transcription, punctuation and capitalization may be incorrect or missing.
|
|
86
|
+
|
|
87
|
+
# Task Description:
|
|
88
|
+
- Your task is to detect and reconstruct broken or incomplete sentences by merging segments into coherent, grammatically correct sentences where appropriate.
|
|
89
|
+
|
|
90
|
+
# Input Format:
|
|
91
|
+
- The input is an array of messages that contain transcription blocks.
|
|
92
|
+
- Each message is a DataType.Message, as described in the output format.
|
|
93
|
+
|
|
94
|
+
# Output Format:
|
|
95
|
+
- You have been provided with the tool that defines the output format; make sure to query it.
|
|
96
|
+
- Do not output anything other than the expected format.
|
|
97
|
+
|
|
98
|
+
# Segment Handling Rules:
|
|
99
|
+
- Sort messages by timestamp.
|
|
100
|
+
- Leave ID of the first message in a sentence.
|
|
101
|
+
- Each divided sentence should be added to the output as a separate message or block within existing messages, preserving the original order.
|
|
102
|
+
- If one message contains multiple sentences, you should not split them.
|
|
103
|
+
- The last sentence may be incomplete; it should still be output as a separate block or message.
|
|
104
|
+
- Keep track of the original timestamps of the messages and blocks and, use the timestamp of the first message/block of a sentence as the 'started' field of the merged message.
|
|
105
|
+
- The 'rangeId' field of the merged message should include 'messageId'-s and 'rangeId'-s of all messages that have been used to construct this message.
|
|
106
|
+
|
|
107
|
+
# Punctuation and Capitalization:
|
|
108
|
+
- Do not rely on the original punctuation and capitalization—they may be incorrect.
|
|
109
|
+
- Use logical reasoning to apply appropriate punctuation (e.g., period, comma, question mark, exclamation mark) and capitalization.
|
|
110
|
+
|
|
111
|
+
# Restrictions:
|
|
112
|
+
- Do not alter the order of the original messages; maintain the natural flow of speech.
|
|
113
|
+
- Do not interpret or infer meaning beyond reconstructing sentences.
|
|
114
|
+
- Do not add or remove any words or phrases.
|
|
115
|
+
`;
|
package/src/testing/testing.ts
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
6
|
import { useEffect, useMemo, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { extractionAnthropicFunction, processTranscriptMessage } from '@dxos/assistant/extraction';
|
|
9
9
|
import { scheduleTaskInterval } from '@dxos/async';
|
|
10
10
|
import { Filter, type Queue } from '@dxos/client/echo';
|
|
11
11
|
import { Context } from '@dxos/context';
|
|
12
|
-
import { Obj, Ref, Type
|
|
13
|
-
import { createQueueDXN } from '@dxos/echo
|
|
12
|
+
import { type Key, Obj, Ref, Type } from '@dxos/echo';
|
|
13
|
+
import { createQueueDXN } from '@dxos/echo/internal';
|
|
14
14
|
import { FunctionExecutor, ServiceContainer } from '@dxos/functions';
|
|
15
15
|
import { IdentityDid } from '@dxos/keys';
|
|
16
16
|
import { log } from '@dxos/log';
|
|
17
17
|
import { faker } from '@dxos/random';
|
|
18
|
-
import {
|
|
18
|
+
import { type Space, useQueue } from '@dxos/react-client/echo';
|
|
19
19
|
import { DataType } from '@dxos/schema';
|
|
20
20
|
import { Testing, seedTestData } from '@dxos/schema/testing';
|
|
21
21
|
|
|
@@ -74,7 +74,7 @@ export class MessageBuilder extends AbstractMessageBuilder {
|
|
|
74
74
|
const obj = this._space.db.add(Obj.make(TestItem, { title: label, description: faker.lorem.paragraph() }));
|
|
75
75
|
const dxn = Ref.make(obj).dxn.toString();
|
|
76
76
|
const words = text.split(' ');
|
|
77
|
-
words.splice(Math.floor(Math.random() * words.length), 0, `[${label}]
|
|
77
|
+
words.splice(Math.floor(Math.random() * words.length), 0, `[${label}](${dxn})`);
|
|
78
78
|
text = words.join(' ');
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -130,7 +130,7 @@ class EntityExtractionMessageBuilder extends AbstractMessageBuilder {
|
|
|
130
130
|
const { message: enhancedMessage } = await processTranscriptMessage({
|
|
131
131
|
input: { message },
|
|
132
132
|
executor: this.executor,
|
|
133
|
-
function:
|
|
133
|
+
function: extractionAnthropicFunction,
|
|
134
134
|
});
|
|
135
135
|
|
|
136
136
|
return enhancedMessage;
|
|
@@ -10,7 +10,7 @@ import { invariant } from '@dxos/invariant';
|
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
11
|
import { trace } from '@dxos/tracing';
|
|
12
12
|
|
|
13
|
-
import { type
|
|
13
|
+
import { type AudioChunk, type AudioRecorder, type WavConfig } from './audio-recorder';
|
|
14
14
|
|
|
15
15
|
let initializingPromise: Promise<void> | undefined;
|
|
16
16
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
+
|
|
7
8
|
import { describe, expect, test } from 'vitest';
|
|
8
9
|
import { WaveFile } from 'wavefile';
|
|
9
10
|
|
|
@@ -11,7 +12,7 @@ import { Trigger } from '@dxos/async';
|
|
|
11
12
|
import { log } from '@dxos/log';
|
|
12
13
|
import { type DataType } from '@dxos/schema';
|
|
13
14
|
import { openAndClose } from '@dxos/test-utils';
|
|
14
|
-
import {
|
|
15
|
+
import { TRACE_PROCESSOR, trace } from '@dxos/tracing';
|
|
15
16
|
|
|
16
17
|
import { type AudioChunk, type AudioRecorder, Transcriber } from '../transcriber';
|
|
17
18
|
import { mergeFloat64Arrays } from '../util';
|
|
@@ -10,10 +10,11 @@ import { log } from '@dxos/log';
|
|
|
10
10
|
import { type DataType } from '@dxos/schema';
|
|
11
11
|
import { trace } from '@dxos/tracing';
|
|
12
12
|
|
|
13
|
-
import { type AudioRecorder, type AudioChunk } from './audio-recorder';
|
|
14
13
|
import { TRANSCRIPTION_URL } from '../types';
|
|
15
14
|
import { mergeFloat64Arrays } from '../util';
|
|
16
15
|
|
|
16
|
+
import { type AudioChunk, type AudioRecorder } from './audio-recorder';
|
|
17
|
+
|
|
17
18
|
type WhisperWord = {
|
|
18
19
|
word: string;
|
|
19
20
|
|