@runtypelabs/voice 0.2.3 → 0.3.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.
- package/README.md +131 -6
- package/dist/index.cjs +211 -17
- package/dist/index.d.cts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +211 -17
- package/dist/persona.cjs +272 -21
- package/dist/persona.d.cts +23 -4
- package/dist/persona.d.ts +23 -4
- package/dist/persona.js +272 -21
- package/dist/react.cjs +261 -24
- package/dist/react.d.cts +7 -4
- package/dist/react.d.ts +7 -4
- package/dist/react.js +262 -25
- package/dist/types-DaXyPeiV.d.cts +95 -0
- package/dist/types-DaXyPeiV.d.ts +95 -0
- package/package.json +9 -10
- package/dist/types-9SfA7oHc.d.cts +0 -40
- package/dist/types-9SfA7oHc.d.ts +0 -40
package/dist/persona.js
CHANGED
|
@@ -143,9 +143,58 @@ function initialSnapshot() {
|
|
|
143
143
|
error: null,
|
|
144
144
|
errorDetails: void 0,
|
|
145
145
|
interruptionMode: "none",
|
|
146
|
-
canCancel: false
|
|
146
|
+
canCancel: false,
|
|
147
|
+
artifacts: [],
|
|
148
|
+
session: null
|
|
147
149
|
};
|
|
148
150
|
}
|
|
151
|
+
function readString(value) {
|
|
152
|
+
return typeof value === "string" && value ? value : void 0;
|
|
153
|
+
}
|
|
154
|
+
function readArtifactFile(value) {
|
|
155
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
156
|
+
const file = value;
|
|
157
|
+
const path = readString(file.path);
|
|
158
|
+
const mimeType = readString(file.mimeType);
|
|
159
|
+
if (!path || !mimeType) return void 0;
|
|
160
|
+
const language = readString(file.language);
|
|
161
|
+
return { path, mimeType, ...language ? { language } : {} };
|
|
162
|
+
}
|
|
163
|
+
function applyArtifactFrame(existing, frame, identity) {
|
|
164
|
+
if (frame.type === "artifact_start") {
|
|
165
|
+
const title = readString(frame.title);
|
|
166
|
+
const component = readString(frame.component);
|
|
167
|
+
const file = readArtifactFile(frame.file);
|
|
168
|
+
return {
|
|
169
|
+
...identity,
|
|
170
|
+
artifactType: frame.artifactType === "component" ? "component" : "markdown",
|
|
171
|
+
...title ? { title } : {},
|
|
172
|
+
...file ? { file } : {},
|
|
173
|
+
...component ? { component } : {},
|
|
174
|
+
content: "",
|
|
175
|
+
status: "streaming"
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (!existing) return void 0;
|
|
179
|
+
if (frame.type === "artifact_delta") {
|
|
180
|
+
const delta = typeof frame.delta === "string" ? frame.delta : "";
|
|
181
|
+
return delta ? { ...existing, content: existing.content + delta } : void 0;
|
|
182
|
+
}
|
|
183
|
+
if (frame.type === "artifact_update") {
|
|
184
|
+
const component = readString(frame.component) ?? existing.component;
|
|
185
|
+
const props = frame.props && typeof frame.props === "object" && !Array.isArray(frame.props) ? frame.props : existing.props;
|
|
186
|
+
return {
|
|
187
|
+
...existing,
|
|
188
|
+
...component ? { component } : {},
|
|
189
|
+
...props ? { props } : {}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
if (frame.type === "artifact_complete") return { ...existing, status: "complete" };
|
|
193
|
+
return void 0;
|
|
194
|
+
}
|
|
195
|
+
function readTurnId(msg) {
|
|
196
|
+
return typeof msg.turnId === "string" && msg.turnId ? msg.turnId : null;
|
|
197
|
+
}
|
|
149
198
|
var VoiceClient = class {
|
|
150
199
|
constructor(options) {
|
|
151
200
|
this.options = options;
|
|
@@ -166,6 +215,16 @@ var VoiceClient = class {
|
|
|
166
215
|
stoppedResponse = false;
|
|
167
216
|
responseEnded = true;
|
|
168
217
|
hasPendingAudio = false;
|
|
218
|
+
/**
|
|
219
|
+
* The assistant entry built from clause captions for the turn being spoken. The
|
|
220
|
+
* authoritative transcript replaces it; a stop commits it, because it captions
|
|
221
|
+
* audio the caller already heard.
|
|
222
|
+
*/
|
|
223
|
+
pendingAssistant = null;
|
|
224
|
+
/** Turn whose clause captions were already committed by a stop, so its authoritative text is redundant. */
|
|
225
|
+
committedAssistantTurnId = null;
|
|
226
|
+
activeTurnId = null;
|
|
227
|
+
suppressedArtifactTurnId = null;
|
|
169
228
|
getSnapshot = () => this.snapshot;
|
|
170
229
|
subscribe = (listener) => {
|
|
171
230
|
this.listeners.add(listener);
|
|
@@ -187,7 +246,7 @@ var VoiceClient = class {
|
|
|
187
246
|
if (this.snapshot.status !== "idle" && this.snapshot.status !== "error") return;
|
|
188
247
|
this.cleanup();
|
|
189
248
|
const generation = this.generation;
|
|
190
|
-
this.update({ ...initialSnapshot(), status: "connecting" });
|
|
249
|
+
this.update({ ...initialSnapshot(), session: this.snapshot.session, status: "connecting" });
|
|
191
250
|
try {
|
|
192
251
|
const token = tokenOverride ?? (typeof this.options.clientToken === "function" ? await this.options.clientToken() : this.options.clientToken);
|
|
193
252
|
if (generation !== this.generation) return;
|
|
@@ -202,6 +261,20 @@ var VoiceClient = class {
|
|
|
202
261
|
url.pathname = `${basePath}/ws/agents/${encodeURIComponent(this.options.agentId)}/voice`;
|
|
203
262
|
url.search = "";
|
|
204
263
|
url.searchParams.set("voiceProtocol", "runtype-browser-v1");
|
|
264
|
+
const capabilities = this.options.artifacts ? "partial_transcript,artifacts" : "partial_transcript";
|
|
265
|
+
url.searchParams.set("clientCapabilities", capabilities);
|
|
266
|
+
const sessionId = this.options.sessionId ?? this.snapshot.session?.sessionId;
|
|
267
|
+
if (sessionId) url.searchParams.set("sessionId", sessionId);
|
|
268
|
+
const visitorToken = typeof this.options.visitorToken === "function" ? await this.options.visitorToken() : this.options.visitorToken;
|
|
269
|
+
if (generation !== this.generation) return;
|
|
270
|
+
if (this.options.visitorToken !== void 0 && !visitorToken) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
"Voice visitor credential unavailable. Initialize the client session first."
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
if (visitorToken && !/^cvt_[A-Za-z0-9_-]+$/.test(visitorToken)) {
|
|
276
|
+
throw new Error("Invalid voice visitor credential.");
|
|
277
|
+
}
|
|
205
278
|
url.hash = "";
|
|
206
279
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
207
280
|
audio: { sampleRate: CAPTURE_SAMPLE_RATE, channelCount: 1, echoCancellation: true }
|
|
@@ -225,7 +298,11 @@ var VoiceClient = class {
|
|
|
225
298
|
return;
|
|
226
299
|
}
|
|
227
300
|
this.player = player;
|
|
228
|
-
const socket = new WebSocket(url.toString(), [
|
|
301
|
+
const socket = new WebSocket(url.toString(), [
|
|
302
|
+
"runtype.bearer",
|
|
303
|
+
token,
|
|
304
|
+
...visitorToken ? [visitorToken] : []
|
|
305
|
+
]);
|
|
229
306
|
socket.binaryType = "arraybuffer";
|
|
230
307
|
this.socket = socket;
|
|
231
308
|
socket.onopen = () => {
|
|
@@ -256,6 +333,11 @@ var VoiceClient = class {
|
|
|
256
333
|
this.cleanup();
|
|
257
334
|
this.update({ status: "idle", interimTranscript: null, audioLevel: 0, isMuted: false });
|
|
258
335
|
};
|
|
336
|
+
/** Forget the remembered conversation so the next startCall opens a new one. No-op during a call. */
|
|
337
|
+
resetSession = () => {
|
|
338
|
+
if (this.snapshot.status !== "idle" && this.snapshot.status !== "error") return;
|
|
339
|
+
this.update({ session: null });
|
|
340
|
+
};
|
|
259
341
|
toggleMute = () => {
|
|
260
342
|
this.update({ isMuted: !this.snapshot.isMuted, audioLevel: 0 });
|
|
261
343
|
};
|
|
@@ -270,9 +352,11 @@ var VoiceClient = class {
|
|
|
270
352
|
this.clearPlayback();
|
|
271
353
|
if (this.snapshot.interruptionMode === "none") {
|
|
272
354
|
this.stoppedResponse = !this.responseEnded;
|
|
355
|
+
this.commitClauseCaptions();
|
|
273
356
|
this.setStatus("listening");
|
|
274
357
|
return;
|
|
275
358
|
}
|
|
359
|
+
this.suppressedArtifactTurnId = this.activeTurnId;
|
|
276
360
|
this.awaitingClear = true;
|
|
277
361
|
this.setStatus("listening");
|
|
278
362
|
this.socket.send(JSON.stringify({ type: "cancel" }));
|
|
@@ -314,6 +398,82 @@ var VoiceClient = class {
|
|
|
314
398
|
this.awaitingClear = false;
|
|
315
399
|
this.stoppedResponse = false;
|
|
316
400
|
this.responseEnded = true;
|
|
401
|
+
this.commitClauseCaptions();
|
|
402
|
+
this.committedAssistantTurnId = null;
|
|
403
|
+
this.activeTurnId = null;
|
|
404
|
+
this.suppressedArtifactTurnId = null;
|
|
405
|
+
const settledArtifacts = this.snapshot.artifacts.filter(
|
|
406
|
+
(artifact) => artifact.status === "complete"
|
|
407
|
+
);
|
|
408
|
+
if (settledArtifacts.length !== this.snapshot.artifacts.length)
|
|
409
|
+
this.update({ artifacts: settledArtifacts });
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Folds an `artifact` message into the snapshot. Frames of a turn the caller
|
|
413
|
+
* cancelled are dropped; a playback-only stop leaves the turn running, so its
|
|
414
|
+
* artifacts keep arriving and are kept.
|
|
415
|
+
*/
|
|
416
|
+
applyArtifactMessage(msg) {
|
|
417
|
+
if (this.awaitingClear) return;
|
|
418
|
+
const frame = msg.event;
|
|
419
|
+
if (!frame || typeof frame !== "object" || Array.isArray(frame)) return;
|
|
420
|
+
const event = frame;
|
|
421
|
+
const id = readString(event.id);
|
|
422
|
+
if (!id) return;
|
|
423
|
+
const turnId = readTurnId(msg);
|
|
424
|
+
if (turnId !== null && turnId === this.suppressedArtifactTurnId) return;
|
|
425
|
+
if (turnId !== null) this.activeTurnId = turnId;
|
|
426
|
+
const artifacts = this.snapshot.artifacts;
|
|
427
|
+
const index = artifacts.findIndex((artifact) => artifact.id === id);
|
|
428
|
+
const executionId = readString(msg.executionId);
|
|
429
|
+
const next = applyArtifactFrame(index === -1 ? void 0 : artifacts[index], event, {
|
|
430
|
+
id,
|
|
431
|
+
turnId,
|
|
432
|
+
...executionId ? { executionId } : {}
|
|
433
|
+
});
|
|
434
|
+
if (!next) return;
|
|
435
|
+
this.update({
|
|
436
|
+
artifacts: index === -1 ? [...artifacts, next] : [...artifacts.slice(0, index), next, ...artifacts.slice(index + 1)]
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
/** Settles the spoken clauses in place and ends the turn's claim on them. */
|
|
440
|
+
commitClauseCaptions() {
|
|
441
|
+
this.committedAssistantTurnId = this.pendingAssistant?.turnId ?? null;
|
|
442
|
+
const settled = this.pendingAssistant !== null;
|
|
443
|
+
this.pendingAssistant = null;
|
|
444
|
+
if (!settled) return;
|
|
445
|
+
const transcript = this.snapshot.transcript;
|
|
446
|
+
const last = transcript[transcript.length - 1];
|
|
447
|
+
if (last?.role !== "assistant" || last.partial !== true) return;
|
|
448
|
+
const { partial: _partial, ...committed } = last;
|
|
449
|
+
this.update({ transcript: [...transcript.slice(0, -1), committed] });
|
|
450
|
+
}
|
|
451
|
+
/** Extends the turn's spoken-so-far assistant entry, or opens one for a new turn. */
|
|
452
|
+
appendClauseCaption(text, turnId) {
|
|
453
|
+
const transcript = this.snapshot.transcript;
|
|
454
|
+
const last = transcript[transcript.length - 1];
|
|
455
|
+
if (this.pendingAssistant?.turnId === turnId && last?.role === "assistant") {
|
|
456
|
+
return {
|
|
457
|
+
interimTranscript: null,
|
|
458
|
+
transcript: [...transcript.slice(0, -1), { ...last, content: last.content + text }],
|
|
459
|
+
status: "speaking"
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
this.pendingAssistant = { turnId };
|
|
463
|
+
return {
|
|
464
|
+
interimTranscript: null,
|
|
465
|
+
transcript: [
|
|
466
|
+
...transcript,
|
|
467
|
+
{
|
|
468
|
+
role: "assistant",
|
|
469
|
+
content: text,
|
|
470
|
+
timestamp: Date.now(),
|
|
471
|
+
partial: true,
|
|
472
|
+
...turnId ? { turnId } : {}
|
|
473
|
+
}
|
|
474
|
+
],
|
|
475
|
+
status: "speaking"
|
|
476
|
+
};
|
|
317
477
|
}
|
|
318
478
|
clearPlayback() {
|
|
319
479
|
this.playbackRevision += 1;
|
|
@@ -348,32 +508,61 @@ var VoiceClient = class {
|
|
|
348
508
|
return;
|
|
349
509
|
}
|
|
350
510
|
switch (msg.type) {
|
|
351
|
-
case "session_config":
|
|
511
|
+
case "session_config": {
|
|
352
512
|
if (msg.interruptionMode === "none" || msg.interruptionMode === "cancel" || msg.interruptionMode === "barge-in") {
|
|
353
513
|
this.update({ interruptionMode: msg.interruptionMode });
|
|
354
514
|
}
|
|
515
|
+
const sessionId = readString(msg.sessionId);
|
|
516
|
+
const conversationId = readString(msg.conversationId);
|
|
517
|
+
if (sessionId && conversationId) {
|
|
518
|
+
const session = { sessionId, conversationId };
|
|
519
|
+
this.update({ session });
|
|
520
|
+
this.options.onSession?.(session);
|
|
521
|
+
}
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
case "artifact":
|
|
525
|
+
this.applyArtifactMessage(msg);
|
|
355
526
|
break;
|
|
356
527
|
case "transcript_interim":
|
|
357
528
|
this.update({ interimTranscript: typeof msg.text === "string" ? msg.text || null : null });
|
|
358
529
|
break;
|
|
359
|
-
case "
|
|
530
|
+
case "transcript_partial": {
|
|
531
|
+
if (msg.role !== "assistant" || typeof msg.text !== "string" || !msg.text) break;
|
|
532
|
+
this.activeTurnId = readTurnId(msg) ?? this.activeTurnId;
|
|
533
|
+
if (this.awaitingClear || this.stoppedResponse) break;
|
|
534
|
+
this.responseEnded = false;
|
|
535
|
+
this.update(this.appendClauseCaption(msg.text, readTurnId(msg)));
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
case "transcript_final": {
|
|
360
539
|
if (typeof msg.text !== "string" || msg.role !== "user" && msg.role !== "assistant") break;
|
|
361
|
-
|
|
540
|
+
const role = msg.role;
|
|
541
|
+
const text = msg.text;
|
|
542
|
+
if (role === "assistant" && (this.awaitingClear || this.stoppedResponse)) break;
|
|
543
|
+
const turnId = readTurnId(msg);
|
|
544
|
+
if (turnId !== null) this.activeTurnId = turnId;
|
|
545
|
+
if (role === "assistant" && turnId !== null && turnId === this.committedAssistantTurnId)
|
|
546
|
+
break;
|
|
362
547
|
this.responseEnded = false;
|
|
548
|
+
const transcript = this.snapshot.transcript;
|
|
549
|
+
const last = transcript[transcript.length - 1];
|
|
550
|
+
const supersedes = role === "assistant" && this.pendingAssistant !== null && last?.role === "assistant" && (turnId === null || this.pendingAssistant.turnId === null || this.pendingAssistant.turnId === turnId);
|
|
551
|
+
this.pendingAssistant = null;
|
|
552
|
+
const entry = {
|
|
553
|
+
role,
|
|
554
|
+
content: text,
|
|
555
|
+
// INVARIANT: reuse the caption's timestamp so a keyed list does not remount the bubble.
|
|
556
|
+
timestamp: supersedes && last ? last.timestamp : Date.now(),
|
|
557
|
+
...turnId ? { turnId } : {}
|
|
558
|
+
};
|
|
363
559
|
this.update({
|
|
364
560
|
interimTranscript: null,
|
|
365
|
-
transcript: [
|
|
366
|
-
|
|
367
|
-
{
|
|
368
|
-
role: msg.role,
|
|
369
|
-
content: msg.text,
|
|
370
|
-
timestamp: Date.now(),
|
|
371
|
-
...typeof msg.turnId === "string" && msg.turnId ? { turnId: msg.turnId } : {}
|
|
372
|
-
}
|
|
373
|
-
],
|
|
374
|
-
status: this.awaitingClear ? this.snapshot.status : msg.role === "user" ? "thinking" : "speaking"
|
|
561
|
+
transcript: supersedes ? [...transcript.slice(0, -1), entry] : [...transcript, entry],
|
|
562
|
+
status: this.awaitingClear ? this.snapshot.status : role === "user" ? "thinking" : "speaking"
|
|
375
563
|
});
|
|
376
564
|
break;
|
|
565
|
+
}
|
|
377
566
|
case "audio_end": {
|
|
378
567
|
this.responseEnded = true;
|
|
379
568
|
if (this.stoppedResponse) {
|
|
@@ -390,10 +579,12 @@ var VoiceClient = class {
|
|
|
390
579
|
break;
|
|
391
580
|
}
|
|
392
581
|
case "audio_clear":
|
|
582
|
+
this.suppressedArtifactTurnId = this.activeTurnId ?? this.suppressedArtifactTurnId;
|
|
393
583
|
this.clearPlayback();
|
|
394
584
|
this.awaitingClear = false;
|
|
395
585
|
this.stoppedResponse = false;
|
|
396
586
|
this.responseEnded = true;
|
|
587
|
+
this.commitClauseCaptions();
|
|
397
588
|
this.setStatus("listening");
|
|
398
589
|
break;
|
|
399
590
|
case "metrics": {
|
|
@@ -406,8 +597,11 @@ var VoiceClient = class {
|
|
|
406
597
|
// @snake-case-ok: Existing voice wire contract.
|
|
407
598
|
firstAudioMs: number(msg.first_audio_ms),
|
|
408
599
|
// @snake-case-ok: Existing voice wire contract.
|
|
409
|
-
totalMs: number(msg.total_ms)
|
|
600
|
+
totalMs: number(msg.total_ms),
|
|
601
|
+
// @snake-case-ok: Existing voice wire contract.
|
|
602
|
+
firstSynthesisMs: number(msg.first_synthesis_ms),
|
|
410
603
|
// @snake-case-ok: Existing voice wire contract.
|
|
604
|
+
incremental: msg.incremental === true
|
|
411
605
|
}
|
|
412
606
|
});
|
|
413
607
|
break;
|
|
@@ -469,6 +663,7 @@ function createPersonaVoiceProvider(options) {
|
|
|
469
663
|
const transcriptCallbacks = /* @__PURE__ */ new Set();
|
|
470
664
|
const metricsCallbacks = /* @__PURE__ */ new Set();
|
|
471
665
|
const levelCallbacks = /* @__PURE__ */ new Set();
|
|
666
|
+
const artifactCallbacks = /* @__PURE__ */ new Set();
|
|
472
667
|
let previous = client.getSnapshot();
|
|
473
668
|
let unsubscribe = null;
|
|
474
669
|
const onSnapshot = () => {
|
|
@@ -492,12 +687,18 @@ function createPersonaVoiceProvider(options) {
|
|
|
492
687
|
for (const callback of transcriptCallbacks)
|
|
493
688
|
callback("user", snapshot.interimTranscript, false);
|
|
494
689
|
}
|
|
495
|
-
|
|
690
|
+
snapshot.artifacts.forEach((artifact, index) => {
|
|
691
|
+
if (artifact === before.artifacts[index]) return;
|
|
692
|
+
for (const callback of artifactCallbacks) callback(artifact);
|
|
693
|
+
});
|
|
694
|
+
snapshot.transcript.forEach((entry, index) => {
|
|
695
|
+
if (entry === before.transcript[index]) return;
|
|
696
|
+
const isFinal = entry.partial !== true;
|
|
496
697
|
for (const callback of transcriptCallbacks) {
|
|
497
|
-
if (entry.turnId) callback(entry.role, entry.content,
|
|
498
|
-
else callback(entry.role, entry.content,
|
|
698
|
+
if (entry.turnId) callback(entry.role, entry.content, isFinal, { turnId: entry.turnId });
|
|
699
|
+
else callback(entry.role, entry.content, isFinal);
|
|
499
700
|
}
|
|
500
|
-
}
|
|
701
|
+
});
|
|
501
702
|
};
|
|
502
703
|
const subscribe = () => {
|
|
503
704
|
if (unsubscribe) return;
|
|
@@ -518,6 +719,7 @@ function createPersonaVoiceProvider(options) {
|
|
|
518
719
|
transcriptCallbacks.clear();
|
|
519
720
|
metricsCallbacks.clear();
|
|
520
721
|
levelCallbacks.clear();
|
|
722
|
+
artifactCallbacks.clear();
|
|
521
723
|
},
|
|
522
724
|
startListening: async () => {
|
|
523
725
|
subscribe();
|
|
@@ -544,6 +746,9 @@ function createPersonaVoiceProvider(options) {
|
|
|
544
746
|
onLevel: (callback) => {
|
|
545
747
|
levelCallbacks.add(callback);
|
|
546
748
|
},
|
|
749
|
+
onArtifact: (callback) => {
|
|
750
|
+
artifactCallbacks.add(callback);
|
|
751
|
+
},
|
|
547
752
|
getInterruptionMode: () => client.getSnapshot().interruptionMode,
|
|
548
753
|
isBargeInActive: () => !["idle", "error"].includes(client.getSnapshot().status),
|
|
549
754
|
deactivateBargeIn: async () => {
|
|
@@ -552,6 +757,52 @@ function createPersonaVoiceProvider(options) {
|
|
|
552
757
|
stopPlayback: client.stopPlayback
|
|
553
758
|
};
|
|
554
759
|
}
|
|
760
|
+
function toManualUpsert(artifact) {
|
|
761
|
+
const title = artifact.title ?? artifact.file?.path;
|
|
762
|
+
const transcript = artifact.status === "complete";
|
|
763
|
+
if (artifact.artifactType === "component") {
|
|
764
|
+
if (!artifact.component) return null;
|
|
765
|
+
return {
|
|
766
|
+
id: artifact.id,
|
|
767
|
+
artifactType: "component",
|
|
768
|
+
component: artifact.component,
|
|
769
|
+
...title ? { title } : {},
|
|
770
|
+
...artifact.props ? { props: artifact.props } : {},
|
|
771
|
+
transcript
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
return {
|
|
775
|
+
id: artifact.id,
|
|
776
|
+
artifactType: "markdown",
|
|
777
|
+
content: artifact.content,
|
|
778
|
+
...title ? { title } : {},
|
|
779
|
+
...artifact.file ? {
|
|
780
|
+
file: {
|
|
781
|
+
path: artifact.file.path,
|
|
782
|
+
mimeType: artifact.file.mimeType,
|
|
783
|
+
...artifact.file.language ? { language: artifact.file.language } : {}
|
|
784
|
+
}
|
|
785
|
+
} : {},
|
|
786
|
+
transcript
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
function bindVoiceArtifactsToPersona(provider, widget, options) {
|
|
790
|
+
let released = false;
|
|
791
|
+
let shown = false;
|
|
792
|
+
provider.onArtifact((artifact) => {
|
|
793
|
+
if (released) return;
|
|
794
|
+
const manual = toManualUpsert(artifact);
|
|
795
|
+
if (!manual) return;
|
|
796
|
+
widget.upsertArtifact(manual);
|
|
797
|
+
if (shown || options?.showOnFirst === false) return;
|
|
798
|
+
shown = true;
|
|
799
|
+
widget.showArtifacts();
|
|
800
|
+
});
|
|
801
|
+
return () => {
|
|
802
|
+
released = true;
|
|
803
|
+
};
|
|
804
|
+
}
|
|
555
805
|
export {
|
|
806
|
+
bindVoiceArtifactsToPersona,
|
|
556
807
|
createPersonaVoiceProvider
|
|
557
808
|
};
|