@aginies/webuikit 0.2.0 → 0.4.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.js CHANGED
@@ -15,6 +15,8 @@ var trimSlash = (s) => s.replace(/\/+$/, "");
15
15
  var AginiesClient = class {
16
16
  baseUrl;
17
17
  token;
18
+ /** True when activated from the platform's own page. */
19
+ hosted;
18
20
  locale;
19
21
  fetchImpl;
20
22
  state = { status: "idle" };
@@ -23,9 +25,12 @@ var AginiesClient = class {
23
25
  session = null;
24
26
  constructor(config) {
25
27
  if (!config?.baseUrl) throw new AginiesError("baseUrl is required", 0, "MISSING_BASE_URL");
26
- if (!config?.token) throw new AginiesError("token is required", 0, "MISSING_TOKEN");
28
+ if (!config?.token && !config?.hosted) {
29
+ throw new AginiesError("token is required", 0, "MISSING_TOKEN");
30
+ }
27
31
  this.baseUrl = trimSlash(config.baseUrl);
28
- this.token = config.token;
32
+ this.token = config.token ?? null;
33
+ this.hosted = config.hosted === true;
29
34
  this.locale = config.locale ?? detectLocale();
30
35
  this.fetchImpl = config.fetch ?? ((...args) => fetch(...args));
31
36
  }
@@ -52,7 +57,7 @@ var AginiesClient = class {
52
57
  const res = await this.fetchImpl(`${this.baseUrl}/api/ui/activate`, {
53
58
  method: "POST",
54
59
  headers: { "Content-Type": "application/json" },
55
- body: JSON.stringify({ token: this.token })
60
+ body: JSON.stringify(this.hosted ? { hosted: true } : { token: this.token })
56
61
  });
57
62
  if (!res.ok) {
58
63
  const body2 = await safeJson(res);
@@ -127,6 +132,30 @@ var AginiesClient = class {
127
132
  this.assertActive();
128
133
  return this.request(path2, init2);
129
134
  }
135
+ /** Sends a recording to the platform's transcription model. */
136
+ async transcribe(identifier, audio, filename = "turn.webm", language) {
137
+ this.assertActive();
138
+ const form = new FormData();
139
+ form.append("file", audio, filename);
140
+ if (language) form.append("language", language);
141
+ const res = await this.request(`/api/chat/${encodeURIComponent(identifier)}/voice/transcribe`, {
142
+ method: "POST",
143
+ body: form
144
+ });
145
+ if (!res.ok) throw await voiceError(res);
146
+ return await res.json();
147
+ }
148
+ /** Audio for a reply from the platform's synthesis endpoint. */
149
+ async speak(identifier, text) {
150
+ this.assertActive();
151
+ const res = await this.request(`/api/chat/${encodeURIComponent(identifier)}/voice/speak`, {
152
+ method: "POST",
153
+ headers: { "Content-Type": "application/json" },
154
+ body: JSON.stringify({ text })
155
+ });
156
+ if (!res.ok) throw await voiceError(res);
157
+ return res.blob();
158
+ }
130
159
  /** Hosted-chat configuration. Resolves to an auth requirement instead of throwing on 401. */
131
160
  async getChat(identifier) {
132
161
  this.assertActive();
@@ -216,6 +245,15 @@ function decodeFrame(frame) {
216
245
  }
217
246
  return null;
218
247
  }
248
+ async function voiceError(res) {
249
+ const body = await safeJson(res);
250
+ if (res.status === 401 && body && typeof body.authRequired === "string") {
251
+ return new AginiesError("Authentication required", 401, "AUTH_REQUIRED");
252
+ }
253
+ if (res.status === 503)
254
+ return new AginiesError("Voice is not available", 503, "VOICE_UNAVAILABLE");
255
+ return new AginiesError(body?.error || `HTTP ${res.status}`, res.status);
256
+ }
219
257
  async function safeJson(res) {
220
258
  try {
221
259
  return await res.json();
@@ -512,7 +550,37 @@ var STRINGS = {
512
550
  tr: "Bu sohbet kurumsal kimlikle a\xE7\u0131l\u0131r.",
513
551
  en: "This chat opens with your organisation account."
514
552
  },
515
- ssoButton: { tr: "Kurumsal giri\u015F", en: "Sign in" }
553
+ ssoButton: { tr: "Kurumsal giri\u015F", en: "Sign in" },
554
+ ssoRetry: { tr: "Giri\u015F yapt\u0131m, yenile", en: "I have signed in, refresh" }
555
+ },
556
+ voice: {
557
+ talk: { tr: "Konu\u015Fmak i\xE7in dokunun", en: "Tap to talk" },
558
+ stopTalking: { tr: "Bitirmek i\xE7in dokunun", en: "Tap when done" },
559
+ dictate: { tr: "Sesle yaz", en: "Dictate" },
560
+ handsFree: { tr: "Sesli sohbet", en: "Voice conversation" },
561
+ exit: { tr: "Sesli sohbetten \xE7\u0131k", en: "Leave voice conversation" },
562
+ listening: { tr: "Dinliyor", en: "Listening" },
563
+ transcribing: { tr: "Yaz\u0131ya d\xF6k\xFCl\xFCyor", en: "Transcribing" },
564
+ thinking: { tr: "Yan\u0131t haz\u0131rlan\u0131yor", en: "Working on it" },
565
+ speaking: { tr: "Konu\u015Fuyor", en: "Speaking" },
566
+ idle: { tr: "Haz\u0131r", en: "Ready" },
567
+ interrupt: { tr: "S\xF6z\xFCn\xFC kesmek i\xE7in dokunun", en: "Tap to interrupt" },
568
+ micDenied: {
569
+ tr: "Mikrofon izni verilmedi. Taray\u0131c\u0131 ayarlar\u0131ndan izin verin.",
570
+ en: "Microphone access was denied. Allow it in your browser settings."
571
+ },
572
+ unsupported: {
573
+ tr: "Bu taray\u0131c\u0131 ses kayd\u0131n\u0131 desteklemiyor.",
574
+ en: "This browser cannot record audio."
575
+ },
576
+ unavailable: {
577
+ tr: "Ses \xF6zelli\u011Fi bu sohbet i\xE7in a\xE7\u0131k de\u011Fil.",
578
+ en: "Voice is not enabled for this chat."
579
+ },
580
+ error: {
581
+ tr: "Ses i\u015Flenemedi. L\xFCtfen tekrar deneyin.",
582
+ en: "Voice could not be processed. Please try again."
583
+ }
516
584
  },
517
585
  run: {
518
586
  submit: { tr: "\xC7al\u0131\u015Ft\u0131r", en: "Run" },
@@ -964,7 +1032,7 @@ function renderField(f, value, set, disabled) {
964
1032
  }
965
1033
 
966
1034
  // src/chat/chat-widget.tsx
967
- import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
1035
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
968
1036
 
969
1037
  // src/chat/markdown.tsx
970
1038
  import { Fragment as Fragment3 } from "react";
@@ -1274,20 +1342,258 @@ function Pie({ labels, data }) {
1274
1342
  ] });
1275
1343
  }
1276
1344
 
1345
+ // src/chat/voice.ts
1346
+ import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
1347
+ var MIME_CANDIDATES = [
1348
+ "audio/webm;codecs=opus",
1349
+ "audio/webm",
1350
+ "audio/mp4",
1351
+ "audio/ogg;codecs=opus"
1352
+ ];
1353
+ var MIN_CLIP_BYTES = 800;
1354
+ var SPEECH_RMS = 0.02;
1355
+ function isVoiceSupported() {
1356
+ return typeof window !== "undefined" && typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && typeof MediaRecorder !== "undefined";
1357
+ }
1358
+ function useVoice({
1359
+ identifier,
1360
+ onTranscript,
1361
+ language,
1362
+ silenceMs = 1400,
1363
+ maxMs = 6e4
1364
+ }) {
1365
+ const { client, t } = useAginies();
1366
+ const supported = isVoiceSupported();
1367
+ const [state, setState] = useState3(supported ? "idle" : "unsupported");
1368
+ const [error, setError] = useState3(null);
1369
+ const [level, setLevel] = useState3(0);
1370
+ const recorderRef = useRef2(null);
1371
+ const streamRef = useRef2(null);
1372
+ const chunksRef = useRef2([]);
1373
+ const discardRef = useRef2(false);
1374
+ const timersRef = useRef2([]);
1375
+ const meterRef = useRef2(null);
1376
+ const audioRef = useRef2(null);
1377
+ const audioUrlRef = useRef2(null);
1378
+ const startingRef = useRef2(false);
1379
+ const onTranscriptRef = useRef2(onTranscript);
1380
+ onTranscriptRef.current = onTranscript;
1381
+ const clearTimers = useCallback2(() => {
1382
+ for (const id of timersRef.current) window.clearTimeout(id);
1383
+ timersRef.current = [];
1384
+ if (meterRef.current) {
1385
+ window.clearInterval(meterRef.current.interval);
1386
+ void meterRef.current.ctx.close().catch(() => {
1387
+ });
1388
+ meterRef.current = null;
1389
+ }
1390
+ setLevel(0);
1391
+ }, []);
1392
+ const releaseStream = useCallback2(() => {
1393
+ for (const track of streamRef.current?.getTracks() ?? []) track.stop();
1394
+ streamRef.current = null;
1395
+ }, []);
1396
+ const stopSpeaking = useCallback2(() => {
1397
+ const audio = audioRef.current;
1398
+ if (audio) {
1399
+ audio.pause();
1400
+ audio.src = "";
1401
+ audioRef.current = null;
1402
+ }
1403
+ if (audioUrlRef.current) {
1404
+ URL.revokeObjectURL(audioUrlRef.current);
1405
+ audioUrlRef.current = null;
1406
+ }
1407
+ setState((s) => s === "speaking" ? "idle" : s);
1408
+ }, []);
1409
+ const finish = useCallback2((recorder) => {
1410
+ recorder.stop();
1411
+ }, []);
1412
+ const stopListening = useCallback2(() => {
1413
+ const recorder = recorderRef.current;
1414
+ if (!recorder || recorder.state === "inactive") return;
1415
+ discardRef.current = false;
1416
+ finish(recorder);
1417
+ }, [finish]);
1418
+ const cancelListening = useCallback2(() => {
1419
+ const recorder = recorderRef.current;
1420
+ if (!recorder || recorder.state === "inactive") return;
1421
+ discardRef.current = true;
1422
+ finish(recorder);
1423
+ }, [finish]);
1424
+ const startMeter = useCallback2(
1425
+ (stream, onSilence) => {
1426
+ const Ctx = typeof AudioContext !== "undefined" ? AudioContext : window.webkitAudioContext;
1427
+ if (!Ctx) return;
1428
+ try {
1429
+ const ctx = new Ctx();
1430
+ const analyser = ctx.createAnalyser();
1431
+ analyser.fftSize = 2048;
1432
+ ctx.createMediaStreamSource(stream).connect(analyser);
1433
+ const data = new Uint8Array(analyser.fftSize);
1434
+ let heard = false;
1435
+ let lastVoice = Date.now();
1436
+ const interval = window.setInterval(() => {
1437
+ analyser.getByteTimeDomainData(data);
1438
+ let sum = 0;
1439
+ for (const v of data) {
1440
+ const n = (v - 128) / 128;
1441
+ sum += n * n;
1442
+ }
1443
+ const rms = Math.sqrt(sum / data.length);
1444
+ setLevel(Math.min(1, rms * 6));
1445
+ if (rms > SPEECH_RMS) {
1446
+ heard = true;
1447
+ lastVoice = Date.now();
1448
+ } else if (heard && Date.now() - lastVoice > silenceMs) {
1449
+ onSilence();
1450
+ }
1451
+ }, 100);
1452
+ meterRef.current = { ctx, interval };
1453
+ } catch {
1454
+ }
1455
+ },
1456
+ [silenceMs]
1457
+ );
1458
+ const startListening = useCallback2(async () => {
1459
+ if (!supported) {
1460
+ setState("unsupported");
1461
+ return;
1462
+ }
1463
+ if (startingRef.current) return;
1464
+ if (recorderRef.current && recorderRef.current.state !== "inactive") return;
1465
+ startingRef.current = true;
1466
+ stopSpeaking();
1467
+ setError(null);
1468
+ let stream;
1469
+ try {
1470
+ stream = await navigator.mediaDevices.getUserMedia({ audio: true });
1471
+ } catch {
1472
+ startingRef.current = false;
1473
+ setState("denied");
1474
+ setError(t("voice", "micDenied"));
1475
+ return;
1476
+ }
1477
+ startingRef.current = false;
1478
+ streamRef.current = stream;
1479
+ const mimeType = MIME_CANDIDATES.find((m) => MediaRecorder.isTypeSupported?.(m));
1480
+ const recorder = new MediaRecorder(stream, mimeType ? { mimeType } : void 0);
1481
+ recorderRef.current = recorder;
1482
+ chunksRef.current = [];
1483
+ discardRef.current = false;
1484
+ recorder.ondataavailable = (e) => {
1485
+ if (e.data && e.data.size > 0) chunksRef.current.push(e.data);
1486
+ };
1487
+ recorder.onstop = () => {
1488
+ clearTimers();
1489
+ releaseStream();
1490
+ recorderRef.current = null;
1491
+ const type = recorder.mimeType || mimeType || "audio/webm";
1492
+ const clip = new Blob(chunksRef.current, { type });
1493
+ chunksRef.current = [];
1494
+ if (discardRef.current || clip.size < MIN_CLIP_BYTES) {
1495
+ setState("idle");
1496
+ return;
1497
+ }
1498
+ setState("transcribing");
1499
+ const ext = type.includes("mp4") ? "m4a" : type.includes("ogg") ? "ogg" : "webm";
1500
+ client.transcribe(identifier, clip, `turn.${ext}`, language).then((result) => {
1501
+ setState("idle");
1502
+ if (result.transcript) onTranscriptRef.current(result.transcript);
1503
+ }).catch((err) => {
1504
+ setState("idle");
1505
+ setError(
1506
+ err instanceof AginiesError && err.code === "VOICE_UNAVAILABLE" ? t("voice", "unavailable") : t("voice", "error")
1507
+ );
1508
+ });
1509
+ };
1510
+ recorder.start(250);
1511
+ setState("listening");
1512
+ startMeter(stream, () => stopListening());
1513
+ timersRef.current.push(window.setTimeout(() => stopListening(), maxMs));
1514
+ }, [
1515
+ supported,
1516
+ stopSpeaking,
1517
+ t,
1518
+ client,
1519
+ identifier,
1520
+ language,
1521
+ maxMs,
1522
+ stopListening,
1523
+ clearTimers,
1524
+ releaseStream,
1525
+ startMeter
1526
+ ]);
1527
+ const speak = useCallback2(
1528
+ async (text) => {
1529
+ stopSpeaking();
1530
+ setError(null);
1531
+ setState("speaking");
1532
+ try {
1533
+ const blob = await client.speak(identifier, text);
1534
+ const url = URL.createObjectURL(blob);
1535
+ audioUrlRef.current = url;
1536
+ const audio = new Audio(url);
1537
+ audioRef.current = audio;
1538
+ await new Promise((resolve) => {
1539
+ audio.onended = () => resolve();
1540
+ audio.onerror = () => resolve();
1541
+ audio.onpause = () => resolve();
1542
+ audio.play().catch(() => resolve());
1543
+ });
1544
+ } catch (err) {
1545
+ setError(
1546
+ err instanceof AginiesError && err.code === "VOICE_UNAVAILABLE" ? t("voice", "unavailable") : t("voice", "error")
1547
+ );
1548
+ } finally {
1549
+ if (audioUrlRef.current) URL.revokeObjectURL(audioUrlRef.current);
1550
+ audioUrlRef.current = null;
1551
+ audioRef.current = null;
1552
+ setState((s) => s === "speaking" ? "idle" : s);
1553
+ }
1554
+ },
1555
+ [client, identifier, stopSpeaking, t]
1556
+ );
1557
+ useEffect3(
1558
+ () => () => {
1559
+ discardRef.current = true;
1560
+ if (recorderRef.current && recorderRef.current.state !== "inactive")
1561
+ recorderRef.current.stop();
1562
+ clearTimers();
1563
+ releaseStream();
1564
+ const audio = audioRef.current;
1565
+ if (audio) audio.pause();
1566
+ if (audioUrlRef.current) URL.revokeObjectURL(audioUrlRef.current);
1567
+ },
1568
+ [clearTimers, releaseStream]
1569
+ );
1570
+ return {
1571
+ state,
1572
+ error,
1573
+ supported,
1574
+ level,
1575
+ startListening,
1576
+ stopListening,
1577
+ cancelListening,
1578
+ speak,
1579
+ stopSpeaking
1580
+ };
1581
+ }
1582
+
1277
1583
  // src/chat/chat-widget.tsx
1278
1584
  import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1279
1585
  var ATTACHMENT_LIMITS = { maxFiles: 5, maxBytes: 10 * 1024 * 1024 };
1280
1586
  function useChat(identifier, enabled = true) {
1281
1587
  const { client, t } = useAginies();
1282
- const [config, setConfig] = useState3(null);
1283
- const [authNeed, setAuthNeed] = useState3(null);
1284
- const [authTitle, setAuthTitle] = useState3();
1285
- const [loadError, setLoadError] = useState3(null);
1286
- const [messages, setMessages] = useState3([]);
1287
- const [busy, setBusy] = useState3(false);
1288
- const [conversationId] = useState3(() => randomId());
1289
- const abortRef = useRef2(null);
1290
- const load = useCallback2(async () => {
1588
+ const [config, setConfig] = useState4(null);
1589
+ const [authNeed, setAuthNeed] = useState4(null);
1590
+ const [authTitle, setAuthTitle] = useState4();
1591
+ const [loadError, setLoadError] = useState4(null);
1592
+ const [messages, setMessages] = useState4([]);
1593
+ const [busy, setBusy] = useState4(false);
1594
+ const [conversationId] = useState4(() => randomId());
1595
+ const abortRef = useRef3(null);
1596
+ const load = useCallback3(async () => {
1291
1597
  setLoadError(null);
1292
1598
  try {
1293
1599
  const res = await client.getChat(identifier);
@@ -1304,11 +1610,11 @@ function useChat(identifier, enabled = true) {
1304
1610
  );
1305
1611
  }
1306
1612
  }, [client, identifier, t]);
1307
- useEffect3(() => {
1613
+ useEffect4(() => {
1308
1614
  if (enabled) void load();
1309
1615
  }, [load, enabled]);
1310
1616
  const chatPath = `/api/chat/${encodeURIComponent(identifier)}`;
1311
- const authenticate = useCallback2(
1617
+ const authenticate = useCallback3(
1312
1618
  async ({ password }) => {
1313
1619
  const res = await client.fetchRaw(chatPath, {
1314
1620
  method: "POST",
@@ -1323,7 +1629,27 @@ function useChat(identifier, enabled = true) {
1323
1629
  },
1324
1630
  [client, chatPath, conversationId, load]
1325
1631
  );
1326
- const requestCode = useCallback2(
1632
+ const signInWithSso = useCallback3(
1633
+ async (email) => {
1634
+ const res = await client.fetchRaw(chatPath, {
1635
+ method: "POST",
1636
+ headers: { "Content-Type": "application/json" },
1637
+ body: JSON.stringify({ email, checkSSOAccess: true })
1638
+ });
1639
+ if (!res.ok) return res.status === 401 || res.status === 403 ? "unauthorized" : "error";
1640
+ const hostedPage = `${client.baseUrl}/chat/${encodeURIComponent(identifier)}`;
1641
+ if (client.hosted) {
1642
+ window.location.assign(
1643
+ `${client.baseUrl}/sso?email=${encodeURIComponent(email)}&callbackUrl=${encodeURIComponent(window.location.pathname)}`
1644
+ );
1645
+ } else {
1646
+ window.open(hostedPage, "_blank", "noopener");
1647
+ }
1648
+ return "redirected";
1649
+ },
1650
+ [client, chatPath, identifier]
1651
+ );
1652
+ const requestCode = useCallback3(
1327
1653
  async (email) => {
1328
1654
  const res = await client.fetchRaw(`${chatPath}/otp`, {
1329
1655
  method: "POST",
@@ -1335,7 +1661,7 @@ function useChat(identifier, enabled = true) {
1335
1661
  },
1336
1662
  [client, chatPath]
1337
1663
  );
1338
- const verifyCode = useCallback2(
1664
+ const verifyCode = useCallback3(
1339
1665
  async (email, otp) => {
1340
1666
  const res = await client.fetchRaw(`${chatPath}/otp`, {
1341
1667
  method: "PUT",
@@ -1350,7 +1676,7 @@ function useChat(identifier, enabled = true) {
1350
1676
  },
1351
1677
  [client, chatPath, load]
1352
1678
  );
1353
- const stop = useCallback2(() => {
1679
+ const stop = useCallback3(() => {
1354
1680
  abortRef.current?.abort();
1355
1681
  abortRef.current = null;
1356
1682
  setBusy(false);
@@ -1363,7 +1689,7 @@ function useChat(identifier, enabled = true) {
1363
1689
  ];
1364
1690
  });
1365
1691
  }, [t]);
1366
- const send = useCallback2(
1692
+ const send = useCallback3(
1367
1693
  async (text, files = []) => {
1368
1694
  const input = text.trim();
1369
1695
  if (!input && files.length === 0 || busy) return;
@@ -1398,6 +1724,14 @@ function useChat(identifier, enabled = true) {
1398
1724
  if (text2 && !content) {
1399
1725
  content = text2;
1400
1726
  update({ content });
1727
+ } else if (!content) {
1728
+ const raw = extractFinalOutput(ev.data);
1729
+ if (raw) {
1730
+ content = `\`\`\`json
1731
+ ${raw}
1732
+ \`\`\``;
1733
+ update({ content });
1734
+ }
1401
1735
  }
1402
1736
  } else if (ev.type === "error") {
1403
1737
  update({ content: ev.message, error: true, streaming: false });
@@ -1428,6 +1762,7 @@ function useChat(identifier, enabled = true) {
1428
1762
  authenticate,
1429
1763
  requestCode,
1430
1764
  verifyCode,
1765
+ signInWithSso,
1431
1766
  reload: load
1432
1767
  };
1433
1768
  }
@@ -1444,6 +1779,14 @@ function extractFinalText(data) {
1444
1779
  }
1445
1780
  return null;
1446
1781
  }
1782
+ function extractFinalOutput(data) {
1783
+ if (!data || typeof data !== "object") return null;
1784
+ const d = data;
1785
+ const payload = d.output ?? d.error;
1786
+ if (payload === void 0 || payload === null) return null;
1787
+ const text = typeof payload === "string" ? payload : JSON.stringify(payload, null, 2);
1788
+ return text.trim() ? text : null;
1789
+ }
1447
1790
  function randomId() {
1448
1791
  if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
1449
1792
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
@@ -1455,15 +1798,38 @@ function ChatWidget({
1455
1798
  launcherLabel,
1456
1799
  defaultOpen = false,
1457
1800
  theme = "auto",
1801
+ voice = true,
1458
1802
  className
1459
1803
  }) {
1460
- const [open, setOpen] = useState3(defaultOpen || mode !== "bubble");
1461
- const { t } = useAginies();
1804
+ const [open, setOpen] = useState4(defaultOpen || mode !== "bubble");
1805
+ const { t, client } = useAginies();
1462
1806
  const enabled = useModule("chat");
1463
1807
  const chat = useChat(identifier, enabled);
1808
+ const [voiceMode, setVoiceMode] = useState4(false);
1809
+ const [dictation, setDictation] = useState4(null);
1810
+ const voiceModeRef = useRef3(voiceMode);
1811
+ voiceModeRef.current = voiceMode;
1812
+ const sendRef = useRef3(chat.send);
1813
+ sendRef.current = chat.send;
1814
+ const voiceControls = useVoice({
1815
+ identifier,
1816
+ onTranscript: (text) => {
1817
+ if (voiceModeRef.current) void sendRef.current(text);
1818
+ else setDictation({ id: Date.now(), text });
1819
+ }
1820
+ });
1821
+ const capabilities = chat.config?.voice;
1822
+ const voiceAvailable = voice && voiceControls.supported && capabilities?.stt === true;
1823
+ const state = client.getState();
1824
+ const brand = state.status === "active" ? state.config.brand : { name: "Aginies", logoUrl: null };
1464
1825
  const title = chat.config?.title ?? chat.authTitle ?? launcherLabel ?? "Aginies";
1465
1826
  const themeClass = theme === "auto" ? void 0 : theme === "dark" ? "dark" : "light";
1466
1827
  if (!enabled) return null;
1828
+ const leaveVoiceMode = () => {
1829
+ voiceControls.cancelListening();
1830
+ voiceControls.stopSpeaking();
1831
+ setVoiceMode(false);
1832
+ };
1467
1833
  const panel = /* @__PURE__ */ jsxs5(
1468
1834
  "section",
1469
1835
  {
@@ -1499,9 +1865,20 @@ function ChatWidget({
1499
1865
  need: chat.authNeed,
1500
1866
  onPassword: chat.authenticate,
1501
1867
  onRequestCode: chat.requestCode,
1502
- onVerifyCode: chat.verifyCode
1868
+ onVerifyCode: chat.verifyCode,
1869
+ onSso: chat.signInWithSso,
1870
+ onRetry: () => void chat.reload()
1871
+ }
1872
+ ) : !chat.config ? /* @__PURE__ */ jsx6("div", { className: "agi-chat__state", children: /* @__PURE__ */ jsx6(Spinner, { label: t("common", "loading") }) }) : voiceMode ? /* @__PURE__ */ jsx6(
1873
+ VoiceConversation,
1874
+ {
1875
+ messages: chat.messages,
1876
+ busy: chat.busy,
1877
+ voice: voiceControls,
1878
+ speakReplies: capabilities?.tts === true,
1879
+ onExit: leaveVoiceMode
1503
1880
  }
1504
- ) : !chat.config ? /* @__PURE__ */ jsx6("div", { className: "agi-chat__state", children: /* @__PURE__ */ jsx6(Spinner, { label: t("common", "loading") }) }) : /* @__PURE__ */ jsxs5(Fragment4, { children: [
1881
+ ) : /* @__PURE__ */ jsxs5(Fragment4, { children: [
1505
1882
  /* @__PURE__ */ jsx6(
1506
1883
  MessageList,
1507
1884
  {
@@ -1510,9 +1887,24 @@ function ChatWidget({
1510
1887
  onAction: (action) => void chat.send(action)
1511
1888
  }
1512
1889
  ),
1513
- /* @__PURE__ */ jsx6(Composer, { busy: chat.busy, onSend: chat.send, onStop: chat.stop })
1890
+ /* @__PURE__ */ jsx6(
1891
+ Composer,
1892
+ {
1893
+ busy: chat.busy,
1894
+ onSend: chat.send,
1895
+ onStop: chat.stop,
1896
+ dictation,
1897
+ voice: voiceAvailable ? {
1898
+ state: voiceControls.state,
1899
+ error: voiceControls.error,
1900
+ start: voiceControls.startListening,
1901
+ stop: voiceControls.stopListening,
1902
+ onHandsFree: () => setVoiceMode(true)
1903
+ } : void 0
1904
+ }
1905
+ )
1514
1906
  ] }),
1515
- /* @__PURE__ */ jsx6("footer", { className: "agi-chat__foot", children: /* @__PURE__ */ jsx6("a", { href: "https://www.aginies.com", target: "_blank", rel: "noreferrer", children: t("chat", "poweredBy") }) })
1907
+ brand.poweredBy !== false && /* @__PURE__ */ jsx6("footer", { className: "agi-chat__foot", children: /* @__PURE__ */ jsx6("a", { href: brand.siteUrl ?? "https://www.aginies.com", target: "_blank", rel: "noreferrer", children: t("chat", "poweredBy") }) })
1516
1908
  ]
1517
1909
  }
1518
1910
  );
@@ -1565,53 +1957,57 @@ function MessageList({
1565
1957
  onAction
1566
1958
  }) {
1567
1959
  const { t } = useAginies();
1568
- const endRef = useRef2(null);
1569
- useEffect3(() => {
1960
+ const endRef = useRef3(null);
1961
+ useEffect4(() => {
1570
1962
  endRef.current?.scrollIntoView?.({ block: "end" });
1571
1963
  }, []);
1572
- return (
1573
- // biome-ignore lint/a11y/useSemanticElements: no native element conveys a live log
1574
- /* @__PURE__ */ jsxs5("div", { className: "agi-chat__messages", role: "log", "aria-live": "polite", children: [
1575
- /* @__PURE__ */ jsxs5("div", { className: "agi-msg agi-msg--assistant", children: [
1576
- /* @__PURE__ */ jsx6("span", { className: "agi-msg__who", children: t("chat", "assistant") }),
1577
- /* @__PURE__ */ jsx6("div", { className: "agi-msg__body", children: welcome })
1578
- ] }),
1579
- messages.map((m) => /* @__PURE__ */ jsxs5(
1580
- "div",
1581
- {
1582
- className: cx("agi-msg", `agi-msg--${m.role}`, m.error && "agi-msg--error"),
1583
- children: [
1584
- /* @__PURE__ */ jsx6("span", { className: "agi-msg__who", children: m.role === "user" ? t("chat", "you") : t("chat", "assistant") }),
1585
- /* @__PURE__ */ jsxs5("div", { className: "agi-msg__body", children: [
1586
- m.structured ? /* @__PURE__ */ jsx6(StructuredUI, { response: m.structured, onAction }) : m.content ? m.role === "assistant" && !m.error ? /* @__PURE__ */ jsx6(Markdown, { className: "agi-md", text: m.content }) : /* @__PURE__ */ jsx6("p", { children: m.content }) : m.streaming ? /* @__PURE__ */ jsxs5("span", { className: "agi-msg__thinking", children: [
1587
- /* @__PURE__ */ jsx6(Spinner, { label: t("chat", "thinking") }),
1588
- " ",
1589
- t("chat", "thinking")
1590
- ] }) : null,
1591
- m.attachments && m.attachments.length > 0 && /* @__PURE__ */ jsx6("ul", { className: "agi-msg__files", "aria-label": t("chat", "attachments"), children: m.attachments.map((file, n) => /* @__PURE__ */ jsxs5("li", { className: "agi-file", children: [
1592
- /* @__PURE__ */ jsx6(FileGlyph, {}),
1593
- /* @__PURE__ */ jsx6("span", { className: "agi-file__name", children: file.name }),
1594
- /* @__PURE__ */ jsx6("span", { className: "agi-file__size", children: formatBytes(file.size) })
1595
- ] }, n)) })
1596
- ] })
1597
- ]
1598
- },
1599
- m.id
1600
- )),
1601
- /* @__PURE__ */ jsx6("div", { ref: endRef })
1602
- ] })
1603
- );
1964
+ return /* @__PURE__ */ jsxs5("div", { className: "agi-chat__messages", role: "log", "aria-live": "polite", children: [
1965
+ /* @__PURE__ */ jsxs5("div", { className: "agi-msg agi-msg--assistant", children: [
1966
+ /* @__PURE__ */ jsx6("span", { className: "agi-msg__who", children: t("chat", "assistant") }),
1967
+ /* @__PURE__ */ jsx6("div", { className: "agi-msg__body", children: welcome })
1968
+ ] }),
1969
+ messages.map((m) => /* @__PURE__ */ jsxs5(
1970
+ "div",
1971
+ {
1972
+ className: cx("agi-msg", `agi-msg--${m.role}`, m.error && "agi-msg--error"),
1973
+ children: [
1974
+ /* @__PURE__ */ jsx6("span", { className: "agi-msg__who", children: m.role === "user" ? t("chat", "you") : t("chat", "assistant") }),
1975
+ /* @__PURE__ */ jsxs5("div", { className: "agi-msg__body", children: [
1976
+ m.structured ? /* @__PURE__ */ jsx6(StructuredUI, { response: m.structured, onAction }) : m.content ? m.role === "assistant" && !m.error ? /* @__PURE__ */ jsx6(Markdown, { className: "agi-md", text: m.content }) : /* @__PURE__ */ jsx6("p", { children: m.content }) : m.streaming ? /* @__PURE__ */ jsxs5("span", { className: "agi-msg__thinking", children: [
1977
+ /* @__PURE__ */ jsx6(Spinner, { label: t("chat", "thinking") }),
1978
+ " ",
1979
+ t("chat", "thinking")
1980
+ ] }) : null,
1981
+ m.attachments && m.attachments.length > 0 && /* @__PURE__ */ jsx6("ul", { className: "agi-msg__files", "aria-label": t("chat", "attachments"), children: m.attachments.map((file, n) => /* @__PURE__ */ jsxs5("li", { className: "agi-file", children: [
1982
+ /* @__PURE__ */ jsx6(FileGlyph, {}),
1983
+ /* @__PURE__ */ jsx6("span", { className: "agi-file__name", children: file.name }),
1984
+ /* @__PURE__ */ jsx6("span", { className: "agi-file__size", children: formatBytes(file.size) })
1985
+ ] }, n)) })
1986
+ ] })
1987
+ ]
1988
+ },
1989
+ m.id
1990
+ )),
1991
+ /* @__PURE__ */ jsx6("div", { ref: endRef })
1992
+ ] });
1604
1993
  }
1605
1994
  function Composer({
1606
1995
  busy,
1607
1996
  onSend,
1608
- onStop
1997
+ onStop,
1998
+ voice,
1999
+ dictation
1609
2000
  }) {
1610
2001
  const { t } = useAginies();
1611
- const [value, setValue] = useState3("");
1612
- const [files, setFiles] = useState3([]);
1613
- const [fileError, setFileError] = useState3(null);
1614
- const fileInput = useRef2(null);
2002
+ const [value, setValue] = useState4("");
2003
+ const [files, setFiles] = useState4([]);
2004
+ const [fileError, setFileError] = useState4(null);
2005
+ const fileInput = useRef3(null);
2006
+ useEffect4(() => {
2007
+ if (dictation) setValue((v) => v.trim() ? `${v.trimEnd()} ${dictation.text}` : dictation.text);
2008
+ }, [dictation]);
2009
+ const listening = voice?.state === "listening";
2010
+ const transcribing = voice?.state === "transcribing";
1615
2011
  const submit = (e) => {
1616
2012
  e.preventDefault();
1617
2013
  if (busy) return;
@@ -1657,6 +2053,7 @@ function Composer({
1657
2053
  ] }, n)),
1658
2054
  fileError && /* @__PURE__ */ jsx6("span", { className: "agi-chat__file-error", children: fileError })
1659
2055
  ] }),
2056
+ voice?.error && /* @__PURE__ */ jsx6("div", { className: "agi-chat__file-error", children: voice.error }),
1660
2057
  /* @__PURE__ */ jsxs5("div", { className: "agi-chat__row", children: [
1661
2058
  /* @__PURE__ */ jsx6(
1662
2059
  "input",
@@ -1700,10 +2097,156 @@ function Composer({
1700
2097
  autoComplete: "off"
1701
2098
  }
1702
2099
  ),
2100
+ voice && /* @__PURE__ */ jsxs5(Fragment4, { children: [
2101
+ /* @__PURE__ */ jsx6(
2102
+ Button,
2103
+ {
2104
+ variant: "ghost",
2105
+ type: "button",
2106
+ className: cx("agi-chat__mic", listening && "agi-chat__mic--on"),
2107
+ "aria-label": listening ? t("voice", "stopTalking") : t("voice", "dictate"),
2108
+ "aria-pressed": listening,
2109
+ title: listening ? t("voice", "stopTalking") : t("voice", "dictate"),
2110
+ disabled: busy || transcribing,
2111
+ onClick: () => listening ? voice.stop() : void voice.start(),
2112
+ children: transcribing ? /* @__PURE__ */ jsx6(Spinner, { label: t("voice", "transcribing") }) : /* @__PURE__ */ jsx6(MicGlyph, {})
2113
+ }
2114
+ ),
2115
+ /* @__PURE__ */ jsx6(
2116
+ Button,
2117
+ {
2118
+ variant: "ghost",
2119
+ type: "button",
2120
+ "aria-label": t("voice", "handsFree"),
2121
+ title: t("voice", "handsFree"),
2122
+ disabled: busy || listening || transcribing,
2123
+ onClick: voice.onHandsFree,
2124
+ children: /* @__PURE__ */ jsx6(HeadsetGlyph, {})
2125
+ }
2126
+ )
2127
+ ] }),
1703
2128
  busy ? /* @__PURE__ */ jsx6(Button, { variant: "outline", onClick: onStop, children: t("chat", "stop") }) : /* @__PURE__ */ jsx6(Button, { variant: "signal", type: "submit", disabled: !canSend, children: t("chat", "send") })
1704
2129
  ] })
1705
2130
  ] });
1706
2131
  }
2132
+ function VoiceConversation({
2133
+ messages,
2134
+ busy,
2135
+ voice,
2136
+ speakReplies,
2137
+ onExit
2138
+ }) {
2139
+ const { t } = useAginies();
2140
+ const spokenRef = useRef3(null);
2141
+ const mountedRef = useRef3(true);
2142
+ const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant");
2143
+ const lastUser = [...messages].reverse().find((m) => m.role === "user");
2144
+ useEffect4(() => {
2145
+ mountedRef.current = true;
2146
+ spokenRef.current = lastAssistant?.id ?? null;
2147
+ void voice.startListening();
2148
+ return () => {
2149
+ mountedRef.current = false;
2150
+ };
2151
+ }, []);
2152
+ const replyId = lastAssistant?.id;
2153
+ const replyStreaming = lastAssistant?.streaming === true;
2154
+ useEffect4(() => {
2155
+ if (!lastAssistant || replyStreaming || replyId === spokenRef.current) return;
2156
+ spokenRef.current = replyId ?? null;
2157
+ const run = async () => {
2158
+ if (speakReplies && lastAssistant.content && !lastAssistant.error) {
2159
+ await voice.speak(lastAssistant.content);
2160
+ }
2161
+ if (mountedRef.current) await voice.startListening();
2162
+ };
2163
+ void run();
2164
+ }, [replyId, replyStreaming]);
2165
+ 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");
2166
+ const hint = voice.state === "listening" ? t("voice", "stopTalking") : voice.state === "speaking" ? t("voice", "interrupt") : voice.state === "transcribing" || busy ? "" : t("voice", "talk");
2167
+ const tap = () => {
2168
+ if (voice.state === "listening") voice.stopListening();
2169
+ else if (voice.state === "speaking") {
2170
+ voice.stopSpeaking();
2171
+ void voice.startListening();
2172
+ } else if (voice.state === "idle" && !busy) void voice.startListening();
2173
+ };
2174
+ return /* @__PURE__ */ jsxs5("section", { className: "agi-voice", "aria-label": t("voice", "handsFree"), children: [
2175
+ /* @__PURE__ */ jsx6(
2176
+ "button",
2177
+ {
2178
+ type: "button",
2179
+ className: "agi-voice__exit",
2180
+ onClick: onExit,
2181
+ "aria-label": t("voice", "exit"),
2182
+ children: "\xD7"
2183
+ }
2184
+ ),
2185
+ /* @__PURE__ */ jsx6("div", { className: "agi-voice__status", "aria-live": "polite", children: status }),
2186
+ lastUser?.content && /* @__PURE__ */ jsx6("p", { className: "agi-voice__you", children: lastUser.content }),
2187
+ /* @__PURE__ */ jsx6("div", { className: "agi-voice__reply", children: lastAssistant?.content ? /* @__PURE__ */ jsx6(Markdown, { className: "agi-md", text: lastAssistant.content }) : busy ? /* @__PURE__ */ jsx6(Spinner, { label: t("voice", "thinking") }) : null }),
2188
+ /* @__PURE__ */ jsx6(
2189
+ "button",
2190
+ {
2191
+ type: "button",
2192
+ className: cx(
2193
+ "agi-voice__orb",
2194
+ `agi-voice__orb--${voice.state}`,
2195
+ busy && "agi-voice__orb--busy"
2196
+ ),
2197
+ style: { ["--agi-level"]: voice.level },
2198
+ onClick: tap,
2199
+ "aria-label": hint || status,
2200
+ disabled: voice.state === "transcribing" || voice.state === "unsupported",
2201
+ children: /* @__PURE__ */ jsx6(MicGlyph, { size: 28 })
2202
+ }
2203
+ ),
2204
+ /* @__PURE__ */ jsx6("div", { className: "agi-voice__hint", children: hint }),
2205
+ voice.error && /* @__PURE__ */ jsx6("div", { className: "agi-voice__error", children: voice.error })
2206
+ ] });
2207
+ }
2208
+ function MicGlyph({ size = 16 }) {
2209
+ return /* @__PURE__ */ jsxs5(
2210
+ "svg",
2211
+ {
2212
+ width: size,
2213
+ height: size,
2214
+ viewBox: "0 0 24 24",
2215
+ fill: "none",
2216
+ stroke: "currentColor",
2217
+ strokeWidth: "1.8",
2218
+ strokeLinecap: "round",
2219
+ strokeLinejoin: "round",
2220
+ "aria-hidden": "true",
2221
+ children: [
2222
+ /* @__PURE__ */ jsx6("rect", { x: "9", y: "3", width: "6", height: "11", rx: "3" }),
2223
+ /* @__PURE__ */ jsx6("path", { d: "M5 11a7 7 0 0 0 14 0M12 18v3M9 21h6" })
2224
+ ]
2225
+ }
2226
+ );
2227
+ }
2228
+ function HeadsetGlyph() {
2229
+ return /* @__PURE__ */ jsxs5(
2230
+ "svg",
2231
+ {
2232
+ width: "16",
2233
+ height: "16",
2234
+ viewBox: "0 0 24 24",
2235
+ fill: "none",
2236
+ stroke: "currentColor",
2237
+ strokeWidth: "1.8",
2238
+ strokeLinecap: "round",
2239
+ strokeLinejoin: "round",
2240
+ "aria-hidden": "true",
2241
+ children: [
2242
+ /* @__PURE__ */ jsx6("path", { d: "M4 14v-2a8 8 0 0 1 16 0v2" }),
2243
+ /* @__PURE__ */ jsx6("rect", { x: "3", y: "13", width: "4", height: "6", rx: "1.5" }),
2244
+ /* @__PURE__ */ jsx6("rect", { x: "17", y: "13", width: "4", height: "6", rx: "1.5" }),
2245
+ /* @__PURE__ */ jsx6("path", { d: "M19 19v1a2 2 0 0 1-2 2h-3" })
2246
+ ]
2247
+ }
2248
+ );
2249
+ }
1707
2250
  function readFile(file) {
1708
2251
  return new Promise((resolve, reject) => {
1709
2252
  const reader = new FileReader();
@@ -1741,35 +2284,62 @@ function ChatAuth({
1741
2284
  need,
1742
2285
  onPassword,
1743
2286
  onRequestCode,
1744
- onVerifyCode
2287
+ onVerifyCode,
2288
+ onSso,
2289
+ onRetry
1745
2290
  }) {
1746
- const { t, client } = useAginies();
1747
2291
  if (need === "sso") {
1748
- return /* @__PURE__ */ jsxs5("div", { className: "agi-chat__state", children: [
1749
- /* @__PURE__ */ jsx6("h3", { children: t("auth", "ssoTitle") }),
1750
- /* @__PURE__ */ jsx6("p", { children: t("auth", "ssoHint") }),
1751
- /* @__PURE__ */ jsx6(
1752
- Button,
1753
- {
1754
- variant: "signal",
1755
- onClick: () => window.open(`${client.baseUrl}/chat/`, "_blank", "noopener"),
1756
- children: t("auth", "ssoButton")
1757
- }
1758
- )
1759
- ] });
2292
+ return /* @__PURE__ */ jsx6(SsoAuth, { onSso, onRetry });
1760
2293
  }
1761
2294
  if (need === "email") {
1762
2295
  return /* @__PURE__ */ jsx6(EmailAuth, { onRequestCode, onVerifyCode });
1763
2296
  }
1764
2297
  return /* @__PURE__ */ jsx6(PasswordAuth, { onPassword });
1765
2298
  }
2299
+ function SsoAuth({
2300
+ onSso,
2301
+ onRetry
2302
+ }) {
2303
+ const { t } = useAginies();
2304
+ const [email, setEmail] = useState4("");
2305
+ const [error, setError] = useState4(null);
2306
+ const [pending, setPending] = useState4(false);
2307
+ const [redirected, setRedirected] = useState4(false);
2308
+ const submit = async (e) => {
2309
+ e.preventDefault();
2310
+ setPending(true);
2311
+ setError(null);
2312
+ const result = await onSso(email.trim());
2313
+ setPending(false);
2314
+ if (result === "redirected") setRedirected(true);
2315
+ else setError(t("auth", result === "unauthorized" ? "invalidEmail" : "codeError"));
2316
+ };
2317
+ return /* @__PURE__ */ jsxs5("form", { className: "agi-chat__state agi-chat__auth", onSubmit: submit, children: [
2318
+ /* @__PURE__ */ jsx6("h3", { children: t("auth", "ssoTitle") }),
2319
+ /* @__PURE__ */ jsx6("p", { children: t("auth", "ssoHint") }),
2320
+ /* @__PURE__ */ jsx6(Field, { label: t("auth", "email"), error: error ?? void 0, children: /* @__PURE__ */ jsx6(
2321
+ Input,
2322
+ {
2323
+ type: "email",
2324
+ value: email,
2325
+ onChange: (e) => setEmail(e.target.value),
2326
+ required: true,
2327
+ autoComplete: "email"
2328
+ }
2329
+ ) }),
2330
+ /* @__PURE__ */ jsxs5("div", { className: "agi-chat__auth-actions", children: [
2331
+ /* @__PURE__ */ jsx6(Button, { variant: "signal", type: "submit", disabled: pending || !email.trim(), children: t("auth", "ssoButton") }),
2332
+ redirected && /* @__PURE__ */ jsx6(Button, { variant: "ghost", type: "button", onClick: onRetry, children: t("auth", "ssoRetry") })
2333
+ ] })
2334
+ ] });
2335
+ }
1766
2336
  function PasswordAuth({
1767
2337
  onPassword
1768
2338
  }) {
1769
2339
  const { t } = useAginies();
1770
- const [value, setValue] = useState3("");
1771
- const [error, setError] = useState3(null);
1772
- const [pending, setPending] = useState3(false);
2340
+ const [value, setValue] = useState4("");
2341
+ const [error, setError] = useState4(null);
2342
+ const [pending, setPending] = useState4(false);
1773
2343
  const submit = async (e) => {
1774
2344
  e.preventDefault();
1775
2345
  setPending(true);
@@ -1799,12 +2369,12 @@ function EmailAuth({
1799
2369
  onVerifyCode
1800
2370
  }) {
1801
2371
  const { t } = useAginies();
1802
- const [email, setEmail] = useState3("");
1803
- const [code, setCode] = useState3("");
1804
- const [step, setStep] = useState3("email");
1805
- const [error, setError] = useState3(null);
1806
- const [notice, setNotice] = useState3(null);
1807
- const [pending, setPending] = useState3(false);
2372
+ const [email, setEmail] = useState4("");
2373
+ const [code, setCode] = useState4("");
2374
+ const [step, setStep] = useState4("email");
2375
+ const [error, setError] = useState4(null);
2376
+ const [notice, setNotice] = useState4(null);
2377
+ const [pending, setPending] = useState4(false);
1808
2378
  const request = async () => {
1809
2379
  setPending(true);
1810
2380
  setError(null);
@@ -2015,7 +2585,7 @@ function CostBars({ items, format = usd, className, ...props }) {
2015
2585
  }
2016
2586
 
2017
2587
  // src/run/agent-runner.tsx
2018
- import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
2588
+ import { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
2019
2589
 
2020
2590
  // src/run/run-client.ts
2021
2591
  async function* runAgent(client, workflowId, options, signal) {
@@ -2155,19 +2725,19 @@ function decodeRunFrame(frame) {
2155
2725
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2156
2726
  function useAgentRun(workflowId, apiKey) {
2157
2727
  const { client, t } = useAginies();
2158
- const [status, setStatus] = useState4("idle");
2159
- const [text, setText] = useState4("");
2160
- const [steps, setSteps] = useState4([]);
2161
- const [output, setOutput] = useState4(null);
2162
- const [error, setError] = useState4(null);
2163
- const [executionId, setExecutionId] = useState4();
2164
- const abortRef = useRef3(null);
2165
- const cancel = useCallback3(() => {
2728
+ const [status, setStatus] = useState5("idle");
2729
+ const [text, setText] = useState5("");
2730
+ const [steps, setSteps] = useState5([]);
2731
+ const [output, setOutput] = useState5(null);
2732
+ const [error, setError] = useState5(null);
2733
+ const [executionId, setExecutionId] = useState5();
2734
+ const abortRef = useRef4(null);
2735
+ const cancel = useCallback4(() => {
2166
2736
  abortRef.current?.abort();
2167
2737
  abortRef.current = null;
2168
2738
  setStatus((s) => s === "running" ? "idle" : s);
2169
2739
  }, []);
2170
- const run = useCallback3(
2740
+ const run = useCallback4(
2171
2741
  async (input) => {
2172
2742
  abortRef.current?.abort();
2173
2743
  const controller = new AbortController();
@@ -2244,7 +2814,7 @@ function AgentRunner({
2244
2814
  const { t } = useAginies();
2245
2815
  const enabled = useModule("run");
2246
2816
  const agent = useAgentRun(workflowId, apiKey);
2247
- const [values, setValues] = useState4(
2817
+ const [values, setValues] = useState5(
2248
2818
  () => Object.fromEntries(
2249
2819
  fields.map((f) => [f.name, f.defaultValue ?? (f.type === "boolean" ? false : "")])
2250
2820
  )
@@ -2256,9 +2826,9 @@ function AgentRunner({
2256
2826
  const input = fields.length === 1 && fields[0]?.name === "input" && fields[0]?.type === "textarea" ? values.input : values;
2257
2827
  await agent.run(input);
2258
2828
  };
2259
- const onResultRef = useRef3(onResult);
2829
+ const onResultRef = useRef4(onResult);
2260
2830
  onResultRef.current = onResult;
2261
- useEffect4(() => {
2831
+ useEffect5(() => {
2262
2832
  if (agent.status === "done") onResultRef.current?.(agent.output);
2263
2833
  }, [agent.status, agent.output]);
2264
2834
  if (!enabled) return null;
@@ -2378,6 +2948,7 @@ export {
2378
2948
  getPausedExecution,
2379
2949
  init,
2380
2950
  initialValues,
2951
+ isVoiceSupported,
2381
2952
  listPausedExecutions,
2382
2953
  outputOf,
2383
2954
  parseFieldValue,
@@ -2391,6 +2962,7 @@ export {
2391
2962
  useAginies,
2392
2963
  useApproval,
2393
2964
  useChat,
2394
- useModule
2965
+ useModule,
2966
+ useVoice
2395
2967
  };
2396
2968
  //# sourceMappingURL=index.js.map