@dxos/plugin-transcription 0.8.1 → 0.8.2-main.12df754
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-JQ3HFHLQ.mjs → TranscriptContainer-4D4BHE7G.mjs} +3 -2
- package/dist/lib/browser/{app-graph-builder-VDQOVX7P.mjs → app-graph-builder-I4SSIQMW.mjs} +18 -41
- package/dist/lib/browser/app-graph-builder-I4SSIQMW.mjs.map +7 -0
- package/dist/lib/browser/{chunk-YEPT334A.mjs → chunk-5BE6RTNC.mjs} +1 -1
- package/dist/lib/browser/chunk-5BE6RTNC.mjs.map +7 -0
- package/dist/lib/browser/{chunk-OGNXNGJB.mjs → chunk-5Q3R53U3.mjs} +8 -8
- package/dist/lib/browser/chunk-5Q3R53U3.mjs.map +7 -0
- package/dist/lib/browser/{chunk-YCZISX63.mjs → chunk-DR3NBV6Q.mjs} +6 -6
- package/dist/lib/browser/{chunk-YCZISX63.mjs.map → chunk-DR3NBV6Q.mjs.map} +3 -3
- package/dist/lib/browser/{chunk-K7FOYAXO.mjs → chunk-GI5LZ4SV.mjs} +2 -2
- package/dist/lib/browser/{chunk-GU2BNYKZ.mjs → chunk-KLD7O5GJ.mjs} +2 -12
- package/dist/lib/browser/chunk-KLD7O5GJ.mjs.map +7 -0
- package/dist/lib/browser/chunk-RHHJZQPM.mjs +702 -0
- package/dist/lib/browser/chunk-RHHJZQPM.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +22 -197
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/{intent-resolver-4R66ZCKT.mjs → intent-resolver-ZQQEH27F.mjs} +7 -7
- package/dist/lib/browser/intent-resolver-ZQQEH27F.mjs.map +7 -0
- package/dist/lib/browser/{meeting-transcription-state-DLFHQK72.mjs → meeting-transcription-state-5D52VTHU.mjs} +4 -4
- package/dist/lib/browser/meeting-transcription-state-5D52VTHU.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{react-surface-FQUHU5G7.mjs → react-surface-HETJ5XY6.mjs} +5 -4
- package/dist/lib/browser/{transcriber-LGMIWDYI.mjs → transcriber-SMRLU2KY.mjs} +8 -7
- package/dist/lib/browser/{transcriber-LGMIWDYI.mjs.map → transcriber-SMRLU2KY.mjs.map} +3 -3
- package/dist/lib/browser/types/index.mjs +1 -1
- package/dist/lib/node/{TranscriptContainer-QEYTTT2V.cjs → TranscriptContainer-ZWP6UKY6.cjs} +8 -7
- package/dist/lib/node/TranscriptContainer-ZWP6UKY6.cjs.map +7 -0
- package/dist/lib/node/{app-graph-builder-IFSIVNUR.cjs → app-graph-builder-WWC4WNTG.cjs} +31 -54
- package/dist/lib/node/app-graph-builder-WWC4WNTG.cjs.map +7 -0
- package/dist/lib/node/{chunk-P4VVD6ZO.cjs → chunk-3QEJ5EBO.cjs} +10 -10
- package/dist/lib/node/chunk-3QEJ5EBO.cjs.map +7 -0
- package/dist/lib/node/{chunk-CZMLKKWT.cjs → chunk-HYGDHS6X.cjs} +4 -4
- package/dist/lib/node/chunk-HYGDHS6X.cjs.map +7 -0
- package/dist/lib/node/{chunk-3KHUSQNW.cjs → chunk-J72PUQ3C.cjs} +6 -15
- package/dist/lib/node/chunk-J72PUQ3C.cjs.map +7 -0
- package/dist/lib/node/chunk-KO6MKCED.cjs +718 -0
- package/dist/lib/node/chunk-KO6MKCED.cjs.map +7 -0
- package/dist/lib/node/{chunk-FCCJALOT.cjs → chunk-LA73IHCB.cjs} +10 -10
- package/dist/lib/node/{chunk-FCCJALOT.cjs.map → chunk-LA73IHCB.cjs.map} +3 -3
- package/dist/lib/node/{chunk-LP4FG423.cjs → chunk-WXG4Z2EZ.cjs} +5 -5
- package/dist/lib/node/index.cjs +49 -233
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/{intent-resolver-WMMCOUEH.cjs → intent-resolver-VY3XWYAA.cjs} +11 -10
- package/dist/lib/node/intent-resolver-VY3XWYAA.cjs.map +7 -0
- package/dist/lib/node/{meeting-transcription-state-URIYILEQ.cjs → meeting-transcription-state-OYWCY5XE.cjs} +7 -7
- package/dist/lib/node/meeting-transcription-state-OYWCY5XE.cjs.map +7 -0
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/{react-surface-WI2MGWCC.cjs → react-surface-M2JQIIDY.cjs} +10 -9
- package/dist/lib/node/{react-surface-WI2MGWCC.cjs.map → react-surface-M2JQIIDY.cjs.map} +1 -1
- package/dist/lib/node/{transcriber-QG5WPKX4.cjs → transcriber-E6AAB4SU.cjs} +14 -13
- package/dist/lib/node/{transcriber-QG5WPKX4.cjs.map → transcriber-E6AAB4SU.cjs.map} +3 -3
- package/dist/lib/node/types/index.cjs +7 -7
- package/dist/lib/node/types/index.cjs.map +1 -1
- package/dist/lib/node-esm/{TranscriptContainer-ZXUCOWGY.mjs → TranscriptContainer-3HJ752CP.mjs} +3 -2
- package/dist/lib/node-esm/{app-graph-builder-UL6XLIZY.mjs → app-graph-builder-PPJQAFSI.mjs} +18 -41
- package/dist/lib/node-esm/app-graph-builder-PPJQAFSI.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-GIO6J6FE.mjs → chunk-54HDN33F.mjs} +8 -8
- package/dist/lib/node-esm/chunk-54HDN33F.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-YQYBLFNI.mjs → chunk-K3R3YK3S.mjs} +6 -6
- package/dist/lib/node-esm/{chunk-YQYBLFNI.mjs.map → chunk-K3R3YK3S.mjs.map} +3 -3
- package/dist/lib/node-esm/{chunk-HKATMD34.mjs → chunk-QMHMEKB3.mjs} +2 -12
- package/dist/lib/node-esm/chunk-QMHMEKB3.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-SDMGJZLN.mjs +703 -0
- package/dist/lib/node-esm/chunk-SDMGJZLN.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-J4D2RBBH.mjs → chunk-XY22OP6H.mjs} +2 -2
- package/dist/lib/node-esm/{chunk-PTZHDZ36.mjs → chunk-YXZOLOFT.mjs} +1 -1
- package/dist/lib/node-esm/chunk-YXZOLOFT.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +22 -197
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/{intent-resolver-QN2LO5TX.mjs → intent-resolver-FCTNJPWY.mjs} +7 -7
- package/dist/lib/node-esm/intent-resolver-FCTNJPWY.mjs.map +7 -0
- package/dist/lib/node-esm/{meeting-transcription-state-ZYVRAW6Y.mjs → meeting-transcription-state-XMZJ45P4.mjs} +4 -4
- package/dist/lib/node-esm/meeting-transcription-state-XMZJ45P4.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/{react-surface-PCH6NTVB.mjs → react-surface-PXFSIM6V.mjs} +5 -4
- package/dist/lib/{browser/react-surface-FQUHU5G7.mjs.map → node-esm/react-surface-PXFSIM6V.mjs.map} +1 -1
- package/dist/lib/node-esm/{transcriber-TMPYZ5L4.mjs → transcriber-REAGRCPY.mjs} +8 -7
- package/dist/lib/node-esm/{transcriber-TMPYZ5L4.mjs.map → transcriber-REAGRCPY.mjs.map} +3 -3
- package/dist/lib/node-esm/types/index.mjs +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/capabilities.d.ts +4 -6
- package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +1 -4
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/meeting-transcription-state.d.ts +2 -4
- package/dist/types/src/capabilities/meeting-transcription-state.d.ts.map +1 -1
- package/dist/types/src/capabilities/transcriber.d.ts.map +1 -1
- package/dist/types/src/components/Transcript/Transcript.d.ts +12 -4
- package/dist/types/src/components/Transcript/Transcript.d.ts.map +1 -1
- package/dist/types/src/components/Transcript/Transcript.stories.d.ts +15 -4
- package/dist/types/src/components/Transcript/Transcript.stories.d.ts.map +1 -1
- package/dist/types/src/components/Transcript/deprecated/Transcript.d.ts +13 -0
- package/dist/types/src/components/Transcript/deprecated/Transcript.d.ts.map +1 -0
- package/dist/types/src/components/Transcript/testings.d.ts +51 -0
- package/dist/types/src/components/Transcript/testings.d.ts.map +1 -0
- package/dist/types/src/components/Transcript/transcript-extension.d.ts +19 -0
- package/dist/types/src/components/Transcript/transcript-extension.d.ts.map +1 -0
- package/dist/types/src/components/TranscriptContainer.d.ts.map +1 -1
- package/dist/types/src/components/transcription.stories.d.ts.map +1 -1
- package/dist/types/src/entity-extraction/entity-extraction.d.ts +16 -0
- package/dist/types/src/entity-extraction/entity-extraction.d.ts.map +1 -0
- package/dist/types/src/entity-extraction/entity-extraction.test.d.ts +2 -0
- package/dist/types/src/entity-extraction/entity-extraction.test.d.ts.map +1 -0
- package/dist/types/src/entity-extraction/index.d.ts +2 -0
- package/dist/types/src/entity-extraction/index.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useQueueModelAdapter.d.ts +7 -0
- package/dist/types/src/hooks/useQueueModelAdapter.d.ts.map +1 -0
- package/dist/types/src/hooks/useTranscriber.d.ts +1 -1
- package/dist/types/src/hooks/useTranscriber.d.ts.map +1 -1
- package/dist/types/src/model/index.d.ts +2 -0
- package/dist/types/src/model/index.d.ts.map +1 -0
- package/dist/types/src/model/model.d.ts +68 -0
- package/dist/types/src/model/model.d.ts.map +1 -0
- package/dist/types/src/model/model.test.d.ts +2 -0
- package/dist/types/src/model/model.test.d.ts.map +1 -0
- package/dist/types/src/testing/test-data.d.ts +73 -0
- package/dist/types/src/testing/test-data.d.ts.map +1 -0
- package/dist/types/src/transcriber/transcriber.browser.test.d.ts +2 -0
- package/dist/types/src/transcriber/transcriber.browser.test.d.ts.map +1 -0
- package/dist/types/src/types/schema.d.ts +9 -9
- package/dist/types/src/util/index.d.ts +0 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/package.json +47 -39
- package/src/capabilities/app-graph-builder.ts +15 -26
- package/src/capabilities/capabilities.ts +3 -2
- package/src/capabilities/intent-resolver.ts +4 -4
- package/src/capabilities/meeting-transcription-state.ts +2 -2
- package/src/capabilities/transcriber.ts +2 -1
- package/src/components/Transcript/Transcript.stories.tsx +165 -43
- package/src/components/Transcript/Transcript.tsx +78 -210
- package/src/components/Transcript/deprecated/Transcript.tsx +231 -0
- package/src/components/Transcript/testings.ts +182 -0
- package/src/components/Transcript/transcript-extension.ts +233 -0
- package/src/components/TranscriptContainer.tsx +6 -3
- package/src/components/transcription.stories.tsx +97 -48
- package/src/entity-extraction/entity-extraction.test.ts +32 -0
- package/src/entity-extraction/entity-extraction.ts +94 -0
- package/src/entity-extraction/index.ts +5 -0
- package/src/entity-extraction/system-prompt.tpl +9 -0
- package/src/entity-extraction/types.d.ts +8 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useQueueModelAdapter.ts +30 -0
- package/src/hooks/useTranscriber.ts +3 -2
- package/src/model/index.ts +5 -0
- package/src/model/model.test.ts +123 -0
- package/src/model/model.ts +229 -0
- package/src/testing/test-data.ts +314 -0
- package/src/transcriber/transcriber.ts +1 -1
- package/src/transcriber/transcription-manager.ts +2 -2
- package/src/types/schema.ts +7 -7
- package/src/types/types.ts +2 -2
- package/src/util/index.ts +0 -1
- package/dist/lib/browser/app-graph-builder-VDQOVX7P.mjs.map +0 -7
- package/dist/lib/browser/chunk-G4B6CPC2.mjs +0 -253
- package/dist/lib/browser/chunk-G4B6CPC2.mjs.map +0 -7
- package/dist/lib/browser/chunk-GU2BNYKZ.mjs.map +0 -7
- package/dist/lib/browser/chunk-OGNXNGJB.mjs.map +0 -7
- package/dist/lib/browser/chunk-YEPT334A.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-4R66ZCKT.mjs.map +0 -7
- package/dist/lib/browser/meeting-transcription-state-DLFHQK72.mjs.map +0 -7
- package/dist/lib/node/TranscriptContainer-QEYTTT2V.cjs.map +0 -7
- package/dist/lib/node/app-graph-builder-IFSIVNUR.cjs.map +0 -7
- package/dist/lib/node/chunk-3KHUSQNW.cjs.map +0 -7
- package/dist/lib/node/chunk-CZMLKKWT.cjs.map +0 -7
- package/dist/lib/node/chunk-P4VVD6ZO.cjs.map +0 -7
- package/dist/lib/node/chunk-UOFHT4GZ.cjs +0 -280
- package/dist/lib/node/chunk-UOFHT4GZ.cjs.map +0 -7
- package/dist/lib/node/intent-resolver-WMMCOUEH.cjs.map +0 -7
- package/dist/lib/node/meeting-transcription-state-URIYILEQ.cjs.map +0 -7
- package/dist/lib/node-esm/app-graph-builder-UL6XLIZY.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-6X7X2UZF.mjs +0 -254
- package/dist/lib/node-esm/chunk-6X7X2UZF.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-GIO6J6FE.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HKATMD34.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-PTZHDZ36.mjs.map +0 -7
- package/dist/lib/node-esm/intent-resolver-QN2LO5TX.mjs.map +0 -7
- package/dist/lib/node-esm/meeting-transcription-state-ZYVRAW6Y.mjs.map +0 -7
- package/dist/types/src/transcriber/transcriber.test.d.ts +0 -2
- package/dist/types/src/transcriber/transcriber.test.d.ts.map +0 -1
- package/dist/types/src/util/random-queue-dxn.d.ts +0 -3
- package/dist/types/src/util/random-queue-dxn.d.ts.map +0 -1
- package/src/util/random-queue-dxn.ts +0 -9
- /package/dist/lib/browser/{TranscriptContainer-JQ3HFHLQ.mjs.map → TranscriptContainer-4D4BHE7G.mjs.map} +0 -0
- /package/dist/lib/browser/{chunk-K7FOYAXO.mjs.map → chunk-GI5LZ4SV.mjs.map} +0 -0
- /package/dist/lib/{node-esm/react-surface-PCH6NTVB.mjs.map → browser/react-surface-HETJ5XY6.mjs.map} +0 -0
- /package/dist/lib/node/{chunk-LP4FG423.cjs.map → chunk-WXG4Z2EZ.cjs.map} +0 -0
- /package/dist/lib/node-esm/{TranscriptContainer-ZXUCOWGY.mjs.map → TranscriptContainer-3HJ752CP.mjs.map} +0 -0
- /package/dist/lib/node-esm/{chunk-J4D2RBBH.mjs.map → chunk-XY22OP6H.mjs.map} +0 -0
- /package/src/transcriber/{transcriber.test.ts → transcriber.browser.test.ts} +0 -0
|
@@ -16,84 +16,107 @@ import React, {
|
|
|
16
16
|
useState,
|
|
17
17
|
} from 'react';
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { Events, IntentPlugin, SettingsPlugin } from '@dxos/app-framework';
|
|
20
|
+
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
21
|
+
import { MemoryQueue } from '@dxos/echo-db';
|
|
22
|
+
import { create } from '@dxos/echo-schema';
|
|
20
23
|
import { type DXN } from '@dxos/keys';
|
|
21
24
|
import { log } from '@dxos/log';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
+
import { ClientPlugin } from '@dxos/plugin-client';
|
|
26
|
+
import { SpacePlugin } from '@dxos/plugin-space';
|
|
27
|
+
import { ThemePlugin } from '@dxos/plugin-theme';
|
|
28
|
+
import { randomQueueDxn, useQueue } from '@dxos/react-client/echo';
|
|
25
29
|
import { IconButton, Toolbar } from '@dxos/react-ui';
|
|
26
30
|
import { ScrollContainer } from '@dxos/react-ui-components';
|
|
31
|
+
import { defaultTx } from '@dxos/react-ui-theme';
|
|
27
32
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
28
33
|
|
|
29
|
-
import { Transcript } from './Transcript';
|
|
30
|
-
import {
|
|
34
|
+
import { renderMarkdown, Transcript } from './Transcript';
|
|
35
|
+
import { TestItem } from './Transcript/testings';
|
|
36
|
+
import { TranscriptionPlugin } from '../TranscriptionPlugin';
|
|
37
|
+
import { useAudioFile, useAudioTrack, useQueueModelAdapter, useTranscriber } from '../hooks';
|
|
38
|
+
import { type BlockModel } from '../model';
|
|
31
39
|
import { type TranscriberParams } from '../transcriber';
|
|
32
40
|
import { TranscriptBlock } from '../types';
|
|
33
|
-
import { randomQueueDxn } from '../util';
|
|
34
41
|
|
|
35
42
|
const TranscriptionStory: FC<{
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}> = ({
|
|
43
|
+
model: BlockModel<TranscriptBlock>;
|
|
44
|
+
running: boolean;
|
|
45
|
+
onRunningChange: Dispatch<SetStateAction<boolean>>;
|
|
46
|
+
}> = ({ model, running, onRunningChange }) => {
|
|
40
47
|
return (
|
|
41
48
|
<div className='flex flex-col w-[30rem]'>
|
|
42
49
|
<Toolbar.Root>
|
|
43
50
|
<IconButton
|
|
44
51
|
iconOnly
|
|
45
|
-
icon={
|
|
46
|
-
label={
|
|
47
|
-
onClick={() =>
|
|
52
|
+
icon={running ? 'ph--pause--regular' : 'ph--play--regular'}
|
|
53
|
+
label={running ? 'Pause' : 'Play'}
|
|
54
|
+
onClick={() => onRunningChange((running) => !running)}
|
|
48
55
|
/>
|
|
49
56
|
</Toolbar.Root>
|
|
50
57
|
<ScrollContainer>
|
|
51
|
-
<Transcript
|
|
58
|
+
<Transcript model={model} attendableId='story' />
|
|
52
59
|
</ScrollContainer>
|
|
53
60
|
</div>
|
|
54
61
|
);
|
|
55
62
|
};
|
|
56
63
|
|
|
57
64
|
const Microphone = () => {
|
|
58
|
-
const [
|
|
65
|
+
const [running, setRunning] = useState(false);
|
|
59
66
|
|
|
60
67
|
// Audio.
|
|
61
|
-
const track = useAudioTrack(
|
|
68
|
+
const track = useAudioTrack(running);
|
|
62
69
|
|
|
63
70
|
// Queue.
|
|
64
71
|
const queueDxn = useMemo(() => randomQueueDxn(), []);
|
|
65
|
-
const queue =
|
|
72
|
+
const queue = useMemo(() => new MemoryQueue<TranscriptBlock>(queueDxn), [queueDxn]);
|
|
73
|
+
const model = useQueueModelAdapter(renderMarkdown, queue);
|
|
66
74
|
|
|
67
75
|
// Transcriber.
|
|
68
76
|
const handleSegments = useCallback<TranscriberParams['onSegments']>(
|
|
69
77
|
async (segments) => {
|
|
70
|
-
const block =
|
|
71
|
-
queue
|
|
78
|
+
const block = create(TranscriptBlock, { segments });
|
|
79
|
+
void queue.append([block]);
|
|
72
80
|
},
|
|
73
81
|
[queue],
|
|
74
82
|
);
|
|
75
|
-
|
|
83
|
+
|
|
84
|
+
const config = useRef<TranscriberParams['config']>({
|
|
85
|
+
transcribeAfterChunksAmount: 20,
|
|
86
|
+
prefixBufferChunksAmount: 5,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const transcriber = useTranscriber({
|
|
90
|
+
audioStreamTrack: track,
|
|
91
|
+
onSegments: handleSegments,
|
|
92
|
+
config: config.current,
|
|
93
|
+
});
|
|
94
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
76
95
|
useEffect(() => {
|
|
77
|
-
void transcriber?.open();
|
|
96
|
+
void transcriber?.open().then(() => setIsOpen(true));
|
|
97
|
+
log.info('transcriber open');
|
|
78
98
|
return () => {
|
|
79
|
-
void transcriber?.close();
|
|
99
|
+
void transcriber?.close().then(() => setIsOpen(false));
|
|
100
|
+
log.info('transcriber close');
|
|
80
101
|
};
|
|
81
102
|
}, [transcriber]);
|
|
82
103
|
|
|
83
104
|
// Manage transcription state.
|
|
84
105
|
useEffect(() => {
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
106
|
+
if (running && transcriber?.isOpen) {
|
|
107
|
+
transcriber?.startChunksRecording();
|
|
108
|
+
log.info('transcriber startChunksRecording');
|
|
109
|
+
} else if (!running) {
|
|
110
|
+
transcriber?.stopChunksRecording();
|
|
111
|
+
log.info('transcriber stopChunksRecording');
|
|
89
112
|
}
|
|
90
|
-
}, [transcriber,
|
|
113
|
+
}, [transcriber, running, isOpen]);
|
|
91
114
|
|
|
92
|
-
return <TranscriptionStory
|
|
115
|
+
return <TranscriptionStory model={model} running={running} onRunningChange={setRunning} />;
|
|
93
116
|
};
|
|
94
117
|
|
|
95
118
|
const AudioFile = ({ queueDxn, audioUrl }: { queueDxn: DXN; audioUrl: string; transcriptUrl: string }) => {
|
|
96
|
-
const [
|
|
119
|
+
const [running, setRunning] = useState(false);
|
|
97
120
|
|
|
98
121
|
// Audio.
|
|
99
122
|
const audioElement = useRef<HTMLAudioElement>(null);
|
|
@@ -110,18 +133,19 @@ const AudioFile = ({ queueDxn, audioUrl }: { queueDxn: DXN; audioUrl: string; tr
|
|
|
110
133
|
return;
|
|
111
134
|
}
|
|
112
135
|
|
|
113
|
-
if (
|
|
136
|
+
if (running) {
|
|
114
137
|
void audio.play();
|
|
115
138
|
} else {
|
|
116
139
|
void audio.pause();
|
|
117
140
|
}
|
|
118
|
-
}, [audio,
|
|
141
|
+
}, [audio, running]);
|
|
119
142
|
|
|
120
143
|
// Transcriber.
|
|
121
144
|
const queue = useQueue<TranscriptBlock>(queueDxn, { pollInterval: 500 });
|
|
145
|
+
const model = useQueueModelAdapter(renderMarkdown, queue);
|
|
122
146
|
const handleSegments = useCallback<TranscriberParams['onSegments']>(
|
|
123
147
|
async (segments) => {
|
|
124
|
-
const block =
|
|
148
|
+
const block = create(TranscriptBlock, { authorName: 'test', authorHue: 'cyan', segments });
|
|
125
149
|
queue?.append([block]);
|
|
126
150
|
},
|
|
127
151
|
[queue],
|
|
@@ -133,15 +157,15 @@ const AudioFile = ({ queueDxn, audioUrl }: { queueDxn: DXN; audioUrl: string; tr
|
|
|
133
157
|
});
|
|
134
158
|
|
|
135
159
|
useEffect(() => {
|
|
136
|
-
if (transcriber &&
|
|
137
|
-
void transcriber.open();
|
|
138
|
-
} else if (!
|
|
160
|
+
if (transcriber && running) {
|
|
161
|
+
void transcriber.open().then(manageChunkRecording);
|
|
162
|
+
} else if (!running) {
|
|
139
163
|
transcriber?.stopChunksRecording();
|
|
140
164
|
void transcriber?.close();
|
|
141
165
|
}
|
|
142
|
-
}, [transcriber,
|
|
166
|
+
}, [transcriber, running]);
|
|
143
167
|
|
|
144
|
-
|
|
168
|
+
const manageChunkRecording = () => {
|
|
145
169
|
if (track?.readyState === 'live' && transcriber?.isOpen) {
|
|
146
170
|
log.info('starting transcription');
|
|
147
171
|
transcriber.startChunksRecording();
|
|
@@ -149,24 +173,49 @@ const AudioFile = ({ queueDxn, audioUrl }: { queueDxn: DXN; audioUrl: string; tr
|
|
|
149
173
|
log.info('stopping transcription');
|
|
150
174
|
transcriber.stopChunksRecording();
|
|
151
175
|
}
|
|
176
|
+
};
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
manageChunkRecording();
|
|
152
179
|
}, [transcriber, track?.readyState, transcriber?.isOpen]);
|
|
153
180
|
|
|
154
|
-
return <TranscriptionStory
|
|
181
|
+
return <TranscriptionStory model={model} running={running} onRunningChange={setRunning} />;
|
|
155
182
|
};
|
|
156
183
|
|
|
157
184
|
const meta: Meta<typeof AudioFile> = {
|
|
158
185
|
title: 'plugins/plugin-transcription/transcription',
|
|
159
186
|
decorators: [
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
187
|
+
withPluginManager({
|
|
188
|
+
plugins: [
|
|
189
|
+
ThemePlugin({ tx: defaultTx }),
|
|
190
|
+
ClientPlugin({
|
|
191
|
+
types: [TestItem],
|
|
192
|
+
onClientInitialized: async (_, client) => {
|
|
193
|
+
await client.halo.createIdentity();
|
|
167
194
|
},
|
|
168
|
-
},
|
|
169
|
-
|
|
195
|
+
}),
|
|
196
|
+
SpacePlugin(),
|
|
197
|
+
SettingsPlugin(),
|
|
198
|
+
IntentPlugin(),
|
|
199
|
+
TranscriptionPlugin(),
|
|
200
|
+
],
|
|
201
|
+
fireEvents: [Events.SetupAppGraph],
|
|
202
|
+
// capabilities: [
|
|
203
|
+
// contributes(
|
|
204
|
+
// Capabilities.ReactSurface,
|
|
205
|
+
// createSurface({
|
|
206
|
+
// id: 'preview-test',
|
|
207
|
+
// role: 'preview',
|
|
208
|
+
// component: ({ data }) => {
|
|
209
|
+
// const schema = getSchema(data);
|
|
210
|
+
// if (!schema) {
|
|
211
|
+
// return null;
|
|
212
|
+
// }
|
|
213
|
+
|
|
214
|
+
// return <Form schema={schema} values={data} />;
|
|
215
|
+
// },
|
|
216
|
+
// }),
|
|
217
|
+
// ),
|
|
218
|
+
// ],
|
|
170
219
|
}),
|
|
171
220
|
withTheme,
|
|
172
221
|
withLayout({
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { AIServiceEdgeClient } from '@dxos/assistant';
|
|
8
|
+
import { AI_SERVICE_ENDPOINT } from '@dxos/assistant/testing';
|
|
9
|
+
import { log } from '@dxos/log';
|
|
10
|
+
|
|
11
|
+
import { processTranscriptBlock } from './entity-extraction';
|
|
12
|
+
import * as TestData from '../testing/test-data';
|
|
13
|
+
|
|
14
|
+
const aiService = new AIServiceEdgeClient({
|
|
15
|
+
endpoint: AI_SERVICE_ENDPOINT.REMOTE,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe.skip('EntityExtraction', { timeout: 180_000 }, () => {
|
|
19
|
+
test('should process a transcript block', async () => {
|
|
20
|
+
for (const block of TestData.transcriptBlocks) {
|
|
21
|
+
log.info('input', block);
|
|
22
|
+
const { block: enhancedBlock } = await processTranscriptBlock({
|
|
23
|
+
block,
|
|
24
|
+
aiService,
|
|
25
|
+
context: {
|
|
26
|
+
objects: [...TestData.documents, ...Object.values(TestData.contacts)],
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
log.info('output', enhancedBlock);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Schema } from 'effect';
|
|
6
|
+
|
|
7
|
+
import { createTemplate, Message, structuredOutputParser } from '@dxos/artifact';
|
|
8
|
+
import type { AIServiceClient } from '@dxos/assistant';
|
|
9
|
+
import { MixedStreamParser } from '@dxos/assistant';
|
|
10
|
+
import { raise } from '@dxos/debug';
|
|
11
|
+
import { create } from '@dxos/echo-schema';
|
|
12
|
+
import type { BaseEchoObject } from '@dxos/echo-schema';
|
|
13
|
+
|
|
14
|
+
import SYSTEM_PROMPT from './system-prompt.tpl?raw';
|
|
15
|
+
import type { TranscriptBlock } from '../types';
|
|
16
|
+
|
|
17
|
+
type ProcessTranscriptBlockParams = {
|
|
18
|
+
block: TranscriptBlock;
|
|
19
|
+
aiService: AIServiceClient;
|
|
20
|
+
context: {
|
|
21
|
+
objects?: BaseEchoObject[];
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type ProcessTranscriptBlockResult = {
|
|
26
|
+
block: TranscriptBlock;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Process Handlebars template.
|
|
31
|
+
*/
|
|
32
|
+
const createSystemPrompt = (): string => {
|
|
33
|
+
const template = createTemplate(SYSTEM_PROMPT);
|
|
34
|
+
return template({});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const processTranscriptBlock = async (
|
|
38
|
+
params: ProcessTranscriptBlockParams,
|
|
39
|
+
): Promise<ProcessTranscriptBlockResult> => {
|
|
40
|
+
// TODO(dmaretskyi): Move context to a vector search index.
|
|
41
|
+
const systemPrompt = `
|
|
42
|
+
${createSystemPrompt()}
|
|
43
|
+
Context:
|
|
44
|
+
${JSON.stringify(params.context.objects)}
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const outputParser = structuredOutputParser(
|
|
48
|
+
Schema.Struct({
|
|
49
|
+
segments: Schema.Array(Schema.String).annotations({
|
|
50
|
+
description: 'The enhanced text of the transcript segments, keep the order and structure exactly as is',
|
|
51
|
+
}),
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
const result = outputParser.getResult(
|
|
55
|
+
await new MixedStreamParser().parse(
|
|
56
|
+
await params.aiService.execStream({
|
|
57
|
+
model: '@anthropic/claude-3-5-haiku-20241022',
|
|
58
|
+
systemPrompt,
|
|
59
|
+
history: [
|
|
60
|
+
create(Message, {
|
|
61
|
+
role: 'user',
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: 'text',
|
|
65
|
+
text: JSON.stringify(params.block),
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
}),
|
|
69
|
+
],
|
|
70
|
+
tools: [outputParser.tool],
|
|
71
|
+
}),
|
|
72
|
+
),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
block: {
|
|
77
|
+
...params.block,
|
|
78
|
+
segments: params.block.segments.map((segment, i) => ({
|
|
79
|
+
...segment,
|
|
80
|
+
text: postprocessText(result?.segments[i] ?? raise(new Error('failed to process email'))),
|
|
81
|
+
})),
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Finds and replaces all inline references with DXNs references.
|
|
88
|
+
*/
|
|
89
|
+
// TODO(dmaretskyi): Lookup and verifiy ids from provided context.
|
|
90
|
+
const postprocessText = (text: string) => {
|
|
91
|
+
return text.replace(/\n/g, ' ').replace(/\[([^\]]+)\]\[([A-Z0-9]+)\]/g, (match, name, id) => {
|
|
92
|
+
return `[${name}][dxn:echo:@:${id}]`;
|
|
93
|
+
});
|
|
94
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
You are a helpful assistant that processes transcripts.
|
|
2
|
+
Your goal is to enhance the transcript with entity references.
|
|
3
|
+
Insert inline references to the entities in the transcript.
|
|
4
|
+
Keep the transcript structure and text as is.
|
|
5
|
+
Call the submit_result tool to submit your result.
|
|
6
|
+
|
|
7
|
+
The inline reference syntax is as follows:
|
|
8
|
+
- [<optional name>][<ID>] or just [<ID>]
|
|
9
|
+
- Example: [Earnings Report][01JT0JP9AX0XKGZX4MV4B69VT6]
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useMemo, useEffect } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type Queue } from '@dxos/react-client/echo';
|
|
8
|
+
|
|
9
|
+
import { type Block, BlockModel, type BlockRenderer } from '../model';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Model adapter for a queue.
|
|
13
|
+
*/
|
|
14
|
+
export const useQueueModelAdapter = <T extends Block>(
|
|
15
|
+
renderer: BlockRenderer<T>,
|
|
16
|
+
queue: Queue<T> | undefined,
|
|
17
|
+
initialBlocks: T[] = [],
|
|
18
|
+
): BlockModel<T> => {
|
|
19
|
+
const model = useMemo(() => new BlockModel<T>(renderer, initialBlocks), [queue]);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!queue?.items.length) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const block = queue.items[queue.items.length - 1];
|
|
26
|
+
model.appendBlock(block);
|
|
27
|
+
}, [model, queue?.items.length]);
|
|
28
|
+
|
|
29
|
+
return model;
|
|
30
|
+
};
|
|
@@ -15,6 +15,7 @@ import { type Transcriber } from '../transcriber';
|
|
|
15
15
|
export const useTranscriber = ({
|
|
16
16
|
audioStreamTrack,
|
|
17
17
|
onSegments,
|
|
18
|
+
config,
|
|
18
19
|
}: Partial<TranscriptionCapabilities.GetTranscriberProps>) => {
|
|
19
20
|
const [getTranscriber] = useCapabilities(TranscriptionCapabilities.Transcriber);
|
|
20
21
|
|
|
@@ -24,8 +25,8 @@ export const useTranscriber = ({
|
|
|
24
25
|
return undefined;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
return getTranscriber({ audioStreamTrack, onSegments });
|
|
28
|
-
}, [audioStreamTrack, onSegments, getTranscriber]);
|
|
28
|
+
return getTranscriber({ audioStreamTrack, onSegments, config });
|
|
29
|
+
}, [audioStreamTrack, onSegments, getTranscriber, config]);
|
|
29
30
|
|
|
30
31
|
useEffect(() => {
|
|
31
32
|
return () => {
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { EditorView } from '@codemirror/view';
|
|
6
|
+
import { describe, test } from 'vitest';
|
|
7
|
+
|
|
8
|
+
import { BlockModel, DocumentAdapter, type BlockRenderer } from './model';
|
|
9
|
+
import { type TranscriptBlock } from '../types';
|
|
10
|
+
|
|
11
|
+
const blockToMarkdown: BlockRenderer<TranscriptBlock> = (block: TranscriptBlock, debug = true): string[] => {
|
|
12
|
+
return [`###### ${block.authorName}`, ...block.segments.map((segment) => segment.text), ''];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const createDate = () => new Date().toISOString();
|
|
16
|
+
|
|
17
|
+
describe('BlockModel', () => {
|
|
18
|
+
test('basic', ({ expect }) => {
|
|
19
|
+
const model = new BlockModel<TranscriptBlock>(blockToMarkdown);
|
|
20
|
+
expect(model.blocks.length).to.eq(0);
|
|
21
|
+
expect(model.doc.toString()).to.eq('');
|
|
22
|
+
|
|
23
|
+
// Create block.
|
|
24
|
+
const block = {
|
|
25
|
+
id: '1',
|
|
26
|
+
authorName: 'Alice',
|
|
27
|
+
segments: [{ started: createDate(), text: 'Hello world!' }],
|
|
28
|
+
};
|
|
29
|
+
model.appendBlock(block);
|
|
30
|
+
{
|
|
31
|
+
const text = model.doc.toString();
|
|
32
|
+
expect(text).to.eq('###### Alice\nHello world!\n\n');
|
|
33
|
+
expect(model.blocks.length).to.eq(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Update block.
|
|
37
|
+
block.segments.push({ started: new Date().toISOString(), text: 'Hello again!' });
|
|
38
|
+
model.updateBlock(block);
|
|
39
|
+
{
|
|
40
|
+
const text = model.doc.toString();
|
|
41
|
+
expect(text).to.eq('###### Alice\nHello world!\nHello again!\n\n');
|
|
42
|
+
expect(model.blocks.length).to.eq(1);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('sync - append', ({ expect }) => {
|
|
47
|
+
const view = new EditorView({ extensions: [], doc: '' });
|
|
48
|
+
const model = new BlockModel<TranscriptBlock>(blockToMarkdown);
|
|
49
|
+
const adapter = new DocumentAdapter(view);
|
|
50
|
+
expect(adapter.lineCount()).to.eq(0);
|
|
51
|
+
expect(view.state.doc.toString()).to.eq('');
|
|
52
|
+
|
|
53
|
+
// Append block.
|
|
54
|
+
{
|
|
55
|
+
const block = {
|
|
56
|
+
id: '1',
|
|
57
|
+
authorName: 'Alice',
|
|
58
|
+
segments: [{ started: createDate(), text: 'Hello world!' }],
|
|
59
|
+
};
|
|
60
|
+
model.appendBlock(block);
|
|
61
|
+
model.sync(adapter);
|
|
62
|
+
expect(view.state.doc.toString()).to.eq('###### Alice\nHello world!\n\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Append block.
|
|
66
|
+
{
|
|
67
|
+
const block = {
|
|
68
|
+
id: '2',
|
|
69
|
+
authorName: 'Bob',
|
|
70
|
+
segments: [{ started: createDate(), text: 'Hello world!' }],
|
|
71
|
+
};
|
|
72
|
+
model.appendBlock(block);
|
|
73
|
+
model.sync(adapter);
|
|
74
|
+
expect(view.state.doc.toString()).to.eq('###### Alice\nHello world!\n\n###### Bob\nHello world!\n\n');
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('sync - append, update, delete', ({ expect }) => {
|
|
79
|
+
const view = new EditorView({ extensions: [], doc: '' });
|
|
80
|
+
const model = new BlockModel<TranscriptBlock>(blockToMarkdown);
|
|
81
|
+
const adapter = new DocumentAdapter(view);
|
|
82
|
+
expect(adapter.lineCount()).to.eq(0);
|
|
83
|
+
|
|
84
|
+
// Append block.
|
|
85
|
+
{
|
|
86
|
+
const block = { id: '1', authorName: 'Alice', segments: [{ started: createDate(), text: 'Hello world!' }] };
|
|
87
|
+
model.appendBlock(block);
|
|
88
|
+
model.sync(adapter);
|
|
89
|
+
expect(view.state.doc.toString()).to.eq('###### Alice\nHello world!\n\n');
|
|
90
|
+
|
|
91
|
+
// Update block (add segment).
|
|
92
|
+
block.segments.push({ started: createDate(), text: 'Hello again!' });
|
|
93
|
+
model.updateBlock(block);
|
|
94
|
+
model.sync(adapter);
|
|
95
|
+
expect(view.state.doc.toString()).to.eq('###### Alice\nHello world!\nHello again!\n\n');
|
|
96
|
+
|
|
97
|
+
// Update block (remove segment).
|
|
98
|
+
block.segments.splice(0, 1);
|
|
99
|
+
model.updateBlock(block);
|
|
100
|
+
model.sync(adapter);
|
|
101
|
+
expect(view.state.doc.toString()).to.eq('###### Alice\nHello again!\n\n');
|
|
102
|
+
|
|
103
|
+
expect(model.getBlockAtLine(1)).to.eq(block);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Append block.
|
|
107
|
+
{
|
|
108
|
+
const block = { id: '2', authorName: 'Bob', segments: [{ started: createDate(), text: 'Hello again!' }] };
|
|
109
|
+
model.appendBlock(block);
|
|
110
|
+
model.sync(adapter);
|
|
111
|
+
expect(view.state.doc.toString()).to.eq('###### Alice\nHello again!\n\n###### Bob\nHello again!\n\n');
|
|
112
|
+
|
|
113
|
+
expect(model.getBlockAtLine(4)).to.eq(block);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Delete block.
|
|
117
|
+
{
|
|
118
|
+
model.deleteBlock('1');
|
|
119
|
+
model.sync(adapter);
|
|
120
|
+
expect(view.state.doc.toString()).to.eq('###### Bob\nHello again!\n\n');
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|