@devicai/ui 0.59.0 → 0.61.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 (63) hide show
  1. package/README.md +10 -0
  2. package/dist/cjs/api/client.js +41 -6
  3. package/dist/cjs/api/client.js.map +1 -1
  4. package/dist/cjs/api/types.js.map +1 -1
  5. package/dist/cjs/components/ChatDrawer/ChatDrawer.js +9 -2
  6. package/dist/cjs/components/ChatDrawer/ChatDrawer.js.map +1 -1
  7. package/dist/cjs/components/ChatDrawer/ChatMessages.js +2 -1
  8. package/dist/cjs/components/ChatDrawer/ChatMessages.js.map +1 -1
  9. package/dist/cjs/components/ChatDrawer/LiveVoicePanel.js +106 -0
  10. package/dist/cjs/components/ChatDrawer/LiveVoicePanel.js.map +1 -0
  11. package/dist/cjs/components/ChatDrawer/LiveVoicePrompter.js +148 -0
  12. package/dist/cjs/components/ChatDrawer/LiveVoicePrompter.js.map +1 -0
  13. package/dist/cjs/hooks/useDevicChat.js +68 -17
  14. package/dist/cjs/hooks/useDevicChat.js.map +1 -1
  15. package/dist/cjs/hooks/useDevicLiveVoice.js +73 -0
  16. package/dist/cjs/hooks/useDevicLiveVoice.js.map +1 -0
  17. package/dist/cjs/hooks/usePolling.js +1 -1
  18. package/dist/cjs/hooks/usePolling.js.map +1 -1
  19. package/dist/cjs/index.js +4 -0
  20. package/dist/cjs/index.js.map +1 -1
  21. package/dist/cjs/styles.css +1 -1
  22. package/dist/cjs/utils/consumeChatStream.js +36 -6
  23. package/dist/cjs/utils/consumeChatStream.js.map +1 -1
  24. package/dist/cjs/voice/LiveVoiceController.js +287 -0
  25. package/dist/cjs/voice/LiveVoiceController.js.map +1 -0
  26. package/dist/esm/api/client.d.ts +12 -3
  27. package/dist/esm/api/client.js +41 -6
  28. package/dist/esm/api/client.js.map +1 -1
  29. package/dist/esm/api/liveVoice.types.d.ts +49 -0
  30. package/dist/esm/api/types.d.ts +3 -0
  31. package/dist/esm/api/types.js.map +1 -1
  32. package/dist/esm/components/ChatDrawer/ChatDrawer.js +11 -4
  33. package/dist/esm/components/ChatDrawer/ChatDrawer.js.map +1 -1
  34. package/dist/esm/components/ChatDrawer/ChatDrawer.types.d.ts +6 -0
  35. package/dist/esm/components/ChatDrawer/ChatMessages.js +2 -1
  36. package/dist/esm/components/ChatDrawer/ChatMessages.js.map +1 -1
  37. package/dist/esm/components/ChatDrawer/LiveVoicePanel.d.ts +21 -0
  38. package/dist/esm/components/ChatDrawer/LiveVoicePanel.js +102 -0
  39. package/dist/esm/components/ChatDrawer/LiveVoicePanel.js.map +1 -0
  40. package/dist/esm/components/ChatDrawer/LiveVoicePrompter.d.ts +12 -0
  41. package/dist/esm/components/ChatDrawer/LiveVoicePrompter.js +145 -0
  42. package/dist/esm/components/ChatDrawer/LiveVoicePrompter.js.map +1 -0
  43. package/dist/esm/hooks/index.d.ts +2 -0
  44. package/dist/esm/hooks/useDevicChat.d.ts +6 -0
  45. package/dist/esm/hooks/useDevicChat.js +68 -17
  46. package/dist/esm/hooks/useDevicChat.js.map +1 -1
  47. package/dist/esm/hooks/useDevicLiveVoice.d.ts +20 -0
  48. package/dist/esm/hooks/useDevicLiveVoice.js +71 -0
  49. package/dist/esm/hooks/useDevicLiveVoice.js.map +1 -0
  50. package/dist/esm/hooks/usePolling.d.ts +1 -1
  51. package/dist/esm/hooks/usePolling.js +1 -1
  52. package/dist/esm/hooks/usePolling.js.map +1 -1
  53. package/dist/esm/index.d.ts +6 -0
  54. package/dist/esm/index.js +2 -0
  55. package/dist/esm/index.js.map +1 -1
  56. package/dist/esm/styles.css +1 -1
  57. package/dist/esm/utils/consumeChatStream.d.ts +8 -4
  58. package/dist/esm/utils/consumeChatStream.js +36 -6
  59. package/dist/esm/utils/consumeChatStream.js.map +1 -1
  60. package/dist/esm/voice/LiveVoiceController.d.ts +38 -0
  61. package/dist/esm/voice/LiveVoiceController.js +284 -0
  62. package/dist/esm/voice/LiveVoiceController.js.map +1 -0
  63. package/package.json +1 -1
@@ -1,7 +1,11 @@
1
1
  /**
2
- * Consume Devic's version-1 SSE snapshots, tolerating arbitrary UTF-8/chunk
3
- * boundaries. `onActivity` fires on every chunk, keep-alive comments included,
4
- * so the caller can tell a quiet connection from a dead one.
2
+ * Consume Devic's version-1 SSE frames, tolerating arbitrary UTF-8/chunk
3
+ * boundaries. `snapshot` frames carry the whole state. When the stream was
4
+ * opened with `?partial=1`, changes to the reply being written arrive as
5
+ * `partial` frames (the whole `streamingMessage`) or `delta` frames (only the
6
+ * text appended to it); both are merged into the last snapshot, so the caller
7
+ * always receives a full state. `onActivity` fires on every chunk, keep-alive
8
+ * comments included, so the caller can tell a quiet connection from a dead one.
5
9
  */
6
10
  async function consumeChatStream(response, onSnapshot, onActivity) {
7
11
  if (!response.ok || !response.headers.get('content-type')?.includes('text/event-stream') || !response.body) {
@@ -10,6 +14,7 @@ async function consumeChatStream(response, onSnapshot, onActivity) {
10
14
  const reader = response.body.getReader();
11
15
  const decoder = new TextDecoder();
12
16
  let buffer = '';
17
+ let last;
13
18
  try {
14
19
  while (true) {
15
20
  const { value, done } = await reader.read();
@@ -24,11 +29,36 @@ async function consumeChatStream(response, onSnapshot, onActivity) {
24
29
  const frame = buffer.slice(0, boundary);
25
30
  buffer = buffer.slice(boundary + 2);
26
31
  const lines = frame.split('\n');
27
- if (!lines.some(line => line === 'event: snapshot'))
32
+ const event = lines.find(line => line.startsWith('event:'))?.slice(6).trim();
33
+ if (event !== 'snapshot' && event !== 'partial' && event !== 'delta')
28
34
  continue;
29
35
  const data = lines.filter(line => line.startsWith('data:')).map(line => line.slice(5).trimStart()).join('\n');
30
- if (data)
31
- await onSnapshot(JSON.parse(data));
36
+ if (!data)
37
+ continue;
38
+ const parsed = JSON.parse(data);
39
+ if (event === 'snapshot') {
40
+ last = parsed;
41
+ }
42
+ else if (!last) {
43
+ // Nothing to merge into yet; the next snapshot carries everything.
44
+ continue;
45
+ }
46
+ else if (event === 'partial') {
47
+ last = { ...last, ...parsed };
48
+ }
49
+ else {
50
+ const message = last.streamingMessage;
51
+ if (!message || typeof parsed.append !== 'string')
52
+ continue;
53
+ last = {
54
+ ...last,
55
+ streamingMessage: {
56
+ ...message,
57
+ content: { ...message.content, message: `${message.content?.message ?? ''}${parsed.append}` },
58
+ },
59
+ };
60
+ }
61
+ await onSnapshot(last);
32
62
  }
33
63
  }
34
64
  }
@@ -1 +1 @@
1
- {"version":3,"file":"consumeChatStream.js","sources":["../../../src/utils/consumeChatStream.ts"],"sourcesContent":["/**\n * Consume Devic's version-1 SSE snapshots, tolerating arbitrary UTF-8/chunk\n * boundaries. `onActivity` fires on every chunk, keep-alive comments included,\n * so the caller can tell a quiet connection from a dead one.\n */\nexport async function consumeChatStream<T>(\n response: Response,\n onSnapshot: (snapshot: T) => void | Promise<void>,\n onActivity?: () => void,\n): Promise<void> {\n if (!response.ok || !response.headers.get('content-type')?.includes('text/event-stream') || !response.body) {\n throw new Error('Chat streaming unavailable');\n }\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n onActivity?.();\n buffer += decoder.decode(value, { stream: true }).replace(/\\r\\n/g, '\\n');\n if (buffer.length > 16 * 1024 * 1024) throw new Error('Chat stream frame too large');\n let boundary: number;\n while ((boundary = buffer.indexOf('\\n\\n')) !== -1) {\n const frame = buffer.slice(0, boundary);\n buffer = buffer.slice(boundary + 2);\n const lines = frame.split('\\n');\n if (!lines.some(line => line === 'event: snapshot')) continue;\n const data = lines.filter(line => line.startsWith('data:')).map(line => line.slice(5).trimStart()).join('\\n');\n if (data) await onSnapshot(JSON.parse(data));\n }\n }\n } finally {\n await reader.cancel().catch(() => {});\n reader.releaseLock();\n }\n}\n\n"],"names":[],"mappings":"AAAA;;;;AAIG;AACI,eAAe,iBAAiB,CACrC,QAAkB,EAClB,UAAiD,EACjD,UAAuB,EAAA;IAEvB,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE;AAC1G,QAAA,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC;IAC/C;IACA,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE;AACxC,IAAA,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE;IACjC,IAAI,MAAM,GAAG,EAAE;AACf,IAAA,IAAI;QACF,OAAO,IAAI,EAAE;YACX,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE;AAC3C,YAAA,IAAI,IAAI;gBAAE;YACV,UAAU,IAAI;YACd,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC;YACxE,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI;AAAE,gBAAA,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC;AACpF,YAAA,IAAI,QAAgB;AACpB,YAAA,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE;gBACjD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;gBACvC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC;gBACnC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;AAC/B,gBAAA,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,iBAAiB,CAAC;oBAAE;AACrD,gBAAA,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;AAC7G,gBAAA,IAAI,IAAI;oBAAE,MAAM,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC9C;QACF;IACF;YAAU;AACR,QAAA,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,MAAK,EAAE,CAAC,CAAC;QACrC,MAAM,CAAC,WAAW,EAAE;IACtB;AACF;;;;"}
1
+ {"version":3,"file":"consumeChatStream.js","sources":["../../../src/utils/consumeChatStream.ts"],"sourcesContent":["/**\n * Consume Devic's version-1 SSE frames, tolerating arbitrary UTF-8/chunk\n * boundaries. `snapshot` frames carry the whole state. When the stream was\n * opened with `?partial=1`, changes to the reply being written arrive as\n * `partial` frames (the whole `streamingMessage`) or `delta` frames (only the\n * text appended to it); both are merged into the last snapshot, so the caller\n * always receives a full state. `onActivity` fires on every chunk, keep-alive\n * comments included, so the caller can tell a quiet connection from a dead one.\n */\nexport async function consumeChatStream<T extends object>(\n response: Response,\n onSnapshot: (snapshot: T) => void | Promise<void>,\n onActivity?: () => void,\n): Promise<void> {\n if (!response.ok || !response.headers.get('content-type')?.includes('text/event-stream') || !response.body) {\n throw new Error('Chat streaming unavailable');\n }\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let last: (T & { streamingMessage?: any }) | undefined;\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n onActivity?.();\n buffer += decoder.decode(value, { stream: true }).replace(/\\r\\n/g, '\\n');\n if (buffer.length > 16 * 1024 * 1024) throw new Error('Chat stream frame too large');\n let boundary: number;\n while ((boundary = buffer.indexOf('\\n\\n')) !== -1) {\n const frame = buffer.slice(0, boundary);\n buffer = buffer.slice(boundary + 2);\n const lines = frame.split('\\n');\n const event = lines.find(line => line.startsWith('event:'))?.slice(6).trim();\n if (event !== 'snapshot' && event !== 'partial' && event !== 'delta') continue;\n const data = lines.filter(line => line.startsWith('data:')).map(line => line.slice(5).trimStart()).join('\\n');\n if (!data) continue;\n const parsed = JSON.parse(data);\n if (event === 'snapshot') {\n last = parsed;\n } else if (!last) {\n // Nothing to merge into yet; the next snapshot carries everything.\n continue;\n } else if (event === 'partial') {\n last = { ...last, ...parsed };\n } else {\n const message = last.streamingMessage;\n if (!message || typeof parsed.append !== 'string') continue;\n last = {\n ...last,\n streamingMessage: {\n ...message,\n content: { ...message.content, message: `${message.content?.message ?? ''}${parsed.append}` },\n },\n };\n }\n await onSnapshot(last as T);\n }\n }\n } finally {\n await reader.cancel().catch(() => {});\n reader.releaseLock();\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { DevicApiClient } from '../api/client';
2
+ import type { LiveVoiceContext, LiveVoiceTurn } from '../api/liveVoice.types';
3
+ export type LiveVoiceState = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'closing' | 'error';
4
+ export interface LiveVoiceSnapshot {
5
+ state: LiveVoiceState;
6
+ chatUid?: string;
7
+ sessionId?: string;
8
+ muted: boolean;
9
+ seconds: number;
10
+ transcript: LiveVoiceTurn[];
11
+ input?: MediaStream;
12
+ output?: MediaStream;
13
+ error?: Error;
14
+ playbackBlocked?: boolean;
15
+ }
16
+ export declare const initialVoiceSnapshot: () => LiveVoiceSnapshot;
17
+ /** No browser globals at import/constructor time; loaded only by start(). */
18
+ export declare class LiveVoiceController {
19
+ private client;
20
+ private assistantId;
21
+ private context;
22
+ private changed;
23
+ private created;
24
+ private snapshot;
25
+ private generation;
26
+ private attempts;
27
+ private disposeTransport;
28
+ private audio?;
29
+ private closing?;
30
+ private disposed;
31
+ constructor(client: DevicApiClient, assistantId: string, context: LiveVoiceContext, changed: (value: LiveVoiceSnapshot) => void, created: (chatUid: string) => void);
32
+ private update;
33
+ mute(muted: boolean): void;
34
+ play(): Promise<void>;
35
+ start(chatUid?: string, recovering?: boolean): Promise<void>;
36
+ stop(): Promise<void>;
37
+ dispose(): void;
38
+ }
@@ -0,0 +1,284 @@
1
+ const initialVoiceSnapshot = () => ({ state: 'idle', muted: false, seconds: 0, transcript: [] });
2
+ /** No browser globals at import/constructor time; loaded only by start(). */
3
+ class LiveVoiceController {
4
+ constructor(client, assistantId, context, changed, created) {
5
+ this.client = client;
6
+ this.assistantId = assistantId;
7
+ this.context = context;
8
+ this.changed = changed;
9
+ this.created = created;
10
+ this.snapshot = initialVoiceSnapshot();
11
+ this.generation = 0;
12
+ this.attempts = 0;
13
+ this.disposeTransport = () => { };
14
+ this.disposed = false;
15
+ }
16
+ update(patch) {
17
+ this.snapshot = { ...this.snapshot, ...patch };
18
+ if (!this.disposed)
19
+ this.changed(this.snapshot);
20
+ }
21
+ mute(muted) {
22
+ this.snapshot.input?.getAudioTracks().forEach(track => { track.enabled = !muted && this.snapshot.state === 'connected'; });
23
+ this.update({ muted });
24
+ }
25
+ async play() {
26
+ try {
27
+ await this.audio?.play();
28
+ this.update({ playbackBlocked: false });
29
+ }
30
+ catch {
31
+ this.update({ playbackBlocked: true });
32
+ }
33
+ }
34
+ async start(chatUid, recovering = false) {
35
+ if (this.disposed || this.closing || (!recovering && !['idle', 'error'].includes(this.snapshot.state)))
36
+ return;
37
+ const generation = ++this.generation;
38
+ const current = () => generation === this.generation && !this.disposed;
39
+ if (!recovering)
40
+ this.attempts = 0;
41
+ this.update({ state: 'connecting', error: undefined, chatUid, sessionId: undefined,
42
+ seconds: 0, ...(recovering ? {} : { transcript: [], muted: false }) });
43
+ let media;
44
+ let peer;
45
+ let channel;
46
+ let audio;
47
+ let health;
48
+ let deadline;
49
+ let connectDeadline;
50
+ let backendReady = false;
51
+ let lastTranscript = 0;
52
+ let sessionId;
53
+ let connectionLostAt = 0;
54
+ let healthFailures = 0;
55
+ const cleanup = () => {
56
+ clearInterval(health);
57
+ clearTimeout(deadline);
58
+ clearTimeout(connectDeadline);
59
+ media?.getTracks().forEach(track => track.stop());
60
+ if (channel) {
61
+ channel.onclose = null;
62
+ channel.onmessage = null;
63
+ channel.close();
64
+ }
65
+ if (peer) {
66
+ peer.onconnectionstatechange = null;
67
+ peer.ontrack = null;
68
+ peer.close();
69
+ }
70
+ if (audio) {
71
+ audio.pause();
72
+ audio.srcObject = null;
73
+ }
74
+ this.audio = undefined;
75
+ this.update({ input: undefined, output: undefined });
76
+ };
77
+ this.disposeTransport = cleanup;
78
+ const ready = () => {
79
+ if (!current() || !backendReady || peer?.connectionState !== 'connected')
80
+ return;
81
+ clearTimeout(connectDeadline);
82
+ media?.getAudioTracks().forEach(track => { track.enabled = !this.snapshot.muted; });
83
+ this.update({ state: 'connected' });
84
+ };
85
+ const recover = async () => {
86
+ if (!current())
87
+ return;
88
+ ++this.generation;
89
+ const ticket = this.generation;
90
+ cleanup();
91
+ this.update({ state: 'reconnecting' });
92
+ try {
93
+ if (!sessionId || ++this.attempts > 3)
94
+ throw new Error('Voice connection interrupted. Please start again.');
95
+ await this.client.closeLiveSession(this.assistantId, sessionId);
96
+ // Never open another paid transport before the old one is confirmed closed.
97
+ for (let i = 0; i < 35 && ticket === this.generation && !this.disposed; i++) {
98
+ const state = await this.client.getLiveSessionStatus(this.assistantId, sessionId);
99
+ if (state.status === 'closed') {
100
+ if (ticket === this.generation && !this.disposed)
101
+ await this.start(this.snapshot.chatUid, true);
102
+ return;
103
+ }
104
+ await new Promise(resolve => setTimeout(resolve, 2000));
105
+ }
106
+ if (ticket === this.generation)
107
+ throw new Error('Could not confirm the previous voice session closed.');
108
+ }
109
+ catch (error) {
110
+ if (ticket === this.generation)
111
+ this.update({ state: 'error', error: error });
112
+ }
113
+ };
114
+ try {
115
+ if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia || typeof RTCPeerConnection === 'undefined')
116
+ throw new Error('Voice requires microphone access on HTTPS or localhost.');
117
+ media = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true }, video: false });
118
+ if (!current()) {
119
+ media.getTracks().forEach(track => track.stop());
120
+ return;
121
+ }
122
+ media.getAudioTracks().forEach(track => { track.enabled = false; });
123
+ this.update({ input: media });
124
+ peer = new RTCPeerConnection({ bundlePolicy: 'max-bundle' });
125
+ audio = new Audio();
126
+ this.audio = audio;
127
+ audio.autoplay = true;
128
+ peer.ontrack = event => {
129
+ if (!current())
130
+ return;
131
+ const output = new MediaStream([event.track]);
132
+ audio.srcObject = output;
133
+ this.update({ output });
134
+ void this.play();
135
+ };
136
+ peer.onconnectionstatechange = () => {
137
+ ready();
138
+ if (peer?.connectionState === 'disconnected')
139
+ connectionLostAt || (connectionLostAt = Date.now());
140
+ else
141
+ connectionLostAt = 0;
142
+ if (peer?.connectionState === 'failed')
143
+ void recover();
144
+ };
145
+ media.getTracks().forEach(track => peer.addTrack(track, media));
146
+ channel = peer.createDataChannel('oai-events');
147
+ channel.onmessage = message => {
148
+ if (!current())
149
+ return;
150
+ try {
151
+ const event = JSON.parse(message.data);
152
+ if (event.type === 'session.started') {
153
+ backendReady = true;
154
+ ready();
155
+ }
156
+ if (['session.input_transcript.delta', 'session.output_transcript.delta'].includes(event.type) && typeof event.delta === 'string') {
157
+ lastTranscript = Date.now();
158
+ const role = event.type === 'session.input_transcript.delta' ? 'user' : 'assistant';
159
+ const turns = this.snapshot.transcript.slice(-3).map(turn => ({ ...turn }));
160
+ if (turns[turns.length - 1]?.role === role)
161
+ turns[turns.length - 1].text = (turns[turns.length - 1].text + event.delta).slice(-2000);
162
+ else
163
+ turns.push({ role, text: event.delta.slice(-2000) });
164
+ this.update({ transcript: turns });
165
+ }
166
+ if (event.type === 'session.closed' && sessionId) {
167
+ void this.client.getLiveSessionStatus(this.assistantId, sessionId).then(state => {
168
+ if (!current())
169
+ return;
170
+ if (state.restartRequested)
171
+ void recover();
172
+ else
173
+ void this.stop();
174
+ }).catch(() => { if (current())
175
+ void this.stop(); });
176
+ }
177
+ if (event.type === 'error') {
178
+ this.update({ error: new Error('Voice session failed.') });
179
+ void this.stop();
180
+ }
181
+ }
182
+ catch { /* Only lifecycle and spoken transcript events are accepted. */ }
183
+ };
184
+ channel.onclose = () => { if (current())
185
+ void recover(); };
186
+ connectDeadline = setTimeout(() => {
187
+ if (current()) {
188
+ this.update({ error: new Error('Voice connection timed out.') });
189
+ void this.stop();
190
+ }
191
+ }, 30000);
192
+ await peer.setLocalDescription(await peer.createOffer());
193
+ await new Promise((resolve, reject) => {
194
+ if (peer.iceGatheringState === 'complete')
195
+ return resolve();
196
+ const done = () => {
197
+ if (peer.iceGatheringState !== 'complete')
198
+ return;
199
+ clearTimeout(timer);
200
+ peer.removeEventListener('icegatheringstatechange', done);
201
+ resolve();
202
+ };
203
+ const timer = setTimeout(() => { peer.removeEventListener('icegatheringstatechange', done); reject(new Error('Could not prepare the audio connection.')); }, 10000);
204
+ peer.addEventListener('icegatheringstatechange', done);
205
+ });
206
+ if (!current())
207
+ return;
208
+ const result = await this.client.createLiveSession(this.assistantId, { ...this.context, chatUid, sdp: peer.localDescription.sdp });
209
+ sessionId = result.sessionId;
210
+ if (!current()) {
211
+ void this.client.closeLiveSession(this.assistantId, sessionId).catch(() => { });
212
+ return;
213
+ }
214
+ this.update({ sessionId, chatUid: result.chatUid });
215
+ this.created(result.chatUid);
216
+ deadline = setTimeout(() => { void this.stop(); }, Math.max(1, Math.min(6000, result.maxDurationSeconds || 6000)) * 1000);
217
+ await peer.setRemoteDescription({ type: 'answer', sdp: result.sdp });
218
+ let busy = false;
219
+ health = setInterval(async () => {
220
+ if (!current() || busy)
221
+ return;
222
+ busy = true;
223
+ try {
224
+ const state = await this.client.getLiveSessionStatus(this.assistantId, sessionId);
225
+ if (!current())
226
+ return;
227
+ healthFailures = 0;
228
+ this.update({ seconds: state.seconds });
229
+ if (state.interrupted || state.restartRequested || (connectionLostAt && Date.now() - connectionLostAt > 5000)) {
230
+ void recover();
231
+ return;
232
+ }
233
+ if (state.status === 'closed') {
234
+ void this.stop();
235
+ return;
236
+ }
237
+ backendReady = state.connected;
238
+ ready();
239
+ if (Date.now() - lastTranscript > 1500 && state.transcript?.length)
240
+ this.update({ transcript: state.transcript.slice(-4).filter(t => t.role === 'user' || t.role === 'assistant').map(t => ({ role: t.role, text: t.text.slice(-2000) })) });
241
+ }
242
+ catch {
243
+ if (current() && ++healthFailures >= 5) {
244
+ this.update({ error: new Error('Voice status unavailable. Session stopped.') });
245
+ void this.stop();
246
+ }
247
+ }
248
+ finally {
249
+ busy = false;
250
+ }
251
+ }, 1000);
252
+ }
253
+ catch (error) {
254
+ if (!current())
255
+ return;
256
+ this.update({ error: error instanceof Error ? error : new Error(String(error)) });
257
+ await this.stop();
258
+ }
259
+ }
260
+ stop() {
261
+ if (this.closing)
262
+ return this.closing;
263
+ ++this.generation;
264
+ this.disposeTransport();
265
+ const id = this.snapshot.sessionId;
266
+ this.update({ state: id ? 'closing' : (this.snapshot.error ? 'error' : 'idle') });
267
+ this.closing = (async () => {
268
+ if (id) {
269
+ try {
270
+ await this.client.closeLiveSession(this.assistantId, id);
271
+ }
272
+ catch (error) {
273
+ this.update({ error: error });
274
+ }
275
+ }
276
+ this.update({ state: this.snapshot.error ? 'error' : 'idle', sessionId: undefined });
277
+ })().finally(() => { this.closing = undefined; });
278
+ return this.closing;
279
+ }
280
+ dispose() { this.disposed = true; void this.stop(); }
281
+ }
282
+
283
+ export { LiveVoiceController, initialVoiceSnapshot };
284
+ //# sourceMappingURL=LiveVoiceController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LiveVoiceController.js","sources":["../../../src/voice/LiveVoiceController.ts"],"sourcesContent":["import type { DevicApiClient } from '../api/client';\nimport type { LiveVoiceContext, LiveVoiceTurn } from '../api/liveVoice.types';\n\nexport type LiveVoiceState = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'closing' | 'error';\nexport interface LiveVoiceSnapshot {\n state: LiveVoiceState;\n chatUid?: string;\n sessionId?: string;\n muted: boolean;\n seconds: number;\n transcript: LiveVoiceTurn[];\n input?: MediaStream;\n output?: MediaStream;\n error?: Error;\n playbackBlocked?: boolean;\n}\nexport const initialVoiceSnapshot = (): LiveVoiceSnapshot => ({ state: 'idle', muted: false, seconds: 0, transcript: [] });\n\n/** No browser globals at import/constructor time; loaded only by start(). */\nexport class LiveVoiceController {\n private snapshot = initialVoiceSnapshot();\n private generation = 0;\n private attempts = 0;\n private disposeTransport: () => void = () => {};\n private audio?: HTMLAudioElement;\n private closing?: Promise<void>;\n private disposed = false;\n constructor(private client: DevicApiClient, private assistantId: string,\n private context: LiveVoiceContext, private changed: (value: LiveVoiceSnapshot) => void,\n private created: (chatUid: string) => void) {}\n\n private update(patch: Partial<LiveVoiceSnapshot>) {\n this.snapshot = { ...this.snapshot, ...patch };\n if (!this.disposed) this.changed(this.snapshot);\n }\n mute(muted: boolean) {\n this.snapshot.input?.getAudioTracks().forEach(track => { track.enabled = !muted && this.snapshot.state === 'connected'; });\n this.update({ muted });\n }\n async play() {\n try { await this.audio?.play(); this.update({ playbackBlocked: false }); }\n catch { this.update({ playbackBlocked: true }); }\n }\n async start(chatUid?: string, recovering = false): Promise<void> {\n if (this.disposed || this.closing || (!recovering && !['idle', 'error'].includes(this.snapshot.state))) return;\n const generation = ++this.generation;\n const current = () => generation === this.generation && !this.disposed;\n if (!recovering) this.attempts = 0;\n this.update({ state: 'connecting', error: undefined, chatUid, sessionId: undefined,\n seconds: 0, ...(recovering ? {} : { transcript: [], muted: false }) });\n let media: MediaStream | undefined;\n let peer: RTCPeerConnection | undefined;\n let channel: RTCDataChannel | undefined;\n let audio: HTMLAudioElement | undefined;\n let health: ReturnType<typeof setInterval> | undefined;\n let deadline: ReturnType<typeof setTimeout> | undefined;\n let connectDeadline: ReturnType<typeof setTimeout> | undefined;\n let backendReady = false;\n let lastTranscript = 0;\n let sessionId: string | undefined;\n let connectionLostAt = 0;\n let healthFailures = 0;\n const cleanup = () => {\n clearInterval(health); clearTimeout(deadline); clearTimeout(connectDeadline);\n media?.getTracks().forEach(track => track.stop());\n if (channel) { channel.onclose = null; channel.onmessage = null; channel.close(); }\n if (peer) { peer.onconnectionstatechange = null; peer.ontrack = null; peer.close(); }\n if (audio) { audio.pause(); audio.srcObject = null; }\n this.audio = undefined;\n this.update({ input: undefined, output: undefined });\n };\n this.disposeTransport = cleanup;\n const ready = () => {\n if (!current() || !backendReady || peer?.connectionState !== 'connected') return;\n clearTimeout(connectDeadline);\n media?.getAudioTracks().forEach(track => { track.enabled = !this.snapshot.muted; });\n this.update({ state: 'connected' });\n };\n const recover = async () => {\n if (!current()) return;\n ++this.generation;\n const ticket = this.generation;\n cleanup();\n this.update({ state: 'reconnecting' });\n try {\n if (!sessionId || ++this.attempts > 3) throw new Error('Voice connection interrupted. Please start again.');\n await this.client.closeLiveSession(this.assistantId, sessionId);\n // Never open another paid transport before the old one is confirmed closed.\n for (let i = 0; i < 35 && ticket === this.generation && !this.disposed; i++) {\n const state = await this.client.getLiveSessionStatus(this.assistantId, sessionId);\n if (state.status === 'closed') {\n if (ticket === this.generation && !this.disposed) await this.start(this.snapshot.chatUid, true);\n return;\n }\n await new Promise(resolve => setTimeout(resolve, 2000));\n }\n if (ticket === this.generation) throw new Error('Could not confirm the previous voice session closed.');\n } catch (error) {\n if (ticket === this.generation) this.update({ state: 'error', error: error as Error });\n }\n };\n try {\n if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia || typeof RTCPeerConnection === 'undefined')\n throw new Error('Voice requires microphone access on HTTPS or localhost.');\n media = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true }, video: false });\n if (!current()) { media.getTracks().forEach(track => track.stop()); return; }\n media.getAudioTracks().forEach(track => { track.enabled = false; });\n this.update({ input: media });\n peer = new RTCPeerConnection({ bundlePolicy: 'max-bundle' });\n audio = new Audio(); this.audio = audio; audio.autoplay = true;\n peer.ontrack = event => {\n if (!current()) return;\n const output = new MediaStream([event.track]);\n audio!.srcObject = output;\n this.update({ output }); void this.play();\n };\n peer.onconnectionstatechange = () => {\n ready();\n if (peer?.connectionState === 'disconnected') connectionLostAt ||= Date.now();\n else connectionLostAt = 0;\n if (peer?.connectionState === 'failed') void recover();\n };\n media.getTracks().forEach(track => peer!.addTrack(track, media!));\n channel = peer.createDataChannel('oai-events');\n channel.onmessage = message => {\n if (!current()) return;\n try {\n const event = JSON.parse(message.data);\n if (event.type === 'session.started') { backendReady = true; ready(); }\n if (['session.input_transcript.delta', 'session.output_transcript.delta'].includes(event.type) && typeof event.delta === 'string') {\n lastTranscript = Date.now();\n const role = event.type === 'session.input_transcript.delta' ? 'user' : 'assistant';\n const turns = this.snapshot.transcript.slice(-3).map(turn => ({ ...turn }));\n if (turns[turns.length - 1]?.role === role) turns[turns.length - 1].text = (turns[turns.length - 1].text + event.delta).slice(-2000);\n else turns.push({ role, text: event.delta.slice(-2000) });\n this.update({ transcript: turns });\n }\n if (event.type === 'session.closed' && sessionId) {\n void this.client.getLiveSessionStatus(this.assistantId, sessionId).then(state => {\n if (!current()) return;\n if (state.restartRequested) void recover(); else void this.stop();\n }).catch(() => { if (current()) void this.stop(); });\n }\n if (event.type === 'error') { this.update({ error: new Error('Voice session failed.') }); void this.stop(); }\n } catch { /* Only lifecycle and spoken transcript events are accepted. */ }\n };\n channel.onclose = () => { if (current()) void recover(); };\n connectDeadline = setTimeout(() => {\n if (current()) { this.update({ error: new Error('Voice connection timed out.') }); void this.stop(); }\n }, 30000);\n await peer.setLocalDescription(await peer.createOffer());\n await new Promise<void>((resolve, reject) => {\n if (peer!.iceGatheringState === 'complete') return resolve();\n const done = () => {\n if (peer!.iceGatheringState !== 'complete') return;\n clearTimeout(timer); peer!.removeEventListener('icegatheringstatechange', done); resolve();\n };\n const timer = setTimeout(() => { peer!.removeEventListener('icegatheringstatechange', done); reject(new Error('Could not prepare the audio connection.')); }, 10000);\n peer!.addEventListener('icegatheringstatechange', done);\n });\n if (!current()) return;\n const result = await this.client.createLiveSession(this.assistantId, { ...this.context, chatUid, sdp: peer.localDescription!.sdp });\n sessionId = result.sessionId;\n if (!current()) { void this.client.closeLiveSession(this.assistantId, sessionId).catch(() => {}); return; }\n this.update({ sessionId, chatUid: result.chatUid }); this.created(result.chatUid);\n deadline = setTimeout(() => { void this.stop(); }, Math.max(1, Math.min(6000, result.maxDurationSeconds || 6000)) * 1000);\n await peer.setRemoteDescription({ type: 'answer', sdp: result.sdp });\n let busy = false;\n health = setInterval(async () => {\n if (!current() || busy) return;\n busy = true;\n try {\n const state = await this.client.getLiveSessionStatus(this.assistantId, sessionId!);\n if (!current()) return;\n healthFailures = 0;\n this.update({ seconds: state.seconds });\n if (state.interrupted || state.restartRequested || (connectionLostAt && Date.now() - connectionLostAt > 5000)) { void recover(); return; }\n if (state.status === 'closed') { void this.stop(); return; }\n backendReady = state.connected; ready();\n if (Date.now() - lastTranscript > 1500 && state.transcript?.length)\n this.update({ transcript: state.transcript.slice(-4).filter(t => t.role === 'user' || t.role === 'assistant').map(t => ({ role: t.role, text: t.text.slice(-2000) })) });\n } catch {\n if (current() && ++healthFailures >= 5) { this.update({ error: new Error('Voice status unavailable. Session stopped.')} ); void this.stop(); }\n } finally { busy = false; }\n }, 1000);\n } catch (error) {\n if (!current()) return;\n this.update({ error: error instanceof Error ? error : new Error(String(error)) });\n await this.stop();\n }\n }\n stop(): Promise<void> {\n if (this.closing) return this.closing;\n ++this.generation;\n this.disposeTransport();\n const id = this.snapshot.sessionId;\n this.update({ state: id ? 'closing' : (this.snapshot.error ? 'error' : 'idle') });\n this.closing = (async () => {\n if (id) {\n try { await this.client.closeLiveSession(this.assistantId, id); }\n catch (error) { this.update({ error: error as Error }); }\n }\n this.update({ state: this.snapshot.error ? 'error' : 'idle', sessionId: undefined });\n })().finally(() => { this.closing = undefined; });\n return this.closing;\n }\n dispose() { this.disposed = true; void this.stop(); }\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devicai/ui",
3
- "version": "0.59.0",
3
+ "version": "0.61.0",
4
4
  "type": "module",
5
5
  "description": "React component library for Devic AI assistants",
6
6
  "engines": {