@aginies/webuikit 0.2.0 → 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/dist/index.cjs CHANGED
@@ -53,6 +53,7 @@ __export(src_exports, {
53
53
  getPausedExecution: () => getPausedExecution,
54
54
  init: () => init,
55
55
  initialValues: () => initialValues,
56
+ isVoiceSupported: () => isVoiceSupported,
56
57
  listPausedExecutions: () => listPausedExecutions,
57
58
  outputOf: () => outputOf,
58
59
  parseFieldValue: () => parseFieldValue,
@@ -66,7 +67,8 @@ __export(src_exports, {
66
67
  useAginies: () => useAginies,
67
68
  useApproval: () => useApproval,
68
69
  useChat: () => useChat,
69
- useModule: () => useModule
70
+ useModule: () => useModule,
71
+ useVoice: () => useVoice
70
72
  });
71
73
  module.exports = __toCommonJS(src_exports);
72
74
 
@@ -85,6 +87,7 @@ var trimSlash = (s) => s.replace(/\/+$/, "");
85
87
  var AginiesClient = class {
86
88
  baseUrl;
87
89
  token;
90
+ hosted;
88
91
  locale;
89
92
  fetchImpl;
90
93
  state = { status: "idle" };
@@ -93,9 +96,12 @@ var AginiesClient = class {
93
96
  session = null;
94
97
  constructor(config) {
95
98
  if (!config?.baseUrl) throw new AginiesError("baseUrl is required", 0, "MISSING_BASE_URL");
96
- if (!config?.token) throw new AginiesError("token is required", 0, "MISSING_TOKEN");
99
+ if (!config?.token && !config?.hosted) {
100
+ throw new AginiesError("token is required", 0, "MISSING_TOKEN");
101
+ }
97
102
  this.baseUrl = trimSlash(config.baseUrl);
98
- this.token = config.token;
103
+ this.token = config.token ?? null;
104
+ this.hosted = config.hosted === true;
99
105
  this.locale = config.locale ?? detectLocale();
100
106
  this.fetchImpl = config.fetch ?? ((...args) => fetch(...args));
101
107
  }
@@ -122,7 +128,7 @@ var AginiesClient = class {
122
128
  const res = await this.fetchImpl(`${this.baseUrl}/api/ui/activate`, {
123
129
  method: "POST",
124
130
  headers: { "Content-Type": "application/json" },
125
- body: JSON.stringify({ token: this.token })
131
+ body: JSON.stringify(this.hosted ? { hosted: true } : { token: this.token })
126
132
  });
127
133
  if (!res.ok) {
128
134
  const body2 = await safeJson(res);
@@ -197,6 +203,30 @@ var AginiesClient = class {
197
203
  this.assertActive();
198
204
  return this.request(path2, init2);
199
205
  }
206
+ /** Sends a recording to the platform's transcription model. */
207
+ async transcribe(identifier, audio, filename = "turn.webm", language) {
208
+ this.assertActive();
209
+ const form = new FormData();
210
+ form.append("file", audio, filename);
211
+ if (language) form.append("language", language);
212
+ const res = await this.request(`/api/chat/${encodeURIComponent(identifier)}/voice/transcribe`, {
213
+ method: "POST",
214
+ body: form
215
+ });
216
+ if (!res.ok) throw await voiceError(res);
217
+ return await res.json();
218
+ }
219
+ /** Audio for a reply from the platform's synthesis endpoint. */
220
+ async speak(identifier, text) {
221
+ this.assertActive();
222
+ const res = await this.request(`/api/chat/${encodeURIComponent(identifier)}/voice/speak`, {
223
+ method: "POST",
224
+ headers: { "Content-Type": "application/json" },
225
+ body: JSON.stringify({ text })
226
+ });
227
+ if (!res.ok) throw await voiceError(res);
228
+ return res.blob();
229
+ }
200
230
  /** Hosted-chat configuration. Resolves to an auth requirement instead of throwing on 401. */
201
231
  async getChat(identifier) {
202
232
  this.assertActive();
@@ -286,6 +316,15 @@ function decodeFrame(frame) {
286
316
  }
287
317
  return null;
288
318
  }
319
+ async function voiceError(res) {
320
+ const body = await safeJson(res);
321
+ if (res.status === 401 && body && typeof body.authRequired === "string") {
322
+ return new AginiesError("Authentication required", 401, "AUTH_REQUIRED");
323
+ }
324
+ if (res.status === 503)
325
+ return new AginiesError("Voice is not available", 503, "VOICE_UNAVAILABLE");
326
+ return new AginiesError(body?.error || `HTTP ${res.status}`, res.status);
327
+ }
289
328
  async function safeJson(res) {
290
329
  try {
291
330
  return await res.json();
@@ -584,6 +623,35 @@ var STRINGS = {
584
623
  },
585
624
  ssoButton: { tr: "Kurumsal giri\u015F", en: "Sign in" }
586
625
  },
626
+ voice: {
627
+ talk: { tr: "Konu\u015Fmak i\xE7in dokunun", en: "Tap to talk" },
628
+ stopTalking: { tr: "Bitirmek i\xE7in dokunun", en: "Tap when done" },
629
+ dictate: { tr: "Sesle yaz", en: "Dictate" },
630
+ handsFree: { tr: "Sesli sohbet", en: "Voice conversation" },
631
+ exit: { tr: "Sesli sohbetten \xE7\u0131k", en: "Leave voice conversation" },
632
+ listening: { tr: "Dinliyor", en: "Listening" },
633
+ transcribing: { tr: "Yaz\u0131ya d\xF6k\xFCl\xFCyor", en: "Transcribing" },
634
+ thinking: { tr: "Yan\u0131t haz\u0131rlan\u0131yor", en: "Working on it" },
635
+ speaking: { tr: "Konu\u015Fuyor", en: "Speaking" },
636
+ idle: { tr: "Haz\u0131r", en: "Ready" },
637
+ interrupt: { tr: "S\xF6z\xFCn\xFC kesmek i\xE7in dokunun", en: "Tap to interrupt" },
638
+ micDenied: {
639
+ tr: "Mikrofon izni verilmedi. Taray\u0131c\u0131 ayarlar\u0131ndan izin verin.",
640
+ en: "Microphone access was denied. Allow it in your browser settings."
641
+ },
642
+ unsupported: {
643
+ tr: "Bu taray\u0131c\u0131 ses kayd\u0131n\u0131 desteklemiyor.",
644
+ en: "This browser cannot record audio."
645
+ },
646
+ unavailable: {
647
+ tr: "Ses \xF6zelli\u011Fi bu sohbet i\xE7in a\xE7\u0131k de\u011Fil.",
648
+ en: "Voice is not enabled for this chat."
649
+ },
650
+ error: {
651
+ tr: "Ses i\u015Flenemedi. L\xFCtfen tekrar deneyin.",
652
+ en: "Voice could not be processed. Please try again."
653
+ }
654
+ },
587
655
  run: {
588
656
  submit: { tr: "\xC7al\u0131\u015Ft\u0131r", en: "Run" },
589
657
  cancel: { tr: "\u0130ptal", en: "Cancel" },
@@ -1034,7 +1102,7 @@ function renderField(f, value, set, disabled) {
1034
1102
  }
1035
1103
 
1036
1104
  // src/chat/chat-widget.tsx
1037
- var import_react5 = require("react");
1105
+ var import_react6 = require("react");
1038
1106
 
1039
1107
  // src/chat/markdown.tsx
1040
1108
  var import_react4 = require("react");
@@ -1344,20 +1412,258 @@ function Pie({ labels, data }) {
1344
1412
  ] });
1345
1413
  }
1346
1414
 
1415
+ // src/chat/voice.ts
1416
+ var import_react5 = require("react");
1417
+ var MIME_CANDIDATES = [
1418
+ "audio/webm;codecs=opus",
1419
+ "audio/webm",
1420
+ "audio/mp4",
1421
+ "audio/ogg;codecs=opus"
1422
+ ];
1423
+ var MIN_CLIP_BYTES = 800;
1424
+ var SPEECH_RMS = 0.02;
1425
+ function isVoiceSupported() {
1426
+ return typeof window !== "undefined" && typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && typeof MediaRecorder !== "undefined";
1427
+ }
1428
+ function useVoice({
1429
+ identifier,
1430
+ onTranscript,
1431
+ language,
1432
+ silenceMs = 1400,
1433
+ maxMs = 6e4
1434
+ }) {
1435
+ const { client, t } = useAginies();
1436
+ const supported = isVoiceSupported();
1437
+ const [state, setState] = (0, import_react5.useState)(supported ? "idle" : "unsupported");
1438
+ const [error, setError] = (0, import_react5.useState)(null);
1439
+ const [level, setLevel] = (0, import_react5.useState)(0);
1440
+ const recorderRef = (0, import_react5.useRef)(null);
1441
+ const streamRef = (0, import_react5.useRef)(null);
1442
+ const chunksRef = (0, import_react5.useRef)([]);
1443
+ const discardRef = (0, import_react5.useRef)(false);
1444
+ const timersRef = (0, import_react5.useRef)([]);
1445
+ const meterRef = (0, import_react5.useRef)(null);
1446
+ const audioRef = (0, import_react5.useRef)(null);
1447
+ const audioUrlRef = (0, import_react5.useRef)(null);
1448
+ const startingRef = (0, import_react5.useRef)(false);
1449
+ const onTranscriptRef = (0, import_react5.useRef)(onTranscript);
1450
+ onTranscriptRef.current = onTranscript;
1451
+ const clearTimers = (0, import_react5.useCallback)(() => {
1452
+ for (const id of timersRef.current) window.clearTimeout(id);
1453
+ timersRef.current = [];
1454
+ if (meterRef.current) {
1455
+ window.clearInterval(meterRef.current.interval);
1456
+ void meterRef.current.ctx.close().catch(() => {
1457
+ });
1458
+ meterRef.current = null;
1459
+ }
1460
+ setLevel(0);
1461
+ }, []);
1462
+ const releaseStream = (0, import_react5.useCallback)(() => {
1463
+ for (const track of streamRef.current?.getTracks() ?? []) track.stop();
1464
+ streamRef.current = null;
1465
+ }, []);
1466
+ const stopSpeaking = (0, import_react5.useCallback)(() => {
1467
+ const audio = audioRef.current;
1468
+ if (audio) {
1469
+ audio.pause();
1470
+ audio.src = "";
1471
+ audioRef.current = null;
1472
+ }
1473
+ if (audioUrlRef.current) {
1474
+ URL.revokeObjectURL(audioUrlRef.current);
1475
+ audioUrlRef.current = null;
1476
+ }
1477
+ setState((s) => s === "speaking" ? "idle" : s);
1478
+ }, []);
1479
+ const finish = (0, import_react5.useCallback)((recorder) => {
1480
+ recorder.stop();
1481
+ }, []);
1482
+ const stopListening = (0, import_react5.useCallback)(() => {
1483
+ const recorder = recorderRef.current;
1484
+ if (!recorder || recorder.state === "inactive") return;
1485
+ discardRef.current = false;
1486
+ finish(recorder);
1487
+ }, [finish]);
1488
+ const cancelListening = (0, import_react5.useCallback)(() => {
1489
+ const recorder = recorderRef.current;
1490
+ if (!recorder || recorder.state === "inactive") return;
1491
+ discardRef.current = true;
1492
+ finish(recorder);
1493
+ }, [finish]);
1494
+ const startMeter = (0, import_react5.useCallback)(
1495
+ (stream, onSilence) => {
1496
+ const Ctx = typeof AudioContext !== "undefined" ? AudioContext : window.webkitAudioContext;
1497
+ if (!Ctx) return;
1498
+ try {
1499
+ const ctx = new Ctx();
1500
+ const analyser = ctx.createAnalyser();
1501
+ analyser.fftSize = 2048;
1502
+ ctx.createMediaStreamSource(stream).connect(analyser);
1503
+ const data = new Uint8Array(analyser.fftSize);
1504
+ let heard = false;
1505
+ let lastVoice = Date.now();
1506
+ const interval = window.setInterval(() => {
1507
+ analyser.getByteTimeDomainData(data);
1508
+ let sum = 0;
1509
+ for (const v of data) {
1510
+ const n = (v - 128) / 128;
1511
+ sum += n * n;
1512
+ }
1513
+ const rms = Math.sqrt(sum / data.length);
1514
+ setLevel(Math.min(1, rms * 6));
1515
+ if (rms > SPEECH_RMS) {
1516
+ heard = true;
1517
+ lastVoice = Date.now();
1518
+ } else if (heard && Date.now() - lastVoice > silenceMs) {
1519
+ onSilence();
1520
+ }
1521
+ }, 100);
1522
+ meterRef.current = { ctx, interval };
1523
+ } catch {
1524
+ }
1525
+ },
1526
+ [silenceMs]
1527
+ );
1528
+ const startListening = (0, import_react5.useCallback)(async () => {
1529
+ if (!supported) {
1530
+ setState("unsupported");
1531
+ return;
1532
+ }
1533
+ if (startingRef.current) return;
1534
+ if (recorderRef.current && recorderRef.current.state !== "inactive") return;
1535
+ startingRef.current = true;
1536
+ stopSpeaking();
1537
+ setError(null);
1538
+ let stream;
1539
+ try {
1540
+ stream = await navigator.mediaDevices.getUserMedia({ audio: true });
1541
+ } catch {
1542
+ startingRef.current = false;
1543
+ setState("denied");
1544
+ setError(t("voice", "micDenied"));
1545
+ return;
1546
+ }
1547
+ startingRef.current = false;
1548
+ streamRef.current = stream;
1549
+ const mimeType = MIME_CANDIDATES.find((m) => MediaRecorder.isTypeSupported?.(m));
1550
+ const recorder = new MediaRecorder(stream, mimeType ? { mimeType } : void 0);
1551
+ recorderRef.current = recorder;
1552
+ chunksRef.current = [];
1553
+ discardRef.current = false;
1554
+ recorder.ondataavailable = (e) => {
1555
+ if (e.data && e.data.size > 0) chunksRef.current.push(e.data);
1556
+ };
1557
+ recorder.onstop = () => {
1558
+ clearTimers();
1559
+ releaseStream();
1560
+ recorderRef.current = null;
1561
+ const type = recorder.mimeType || mimeType || "audio/webm";
1562
+ const clip = new Blob(chunksRef.current, { type });
1563
+ chunksRef.current = [];
1564
+ if (discardRef.current || clip.size < MIN_CLIP_BYTES) {
1565
+ setState("idle");
1566
+ return;
1567
+ }
1568
+ setState("transcribing");
1569
+ const ext = type.includes("mp4") ? "m4a" : type.includes("ogg") ? "ogg" : "webm";
1570
+ client.transcribe(identifier, clip, `turn.${ext}`, language).then((result) => {
1571
+ setState("idle");
1572
+ if (result.transcript) onTranscriptRef.current(result.transcript);
1573
+ }).catch((err) => {
1574
+ setState("idle");
1575
+ setError(
1576
+ err instanceof AginiesError && err.code === "VOICE_UNAVAILABLE" ? t("voice", "unavailable") : t("voice", "error")
1577
+ );
1578
+ });
1579
+ };
1580
+ recorder.start(250);
1581
+ setState("listening");
1582
+ startMeter(stream, () => stopListening());
1583
+ timersRef.current.push(window.setTimeout(() => stopListening(), maxMs));
1584
+ }, [
1585
+ supported,
1586
+ stopSpeaking,
1587
+ t,
1588
+ client,
1589
+ identifier,
1590
+ language,
1591
+ maxMs,
1592
+ stopListening,
1593
+ clearTimers,
1594
+ releaseStream,
1595
+ startMeter
1596
+ ]);
1597
+ const speak = (0, import_react5.useCallback)(
1598
+ async (text) => {
1599
+ stopSpeaking();
1600
+ setError(null);
1601
+ setState("speaking");
1602
+ try {
1603
+ const blob = await client.speak(identifier, text);
1604
+ const url = URL.createObjectURL(blob);
1605
+ audioUrlRef.current = url;
1606
+ const audio = new Audio(url);
1607
+ audioRef.current = audio;
1608
+ await new Promise((resolve) => {
1609
+ audio.onended = () => resolve();
1610
+ audio.onerror = () => resolve();
1611
+ audio.onpause = () => resolve();
1612
+ audio.play().catch(() => resolve());
1613
+ });
1614
+ } catch (err) {
1615
+ setError(
1616
+ err instanceof AginiesError && err.code === "VOICE_UNAVAILABLE" ? t("voice", "unavailable") : t("voice", "error")
1617
+ );
1618
+ } finally {
1619
+ if (audioUrlRef.current) URL.revokeObjectURL(audioUrlRef.current);
1620
+ audioUrlRef.current = null;
1621
+ audioRef.current = null;
1622
+ setState((s) => s === "speaking" ? "idle" : s);
1623
+ }
1624
+ },
1625
+ [client, identifier, stopSpeaking, t]
1626
+ );
1627
+ (0, import_react5.useEffect)(
1628
+ () => () => {
1629
+ discardRef.current = true;
1630
+ if (recorderRef.current && recorderRef.current.state !== "inactive")
1631
+ recorderRef.current.stop();
1632
+ clearTimers();
1633
+ releaseStream();
1634
+ const audio = audioRef.current;
1635
+ if (audio) audio.pause();
1636
+ if (audioUrlRef.current) URL.revokeObjectURL(audioUrlRef.current);
1637
+ },
1638
+ [clearTimers, releaseStream]
1639
+ );
1640
+ return {
1641
+ state,
1642
+ error,
1643
+ supported,
1644
+ level,
1645
+ startListening,
1646
+ stopListening,
1647
+ cancelListening,
1648
+ speak,
1649
+ stopSpeaking
1650
+ };
1651
+ }
1652
+
1347
1653
  // src/chat/chat-widget.tsx
1348
1654
  var import_jsx_runtime6 = require("react/jsx-runtime");
1349
1655
  var ATTACHMENT_LIMITS = { maxFiles: 5, maxBytes: 10 * 1024 * 1024 };
1350
1656
  function useChat(identifier, enabled = true) {
1351
1657
  const { client, t } = useAginies();
1352
- const [config, setConfig] = (0, import_react5.useState)(null);
1353
- const [authNeed, setAuthNeed] = (0, import_react5.useState)(null);
1354
- const [authTitle, setAuthTitle] = (0, import_react5.useState)();
1355
- const [loadError, setLoadError] = (0, import_react5.useState)(null);
1356
- const [messages, setMessages] = (0, import_react5.useState)([]);
1357
- const [busy, setBusy] = (0, import_react5.useState)(false);
1358
- const [conversationId] = (0, import_react5.useState)(() => randomId());
1359
- const abortRef = (0, import_react5.useRef)(null);
1360
- const load = (0, import_react5.useCallback)(async () => {
1658
+ const [config, setConfig] = (0, import_react6.useState)(null);
1659
+ const [authNeed, setAuthNeed] = (0, import_react6.useState)(null);
1660
+ const [authTitle, setAuthTitle] = (0, import_react6.useState)();
1661
+ const [loadError, setLoadError] = (0, import_react6.useState)(null);
1662
+ const [messages, setMessages] = (0, import_react6.useState)([]);
1663
+ const [busy, setBusy] = (0, import_react6.useState)(false);
1664
+ const [conversationId] = (0, import_react6.useState)(() => randomId());
1665
+ const abortRef = (0, import_react6.useRef)(null);
1666
+ const load = (0, import_react6.useCallback)(async () => {
1361
1667
  setLoadError(null);
1362
1668
  try {
1363
1669
  const res = await client.getChat(identifier);
@@ -1374,11 +1680,11 @@ function useChat(identifier, enabled = true) {
1374
1680
  );
1375
1681
  }
1376
1682
  }, [client, identifier, t]);
1377
- (0, import_react5.useEffect)(() => {
1683
+ (0, import_react6.useEffect)(() => {
1378
1684
  if (enabled) void load();
1379
1685
  }, [load, enabled]);
1380
1686
  const chatPath = `/api/chat/${encodeURIComponent(identifier)}`;
1381
- const authenticate = (0, import_react5.useCallback)(
1687
+ const authenticate = (0, import_react6.useCallback)(
1382
1688
  async ({ password }) => {
1383
1689
  const res = await client.fetchRaw(chatPath, {
1384
1690
  method: "POST",
@@ -1393,7 +1699,7 @@ function useChat(identifier, enabled = true) {
1393
1699
  },
1394
1700
  [client, chatPath, conversationId, load]
1395
1701
  );
1396
- const requestCode = (0, import_react5.useCallback)(
1702
+ const requestCode = (0, import_react6.useCallback)(
1397
1703
  async (email) => {
1398
1704
  const res = await client.fetchRaw(`${chatPath}/otp`, {
1399
1705
  method: "POST",
@@ -1405,7 +1711,7 @@ function useChat(identifier, enabled = true) {
1405
1711
  },
1406
1712
  [client, chatPath]
1407
1713
  );
1408
- const verifyCode = (0, import_react5.useCallback)(
1714
+ const verifyCode = (0, import_react6.useCallback)(
1409
1715
  async (email, otp) => {
1410
1716
  const res = await client.fetchRaw(`${chatPath}/otp`, {
1411
1717
  method: "PUT",
@@ -1420,7 +1726,7 @@ function useChat(identifier, enabled = true) {
1420
1726
  },
1421
1727
  [client, chatPath, load]
1422
1728
  );
1423
- const stop = (0, import_react5.useCallback)(() => {
1729
+ const stop = (0, import_react6.useCallback)(() => {
1424
1730
  abortRef.current?.abort();
1425
1731
  abortRef.current = null;
1426
1732
  setBusy(false);
@@ -1433,7 +1739,7 @@ function useChat(identifier, enabled = true) {
1433
1739
  ];
1434
1740
  });
1435
1741
  }, [t]);
1436
- const send = (0, import_react5.useCallback)(
1742
+ const send = (0, import_react6.useCallback)(
1437
1743
  async (text, files = []) => {
1438
1744
  const input = text.trim();
1439
1745
  if (!input && files.length === 0 || busy) return;
@@ -1525,15 +1831,36 @@ function ChatWidget({
1525
1831
  launcherLabel,
1526
1832
  defaultOpen = false,
1527
1833
  theme = "auto",
1834
+ voice = true,
1528
1835
  className
1529
1836
  }) {
1530
- const [open, setOpen] = (0, import_react5.useState)(defaultOpen || mode !== "bubble");
1837
+ const [open, setOpen] = (0, import_react6.useState)(defaultOpen || mode !== "bubble");
1531
1838
  const { t } = useAginies();
1532
1839
  const enabled = useModule("chat");
1533
1840
  const chat = useChat(identifier, enabled);
1841
+ const [voiceMode, setVoiceMode] = (0, import_react6.useState)(false);
1842
+ const [dictation, setDictation] = (0, import_react6.useState)(null);
1843
+ const voiceModeRef = (0, import_react6.useRef)(voiceMode);
1844
+ voiceModeRef.current = voiceMode;
1845
+ const sendRef = (0, import_react6.useRef)(chat.send);
1846
+ sendRef.current = chat.send;
1847
+ const voiceControls = useVoice({
1848
+ identifier,
1849
+ onTranscript: (text) => {
1850
+ if (voiceModeRef.current) void sendRef.current(text);
1851
+ else setDictation({ id: Date.now(), text });
1852
+ }
1853
+ });
1854
+ const capabilities = chat.config?.voice;
1855
+ const voiceAvailable = voice && voiceControls.supported && capabilities?.stt === true;
1534
1856
  const title = chat.config?.title ?? chat.authTitle ?? launcherLabel ?? "Aginies";
1535
1857
  const themeClass = theme === "auto" ? void 0 : theme === "dark" ? "dark" : "light";
1536
1858
  if (!enabled) return null;
1859
+ const leaveVoiceMode = () => {
1860
+ voiceControls.cancelListening();
1861
+ voiceControls.stopSpeaking();
1862
+ setVoiceMode(false);
1863
+ };
1537
1864
  const panel = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1538
1865
  "section",
1539
1866
  {
@@ -1571,7 +1898,16 @@ function ChatWidget({
1571
1898
  onRequestCode: chat.requestCode,
1572
1899
  onVerifyCode: chat.verifyCode
1573
1900
  }
1574
- ) : !chat.config ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agi-chat__state", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Spinner, { label: t("common", "loading") }) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1901
+ ) : !chat.config ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agi-chat__state", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Spinner, { label: t("common", "loading") }) }) : voiceMode ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1902
+ VoiceConversation,
1903
+ {
1904
+ messages: chat.messages,
1905
+ busy: chat.busy,
1906
+ voice: voiceControls,
1907
+ speakReplies: capabilities?.tts === true,
1908
+ onExit: leaveVoiceMode
1909
+ }
1910
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1575
1911
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1576
1912
  MessageList,
1577
1913
  {
@@ -1580,7 +1916,22 @@ function ChatWidget({
1580
1916
  onAction: (action) => void chat.send(action)
1581
1917
  }
1582
1918
  ),
1583
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Composer, { busy: chat.busy, onSend: chat.send, onStop: chat.stop })
1919
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1920
+ Composer,
1921
+ {
1922
+ busy: chat.busy,
1923
+ onSend: chat.send,
1924
+ onStop: chat.stop,
1925
+ dictation,
1926
+ voice: voiceAvailable ? {
1927
+ state: voiceControls.state,
1928
+ error: voiceControls.error,
1929
+ start: voiceControls.startListening,
1930
+ stop: voiceControls.stopListening,
1931
+ onHandsFree: () => setVoiceMode(true)
1932
+ } : void 0
1933
+ }
1934
+ )
1584
1935
  ] }),
1585
1936
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("footer", { className: "agi-chat__foot", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { href: "https://www.aginies.com", target: "_blank", rel: "noreferrer", children: t("chat", "poweredBy") }) })
1586
1937
  ]
@@ -1635,53 +1986,57 @@ function MessageList({
1635
1986
  onAction
1636
1987
  }) {
1637
1988
  const { t } = useAginies();
1638
- const endRef = (0, import_react5.useRef)(null);
1639
- (0, import_react5.useEffect)(() => {
1989
+ const endRef = (0, import_react6.useRef)(null);
1990
+ (0, import_react6.useEffect)(() => {
1640
1991
  endRef.current?.scrollIntoView?.({ block: "end" });
1641
1992
  }, []);
1642
- return (
1643
- // biome-ignore lint/a11y/useSemanticElements: no native element conveys a live log
1644
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agi-chat__messages", role: "log", "aria-live": "polite", children: [
1645
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agi-msg agi-msg--assistant", children: [
1646
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agi-msg__who", children: t("chat", "assistant") }),
1647
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agi-msg__body", children: welcome })
1648
- ] }),
1649
- messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1650
- "div",
1651
- {
1652
- className: cx("agi-msg", `agi-msg--${m.role}`, m.error && "agi-msg--error"),
1653
- children: [
1654
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agi-msg__who", children: m.role === "user" ? t("chat", "you") : t("chat", "assistant") }),
1655
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agi-msg__body", children: [
1656
- m.structured ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StructuredUI, { response: m.structured, onAction }) : m.content ? m.role === "assistant" && !m.error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Markdown, { className: "agi-md", text: m.content }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: m.content }) : m.streaming ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "agi-msg__thinking", children: [
1657
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Spinner, { label: t("chat", "thinking") }),
1658
- " ",
1659
- t("chat", "thinking")
1660
- ] }) : null,
1661
- m.attachments && m.attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { className: "agi-msg__files", "aria-label": t("chat", "attachments"), children: m.attachments.map((file, n) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("li", { className: "agi-file", children: [
1662
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FileGlyph, {}),
1663
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agi-file__name", children: file.name }),
1664
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agi-file__size", children: formatBytes(file.size) })
1665
- ] }, n)) })
1666
- ] })
1667
- ]
1668
- },
1669
- m.id
1670
- )),
1671
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: endRef })
1672
- ] })
1673
- );
1993
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agi-chat__messages", role: "log", "aria-live": "polite", children: [
1994
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agi-msg agi-msg--assistant", children: [
1995
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agi-msg__who", children: t("chat", "assistant") }),
1996
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agi-msg__body", children: welcome })
1997
+ ] }),
1998
+ messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1999
+ "div",
2000
+ {
2001
+ className: cx("agi-msg", `agi-msg--${m.role}`, m.error && "agi-msg--error"),
2002
+ children: [
2003
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agi-msg__who", children: m.role === "user" ? t("chat", "you") : t("chat", "assistant") }),
2004
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agi-msg__body", children: [
2005
+ m.structured ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StructuredUI, { response: m.structured, onAction }) : m.content ? m.role === "assistant" && !m.error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Markdown, { className: "agi-md", text: m.content }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: m.content }) : m.streaming ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "agi-msg__thinking", children: [
2006
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Spinner, { label: t("chat", "thinking") }),
2007
+ " ",
2008
+ t("chat", "thinking")
2009
+ ] }) : null,
2010
+ m.attachments && m.attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { className: "agi-msg__files", "aria-label": t("chat", "attachments"), children: m.attachments.map((file, n) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("li", { className: "agi-file", children: [
2011
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FileGlyph, {}),
2012
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agi-file__name", children: file.name }),
2013
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agi-file__size", children: formatBytes(file.size) })
2014
+ ] }, n)) })
2015
+ ] })
2016
+ ]
2017
+ },
2018
+ m.id
2019
+ )),
2020
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: endRef })
2021
+ ] });
1674
2022
  }
1675
2023
  function Composer({
1676
2024
  busy,
1677
2025
  onSend,
1678
- onStop
2026
+ onStop,
2027
+ voice,
2028
+ dictation
1679
2029
  }) {
1680
2030
  const { t } = useAginies();
1681
- const [value, setValue] = (0, import_react5.useState)("");
1682
- const [files, setFiles] = (0, import_react5.useState)([]);
1683
- const [fileError, setFileError] = (0, import_react5.useState)(null);
1684
- const fileInput = (0, import_react5.useRef)(null);
2031
+ const [value, setValue] = (0, import_react6.useState)("");
2032
+ const [files, setFiles] = (0, import_react6.useState)([]);
2033
+ const [fileError, setFileError] = (0, import_react6.useState)(null);
2034
+ const fileInput = (0, import_react6.useRef)(null);
2035
+ (0, import_react6.useEffect)(() => {
2036
+ if (dictation) setValue((v) => v.trim() ? `${v.trimEnd()} ${dictation.text}` : dictation.text);
2037
+ }, [dictation]);
2038
+ const listening = voice?.state === "listening";
2039
+ const transcribing = voice?.state === "transcribing";
1685
2040
  const submit = (e) => {
1686
2041
  e.preventDefault();
1687
2042
  if (busy) return;
@@ -1727,6 +2082,7 @@ function Composer({
1727
2082
  ] }, n)),
1728
2083
  fileError && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agi-chat__file-error", children: fileError })
1729
2084
  ] }),
2085
+ voice?.error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agi-chat__file-error", children: voice.error }),
1730
2086
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agi-chat__row", children: [
1731
2087
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1732
2088
  "input",
@@ -1770,10 +2126,156 @@ function Composer({
1770
2126
  autoComplete: "off"
1771
2127
  }
1772
2128
  ),
2129
+ voice && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2130
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2131
+ Button,
2132
+ {
2133
+ variant: "ghost",
2134
+ type: "button",
2135
+ className: cx("agi-chat__mic", listening && "agi-chat__mic--on"),
2136
+ "aria-label": listening ? t("voice", "stopTalking") : t("voice", "dictate"),
2137
+ "aria-pressed": listening,
2138
+ title: listening ? t("voice", "stopTalking") : t("voice", "dictate"),
2139
+ disabled: busy || transcribing,
2140
+ onClick: () => listening ? voice.stop() : void voice.start(),
2141
+ children: transcribing ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Spinner, { label: t("voice", "transcribing") }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicGlyph, {})
2142
+ }
2143
+ ),
2144
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2145
+ Button,
2146
+ {
2147
+ variant: "ghost",
2148
+ type: "button",
2149
+ "aria-label": t("voice", "handsFree"),
2150
+ title: t("voice", "handsFree"),
2151
+ disabled: busy || listening || transcribing,
2152
+ onClick: voice.onHandsFree,
2153
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HeadsetGlyph, {})
2154
+ }
2155
+ )
2156
+ ] }),
1773
2157
  busy ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Button, { variant: "outline", onClick: onStop, children: t("chat", "stop") }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Button, { variant: "signal", type: "submit", disabled: !canSend, children: t("chat", "send") })
1774
2158
  ] })
1775
2159
  ] });
1776
2160
  }
2161
+ function VoiceConversation({
2162
+ messages,
2163
+ busy,
2164
+ voice,
2165
+ speakReplies,
2166
+ onExit
2167
+ }) {
2168
+ const { t } = useAginies();
2169
+ const spokenRef = (0, import_react6.useRef)(null);
2170
+ const mountedRef = (0, import_react6.useRef)(true);
2171
+ const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant");
2172
+ const lastUser = [...messages].reverse().find((m) => m.role === "user");
2173
+ (0, import_react6.useEffect)(() => {
2174
+ mountedRef.current = true;
2175
+ spokenRef.current = lastAssistant?.id ?? null;
2176
+ void voice.startListening();
2177
+ return () => {
2178
+ mountedRef.current = false;
2179
+ };
2180
+ }, []);
2181
+ const replyId = lastAssistant?.id;
2182
+ const replyStreaming = lastAssistant?.streaming === true;
2183
+ (0, import_react6.useEffect)(() => {
2184
+ if (!lastAssistant || replyStreaming || replyId === spokenRef.current) return;
2185
+ spokenRef.current = replyId ?? null;
2186
+ const run = async () => {
2187
+ if (speakReplies && lastAssistant.content && !lastAssistant.error) {
2188
+ await voice.speak(lastAssistant.content);
2189
+ }
2190
+ if (mountedRef.current) await voice.startListening();
2191
+ };
2192
+ void run();
2193
+ }, [replyId, replyStreaming]);
2194
+ const status = voice.state === "listening" ? t("voice", "listening") : voice.state === "transcribing" ? t("voice", "transcribing") : voice.state === "speaking" ? t("voice", "speaking") : busy ? t("voice", "thinking") : t("voice", "idle");
2195
+ const hint = voice.state === "listening" ? t("voice", "stopTalking") : voice.state === "speaking" ? t("voice", "interrupt") : voice.state === "transcribing" || busy ? "" : t("voice", "talk");
2196
+ const tap = () => {
2197
+ if (voice.state === "listening") voice.stopListening();
2198
+ else if (voice.state === "speaking") {
2199
+ voice.stopSpeaking();
2200
+ void voice.startListening();
2201
+ } else if (voice.state === "idle" && !busy) void voice.startListening();
2202
+ };
2203
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "agi-voice", "aria-label": t("voice", "handsFree"), children: [
2204
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2205
+ "button",
2206
+ {
2207
+ type: "button",
2208
+ className: "agi-voice__exit",
2209
+ onClick: onExit,
2210
+ "aria-label": t("voice", "exit"),
2211
+ children: "\xD7"
2212
+ }
2213
+ ),
2214
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agi-voice__status", "aria-live": "polite", children: status }),
2215
+ lastUser?.content && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "agi-voice__you", children: lastUser.content }),
2216
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agi-voice__reply", children: lastAssistant?.content ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Markdown, { className: "agi-md", text: lastAssistant.content }) : busy ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Spinner, { label: t("voice", "thinking") }) : null }),
2217
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2218
+ "button",
2219
+ {
2220
+ type: "button",
2221
+ className: cx(
2222
+ "agi-voice__orb",
2223
+ `agi-voice__orb--${voice.state}`,
2224
+ busy && "agi-voice__orb--busy"
2225
+ ),
2226
+ style: { ["--agi-level"]: voice.level },
2227
+ onClick: tap,
2228
+ "aria-label": hint || status,
2229
+ disabled: voice.state === "transcribing" || voice.state === "unsupported",
2230
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicGlyph, { size: 28 })
2231
+ }
2232
+ ),
2233
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agi-voice__hint", children: hint }),
2234
+ voice.error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agi-voice__error", children: voice.error })
2235
+ ] });
2236
+ }
2237
+ function MicGlyph({ size = 16 }) {
2238
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2239
+ "svg",
2240
+ {
2241
+ width: size,
2242
+ height: size,
2243
+ viewBox: "0 0 24 24",
2244
+ fill: "none",
2245
+ stroke: "currentColor",
2246
+ strokeWidth: "1.8",
2247
+ strokeLinecap: "round",
2248
+ strokeLinejoin: "round",
2249
+ "aria-hidden": "true",
2250
+ children: [
2251
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: "9", y: "3", width: "6", height: "11", rx: "3" }),
2252
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5 11a7 7 0 0 0 14 0M12 18v3M9 21h6" })
2253
+ ]
2254
+ }
2255
+ );
2256
+ }
2257
+ function HeadsetGlyph() {
2258
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2259
+ "svg",
2260
+ {
2261
+ width: "16",
2262
+ height: "16",
2263
+ viewBox: "0 0 24 24",
2264
+ fill: "none",
2265
+ stroke: "currentColor",
2266
+ strokeWidth: "1.8",
2267
+ strokeLinecap: "round",
2268
+ strokeLinejoin: "round",
2269
+ "aria-hidden": "true",
2270
+ children: [
2271
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M4 14v-2a8 8 0 0 1 16 0v2" }),
2272
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: "3", y: "13", width: "4", height: "6", rx: "1.5" }),
2273
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: "17", y: "13", width: "4", height: "6", rx: "1.5" }),
2274
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M19 19v1a2 2 0 0 1-2 2h-3" })
2275
+ ]
2276
+ }
2277
+ );
2278
+ }
1777
2279
  function readFile(file) {
1778
2280
  return new Promise((resolve, reject) => {
1779
2281
  const reader = new FileReader();
@@ -1837,9 +2339,9 @@ function PasswordAuth({
1837
2339
  onPassword
1838
2340
  }) {
1839
2341
  const { t } = useAginies();
1840
- const [value, setValue] = (0, import_react5.useState)("");
1841
- const [error, setError] = (0, import_react5.useState)(null);
1842
- const [pending, setPending] = (0, import_react5.useState)(false);
2342
+ const [value, setValue] = (0, import_react6.useState)("");
2343
+ const [error, setError] = (0, import_react6.useState)(null);
2344
+ const [pending, setPending] = (0, import_react6.useState)(false);
1843
2345
  const submit = async (e) => {
1844
2346
  e.preventDefault();
1845
2347
  setPending(true);
@@ -1869,12 +2371,12 @@ function EmailAuth({
1869
2371
  onVerifyCode
1870
2372
  }) {
1871
2373
  const { t } = useAginies();
1872
- const [email, setEmail] = (0, import_react5.useState)("");
1873
- const [code, setCode] = (0, import_react5.useState)("");
1874
- const [step, setStep] = (0, import_react5.useState)("email");
1875
- const [error, setError] = (0, import_react5.useState)(null);
1876
- const [notice, setNotice] = (0, import_react5.useState)(null);
1877
- const [pending, setPending] = (0, import_react5.useState)(false);
2374
+ const [email, setEmail] = (0, import_react6.useState)("");
2375
+ const [code, setCode] = (0, import_react6.useState)("");
2376
+ const [step, setStep] = (0, import_react6.useState)("email");
2377
+ const [error, setError] = (0, import_react6.useState)(null);
2378
+ const [notice, setNotice] = (0, import_react6.useState)(null);
2379
+ const [pending, setPending] = (0, import_react6.useState)(false);
1878
2380
  const request = async () => {
1879
2381
  setPending(true);
1880
2382
  setError(null);
@@ -2085,7 +2587,7 @@ function CostBars({ items, format = usd, className, ...props }) {
2085
2587
  }
2086
2588
 
2087
2589
  // src/run/agent-runner.tsx
2088
- var import_react6 = require("react");
2590
+ var import_react7 = require("react");
2089
2591
 
2090
2592
  // src/run/run-client.ts
2091
2593
  async function* runAgent(client, workflowId, options, signal) {
@@ -2225,19 +2727,19 @@ function decodeRunFrame(frame) {
2225
2727
  var import_jsx_runtime8 = require("react/jsx-runtime");
2226
2728
  function useAgentRun(workflowId, apiKey) {
2227
2729
  const { client, t } = useAginies();
2228
- const [status, setStatus] = (0, import_react6.useState)("idle");
2229
- const [text, setText] = (0, import_react6.useState)("");
2230
- const [steps, setSteps] = (0, import_react6.useState)([]);
2231
- const [output, setOutput] = (0, import_react6.useState)(null);
2232
- const [error, setError] = (0, import_react6.useState)(null);
2233
- const [executionId, setExecutionId] = (0, import_react6.useState)();
2234
- const abortRef = (0, import_react6.useRef)(null);
2235
- const cancel = (0, import_react6.useCallback)(() => {
2730
+ const [status, setStatus] = (0, import_react7.useState)("idle");
2731
+ const [text, setText] = (0, import_react7.useState)("");
2732
+ const [steps, setSteps] = (0, import_react7.useState)([]);
2733
+ const [output, setOutput] = (0, import_react7.useState)(null);
2734
+ const [error, setError] = (0, import_react7.useState)(null);
2735
+ const [executionId, setExecutionId] = (0, import_react7.useState)();
2736
+ const abortRef = (0, import_react7.useRef)(null);
2737
+ const cancel = (0, import_react7.useCallback)(() => {
2236
2738
  abortRef.current?.abort();
2237
2739
  abortRef.current = null;
2238
2740
  setStatus((s) => s === "running" ? "idle" : s);
2239
2741
  }, []);
2240
- const run = (0, import_react6.useCallback)(
2742
+ const run = (0, import_react7.useCallback)(
2241
2743
  async (input) => {
2242
2744
  abortRef.current?.abort();
2243
2745
  const controller = new AbortController();
@@ -2314,7 +2816,7 @@ function AgentRunner({
2314
2816
  const { t } = useAginies();
2315
2817
  const enabled = useModule("run");
2316
2818
  const agent = useAgentRun(workflowId, apiKey);
2317
- const [values, setValues] = (0, import_react6.useState)(
2819
+ const [values, setValues] = (0, import_react7.useState)(
2318
2820
  () => Object.fromEntries(
2319
2821
  fields.map((f) => [f.name, f.defaultValue ?? (f.type === "boolean" ? false : "")])
2320
2822
  )
@@ -2326,9 +2828,9 @@ function AgentRunner({
2326
2828
  const input = fields.length === 1 && fields[0]?.name === "input" && fields[0]?.type === "textarea" ? values.input : values;
2327
2829
  await agent.run(input);
2328
2830
  };
2329
- const onResultRef = (0, import_react6.useRef)(onResult);
2831
+ const onResultRef = (0, import_react7.useRef)(onResult);
2330
2832
  onResultRef.current = onResult;
2331
- (0, import_react6.useEffect)(() => {
2833
+ (0, import_react7.useEffect)(() => {
2332
2834
  if (agent.status === "done") onResultRef.current?.(agent.output);
2333
2835
  }, [agent.status, agent.output]);
2334
2836
  if (!enabled) return null;
@@ -2449,6 +2951,7 @@ function formatOutput(output) {
2449
2951
  getPausedExecution,
2450
2952
  init,
2451
2953
  initialValues,
2954
+ isVoiceSupported,
2452
2955
  listPausedExecutions,
2453
2956
  outputOf,
2454
2957
  parseFieldValue,
@@ -2462,6 +2965,7 @@ function formatOutput(output) {
2462
2965
  useAginies,
2463
2966
  useApproval,
2464
2967
  useChat,
2465
- useModule
2968
+ useModule,
2969
+ useVoice
2466
2970
  });
2467
2971
  //# sourceMappingURL=index.cjs.map