@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/README.md +6 -0
- package/dist/index.cjs +677 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +140 -5
- package/dist/index.d.ts +140 -5
- package/dist/index.js +674 -102
- package/dist/index.js.map +1 -1
- package/dist/styles.css +118 -0
- package/package.json +1 -1
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,8 @@ var trimSlash = (s) => s.replace(/\/+$/, "");
|
|
|
85
87
|
var AginiesClient = class {
|
|
86
88
|
baseUrl;
|
|
87
89
|
token;
|
|
90
|
+
/** True when activated from the platform's own page. */
|
|
91
|
+
hosted;
|
|
88
92
|
locale;
|
|
89
93
|
fetchImpl;
|
|
90
94
|
state = { status: "idle" };
|
|
@@ -93,9 +97,12 @@ var AginiesClient = class {
|
|
|
93
97
|
session = null;
|
|
94
98
|
constructor(config) {
|
|
95
99
|
if (!config?.baseUrl) throw new AginiesError("baseUrl is required", 0, "MISSING_BASE_URL");
|
|
96
|
-
if (!config?.token
|
|
100
|
+
if (!config?.token && !config?.hosted) {
|
|
101
|
+
throw new AginiesError("token is required", 0, "MISSING_TOKEN");
|
|
102
|
+
}
|
|
97
103
|
this.baseUrl = trimSlash(config.baseUrl);
|
|
98
|
-
this.token = config.token;
|
|
104
|
+
this.token = config.token ?? null;
|
|
105
|
+
this.hosted = config.hosted === true;
|
|
99
106
|
this.locale = config.locale ?? detectLocale();
|
|
100
107
|
this.fetchImpl = config.fetch ?? ((...args) => fetch(...args));
|
|
101
108
|
}
|
|
@@ -122,7 +129,7 @@ var AginiesClient = class {
|
|
|
122
129
|
const res = await this.fetchImpl(`${this.baseUrl}/api/ui/activate`, {
|
|
123
130
|
method: "POST",
|
|
124
131
|
headers: { "Content-Type": "application/json" },
|
|
125
|
-
body: JSON.stringify({ token: this.token })
|
|
132
|
+
body: JSON.stringify(this.hosted ? { hosted: true } : { token: this.token })
|
|
126
133
|
});
|
|
127
134
|
if (!res.ok) {
|
|
128
135
|
const body2 = await safeJson(res);
|
|
@@ -197,6 +204,30 @@ var AginiesClient = class {
|
|
|
197
204
|
this.assertActive();
|
|
198
205
|
return this.request(path2, init2);
|
|
199
206
|
}
|
|
207
|
+
/** Sends a recording to the platform's transcription model. */
|
|
208
|
+
async transcribe(identifier, audio, filename = "turn.webm", language) {
|
|
209
|
+
this.assertActive();
|
|
210
|
+
const form = new FormData();
|
|
211
|
+
form.append("file", audio, filename);
|
|
212
|
+
if (language) form.append("language", language);
|
|
213
|
+
const res = await this.request(`/api/chat/${encodeURIComponent(identifier)}/voice/transcribe`, {
|
|
214
|
+
method: "POST",
|
|
215
|
+
body: form
|
|
216
|
+
});
|
|
217
|
+
if (!res.ok) throw await voiceError(res);
|
|
218
|
+
return await res.json();
|
|
219
|
+
}
|
|
220
|
+
/** Audio for a reply from the platform's synthesis endpoint. */
|
|
221
|
+
async speak(identifier, text) {
|
|
222
|
+
this.assertActive();
|
|
223
|
+
const res = await this.request(`/api/chat/${encodeURIComponent(identifier)}/voice/speak`, {
|
|
224
|
+
method: "POST",
|
|
225
|
+
headers: { "Content-Type": "application/json" },
|
|
226
|
+
body: JSON.stringify({ text })
|
|
227
|
+
});
|
|
228
|
+
if (!res.ok) throw await voiceError(res);
|
|
229
|
+
return res.blob();
|
|
230
|
+
}
|
|
200
231
|
/** Hosted-chat configuration. Resolves to an auth requirement instead of throwing on 401. */
|
|
201
232
|
async getChat(identifier) {
|
|
202
233
|
this.assertActive();
|
|
@@ -286,6 +317,15 @@ function decodeFrame(frame) {
|
|
|
286
317
|
}
|
|
287
318
|
return null;
|
|
288
319
|
}
|
|
320
|
+
async function voiceError(res) {
|
|
321
|
+
const body = await safeJson(res);
|
|
322
|
+
if (res.status === 401 && body && typeof body.authRequired === "string") {
|
|
323
|
+
return new AginiesError("Authentication required", 401, "AUTH_REQUIRED");
|
|
324
|
+
}
|
|
325
|
+
if (res.status === 503)
|
|
326
|
+
return new AginiesError("Voice is not available", 503, "VOICE_UNAVAILABLE");
|
|
327
|
+
return new AginiesError(body?.error || `HTTP ${res.status}`, res.status);
|
|
328
|
+
}
|
|
289
329
|
async function safeJson(res) {
|
|
290
330
|
try {
|
|
291
331
|
return await res.json();
|
|
@@ -582,7 +622,37 @@ var STRINGS = {
|
|
|
582
622
|
tr: "Bu sohbet kurumsal kimlikle a\xE7\u0131l\u0131r.",
|
|
583
623
|
en: "This chat opens with your organisation account."
|
|
584
624
|
},
|
|
585
|
-
ssoButton: { tr: "Kurumsal giri\u015F", en: "Sign in" }
|
|
625
|
+
ssoButton: { tr: "Kurumsal giri\u015F", en: "Sign in" },
|
|
626
|
+
ssoRetry: { tr: "Giri\u015F yapt\u0131m, yenile", en: "I have signed in, refresh" }
|
|
627
|
+
},
|
|
628
|
+
voice: {
|
|
629
|
+
talk: { tr: "Konu\u015Fmak i\xE7in dokunun", en: "Tap to talk" },
|
|
630
|
+
stopTalking: { tr: "Bitirmek i\xE7in dokunun", en: "Tap when done" },
|
|
631
|
+
dictate: { tr: "Sesle yaz", en: "Dictate" },
|
|
632
|
+
handsFree: { tr: "Sesli sohbet", en: "Voice conversation" },
|
|
633
|
+
exit: { tr: "Sesli sohbetten \xE7\u0131k", en: "Leave voice conversation" },
|
|
634
|
+
listening: { tr: "Dinliyor", en: "Listening" },
|
|
635
|
+
transcribing: { tr: "Yaz\u0131ya d\xF6k\xFCl\xFCyor", en: "Transcribing" },
|
|
636
|
+
thinking: { tr: "Yan\u0131t haz\u0131rlan\u0131yor", en: "Working on it" },
|
|
637
|
+
speaking: { tr: "Konu\u015Fuyor", en: "Speaking" },
|
|
638
|
+
idle: { tr: "Haz\u0131r", en: "Ready" },
|
|
639
|
+
interrupt: { tr: "S\xF6z\xFCn\xFC kesmek i\xE7in dokunun", en: "Tap to interrupt" },
|
|
640
|
+
micDenied: {
|
|
641
|
+
tr: "Mikrofon izni verilmedi. Taray\u0131c\u0131 ayarlar\u0131ndan izin verin.",
|
|
642
|
+
en: "Microphone access was denied. Allow it in your browser settings."
|
|
643
|
+
},
|
|
644
|
+
unsupported: {
|
|
645
|
+
tr: "Bu taray\u0131c\u0131 ses kayd\u0131n\u0131 desteklemiyor.",
|
|
646
|
+
en: "This browser cannot record audio."
|
|
647
|
+
},
|
|
648
|
+
unavailable: {
|
|
649
|
+
tr: "Ses \xF6zelli\u011Fi bu sohbet i\xE7in a\xE7\u0131k de\u011Fil.",
|
|
650
|
+
en: "Voice is not enabled for this chat."
|
|
651
|
+
},
|
|
652
|
+
error: {
|
|
653
|
+
tr: "Ses i\u015Flenemedi. L\xFCtfen tekrar deneyin.",
|
|
654
|
+
en: "Voice could not be processed. Please try again."
|
|
655
|
+
}
|
|
586
656
|
},
|
|
587
657
|
run: {
|
|
588
658
|
submit: { tr: "\xC7al\u0131\u015Ft\u0131r", en: "Run" },
|
|
@@ -1034,7 +1104,7 @@ function renderField(f, value, set, disabled) {
|
|
|
1034
1104
|
}
|
|
1035
1105
|
|
|
1036
1106
|
// src/chat/chat-widget.tsx
|
|
1037
|
-
var
|
|
1107
|
+
var import_react6 = require("react");
|
|
1038
1108
|
|
|
1039
1109
|
// src/chat/markdown.tsx
|
|
1040
1110
|
var import_react4 = require("react");
|
|
@@ -1344,20 +1414,258 @@ function Pie({ labels, data }) {
|
|
|
1344
1414
|
] });
|
|
1345
1415
|
}
|
|
1346
1416
|
|
|
1417
|
+
// src/chat/voice.ts
|
|
1418
|
+
var import_react5 = require("react");
|
|
1419
|
+
var MIME_CANDIDATES = [
|
|
1420
|
+
"audio/webm;codecs=opus",
|
|
1421
|
+
"audio/webm",
|
|
1422
|
+
"audio/mp4",
|
|
1423
|
+
"audio/ogg;codecs=opus"
|
|
1424
|
+
];
|
|
1425
|
+
var MIN_CLIP_BYTES = 800;
|
|
1426
|
+
var SPEECH_RMS = 0.02;
|
|
1427
|
+
function isVoiceSupported() {
|
|
1428
|
+
return typeof window !== "undefined" && typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && typeof MediaRecorder !== "undefined";
|
|
1429
|
+
}
|
|
1430
|
+
function useVoice({
|
|
1431
|
+
identifier,
|
|
1432
|
+
onTranscript,
|
|
1433
|
+
language,
|
|
1434
|
+
silenceMs = 1400,
|
|
1435
|
+
maxMs = 6e4
|
|
1436
|
+
}) {
|
|
1437
|
+
const { client, t } = useAginies();
|
|
1438
|
+
const supported = isVoiceSupported();
|
|
1439
|
+
const [state, setState] = (0, import_react5.useState)(supported ? "idle" : "unsupported");
|
|
1440
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
1441
|
+
const [level, setLevel] = (0, import_react5.useState)(0);
|
|
1442
|
+
const recorderRef = (0, import_react5.useRef)(null);
|
|
1443
|
+
const streamRef = (0, import_react5.useRef)(null);
|
|
1444
|
+
const chunksRef = (0, import_react5.useRef)([]);
|
|
1445
|
+
const discardRef = (0, import_react5.useRef)(false);
|
|
1446
|
+
const timersRef = (0, import_react5.useRef)([]);
|
|
1447
|
+
const meterRef = (0, import_react5.useRef)(null);
|
|
1448
|
+
const audioRef = (0, import_react5.useRef)(null);
|
|
1449
|
+
const audioUrlRef = (0, import_react5.useRef)(null);
|
|
1450
|
+
const startingRef = (0, import_react5.useRef)(false);
|
|
1451
|
+
const onTranscriptRef = (0, import_react5.useRef)(onTranscript);
|
|
1452
|
+
onTranscriptRef.current = onTranscript;
|
|
1453
|
+
const clearTimers = (0, import_react5.useCallback)(() => {
|
|
1454
|
+
for (const id of timersRef.current) window.clearTimeout(id);
|
|
1455
|
+
timersRef.current = [];
|
|
1456
|
+
if (meterRef.current) {
|
|
1457
|
+
window.clearInterval(meterRef.current.interval);
|
|
1458
|
+
void meterRef.current.ctx.close().catch(() => {
|
|
1459
|
+
});
|
|
1460
|
+
meterRef.current = null;
|
|
1461
|
+
}
|
|
1462
|
+
setLevel(0);
|
|
1463
|
+
}, []);
|
|
1464
|
+
const releaseStream = (0, import_react5.useCallback)(() => {
|
|
1465
|
+
for (const track of streamRef.current?.getTracks() ?? []) track.stop();
|
|
1466
|
+
streamRef.current = null;
|
|
1467
|
+
}, []);
|
|
1468
|
+
const stopSpeaking = (0, import_react5.useCallback)(() => {
|
|
1469
|
+
const audio = audioRef.current;
|
|
1470
|
+
if (audio) {
|
|
1471
|
+
audio.pause();
|
|
1472
|
+
audio.src = "";
|
|
1473
|
+
audioRef.current = null;
|
|
1474
|
+
}
|
|
1475
|
+
if (audioUrlRef.current) {
|
|
1476
|
+
URL.revokeObjectURL(audioUrlRef.current);
|
|
1477
|
+
audioUrlRef.current = null;
|
|
1478
|
+
}
|
|
1479
|
+
setState((s) => s === "speaking" ? "idle" : s);
|
|
1480
|
+
}, []);
|
|
1481
|
+
const finish = (0, import_react5.useCallback)((recorder) => {
|
|
1482
|
+
recorder.stop();
|
|
1483
|
+
}, []);
|
|
1484
|
+
const stopListening = (0, import_react5.useCallback)(() => {
|
|
1485
|
+
const recorder = recorderRef.current;
|
|
1486
|
+
if (!recorder || recorder.state === "inactive") return;
|
|
1487
|
+
discardRef.current = false;
|
|
1488
|
+
finish(recorder);
|
|
1489
|
+
}, [finish]);
|
|
1490
|
+
const cancelListening = (0, import_react5.useCallback)(() => {
|
|
1491
|
+
const recorder = recorderRef.current;
|
|
1492
|
+
if (!recorder || recorder.state === "inactive") return;
|
|
1493
|
+
discardRef.current = true;
|
|
1494
|
+
finish(recorder);
|
|
1495
|
+
}, [finish]);
|
|
1496
|
+
const startMeter = (0, import_react5.useCallback)(
|
|
1497
|
+
(stream, onSilence) => {
|
|
1498
|
+
const Ctx = typeof AudioContext !== "undefined" ? AudioContext : window.webkitAudioContext;
|
|
1499
|
+
if (!Ctx) return;
|
|
1500
|
+
try {
|
|
1501
|
+
const ctx = new Ctx();
|
|
1502
|
+
const analyser = ctx.createAnalyser();
|
|
1503
|
+
analyser.fftSize = 2048;
|
|
1504
|
+
ctx.createMediaStreamSource(stream).connect(analyser);
|
|
1505
|
+
const data = new Uint8Array(analyser.fftSize);
|
|
1506
|
+
let heard = false;
|
|
1507
|
+
let lastVoice = Date.now();
|
|
1508
|
+
const interval = window.setInterval(() => {
|
|
1509
|
+
analyser.getByteTimeDomainData(data);
|
|
1510
|
+
let sum = 0;
|
|
1511
|
+
for (const v of data) {
|
|
1512
|
+
const n = (v - 128) / 128;
|
|
1513
|
+
sum += n * n;
|
|
1514
|
+
}
|
|
1515
|
+
const rms = Math.sqrt(sum / data.length);
|
|
1516
|
+
setLevel(Math.min(1, rms * 6));
|
|
1517
|
+
if (rms > SPEECH_RMS) {
|
|
1518
|
+
heard = true;
|
|
1519
|
+
lastVoice = Date.now();
|
|
1520
|
+
} else if (heard && Date.now() - lastVoice > silenceMs) {
|
|
1521
|
+
onSilence();
|
|
1522
|
+
}
|
|
1523
|
+
}, 100);
|
|
1524
|
+
meterRef.current = { ctx, interval };
|
|
1525
|
+
} catch {
|
|
1526
|
+
}
|
|
1527
|
+
},
|
|
1528
|
+
[silenceMs]
|
|
1529
|
+
);
|
|
1530
|
+
const startListening = (0, import_react5.useCallback)(async () => {
|
|
1531
|
+
if (!supported) {
|
|
1532
|
+
setState("unsupported");
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
if (startingRef.current) return;
|
|
1536
|
+
if (recorderRef.current && recorderRef.current.state !== "inactive") return;
|
|
1537
|
+
startingRef.current = true;
|
|
1538
|
+
stopSpeaking();
|
|
1539
|
+
setError(null);
|
|
1540
|
+
let stream;
|
|
1541
|
+
try {
|
|
1542
|
+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
1543
|
+
} catch {
|
|
1544
|
+
startingRef.current = false;
|
|
1545
|
+
setState("denied");
|
|
1546
|
+
setError(t("voice", "micDenied"));
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
startingRef.current = false;
|
|
1550
|
+
streamRef.current = stream;
|
|
1551
|
+
const mimeType = MIME_CANDIDATES.find((m) => MediaRecorder.isTypeSupported?.(m));
|
|
1552
|
+
const recorder = new MediaRecorder(stream, mimeType ? { mimeType } : void 0);
|
|
1553
|
+
recorderRef.current = recorder;
|
|
1554
|
+
chunksRef.current = [];
|
|
1555
|
+
discardRef.current = false;
|
|
1556
|
+
recorder.ondataavailable = (e) => {
|
|
1557
|
+
if (e.data && e.data.size > 0) chunksRef.current.push(e.data);
|
|
1558
|
+
};
|
|
1559
|
+
recorder.onstop = () => {
|
|
1560
|
+
clearTimers();
|
|
1561
|
+
releaseStream();
|
|
1562
|
+
recorderRef.current = null;
|
|
1563
|
+
const type = recorder.mimeType || mimeType || "audio/webm";
|
|
1564
|
+
const clip = new Blob(chunksRef.current, { type });
|
|
1565
|
+
chunksRef.current = [];
|
|
1566
|
+
if (discardRef.current || clip.size < MIN_CLIP_BYTES) {
|
|
1567
|
+
setState("idle");
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
setState("transcribing");
|
|
1571
|
+
const ext = type.includes("mp4") ? "m4a" : type.includes("ogg") ? "ogg" : "webm";
|
|
1572
|
+
client.transcribe(identifier, clip, `turn.${ext}`, language).then((result) => {
|
|
1573
|
+
setState("idle");
|
|
1574
|
+
if (result.transcript) onTranscriptRef.current(result.transcript);
|
|
1575
|
+
}).catch((err) => {
|
|
1576
|
+
setState("idle");
|
|
1577
|
+
setError(
|
|
1578
|
+
err instanceof AginiesError && err.code === "VOICE_UNAVAILABLE" ? t("voice", "unavailable") : t("voice", "error")
|
|
1579
|
+
);
|
|
1580
|
+
});
|
|
1581
|
+
};
|
|
1582
|
+
recorder.start(250);
|
|
1583
|
+
setState("listening");
|
|
1584
|
+
startMeter(stream, () => stopListening());
|
|
1585
|
+
timersRef.current.push(window.setTimeout(() => stopListening(), maxMs));
|
|
1586
|
+
}, [
|
|
1587
|
+
supported,
|
|
1588
|
+
stopSpeaking,
|
|
1589
|
+
t,
|
|
1590
|
+
client,
|
|
1591
|
+
identifier,
|
|
1592
|
+
language,
|
|
1593
|
+
maxMs,
|
|
1594
|
+
stopListening,
|
|
1595
|
+
clearTimers,
|
|
1596
|
+
releaseStream,
|
|
1597
|
+
startMeter
|
|
1598
|
+
]);
|
|
1599
|
+
const speak = (0, import_react5.useCallback)(
|
|
1600
|
+
async (text) => {
|
|
1601
|
+
stopSpeaking();
|
|
1602
|
+
setError(null);
|
|
1603
|
+
setState("speaking");
|
|
1604
|
+
try {
|
|
1605
|
+
const blob = await client.speak(identifier, text);
|
|
1606
|
+
const url = URL.createObjectURL(blob);
|
|
1607
|
+
audioUrlRef.current = url;
|
|
1608
|
+
const audio = new Audio(url);
|
|
1609
|
+
audioRef.current = audio;
|
|
1610
|
+
await new Promise((resolve) => {
|
|
1611
|
+
audio.onended = () => resolve();
|
|
1612
|
+
audio.onerror = () => resolve();
|
|
1613
|
+
audio.onpause = () => resolve();
|
|
1614
|
+
audio.play().catch(() => resolve());
|
|
1615
|
+
});
|
|
1616
|
+
} catch (err) {
|
|
1617
|
+
setError(
|
|
1618
|
+
err instanceof AginiesError && err.code === "VOICE_UNAVAILABLE" ? t("voice", "unavailable") : t("voice", "error")
|
|
1619
|
+
);
|
|
1620
|
+
} finally {
|
|
1621
|
+
if (audioUrlRef.current) URL.revokeObjectURL(audioUrlRef.current);
|
|
1622
|
+
audioUrlRef.current = null;
|
|
1623
|
+
audioRef.current = null;
|
|
1624
|
+
setState((s) => s === "speaking" ? "idle" : s);
|
|
1625
|
+
}
|
|
1626
|
+
},
|
|
1627
|
+
[client, identifier, stopSpeaking, t]
|
|
1628
|
+
);
|
|
1629
|
+
(0, import_react5.useEffect)(
|
|
1630
|
+
() => () => {
|
|
1631
|
+
discardRef.current = true;
|
|
1632
|
+
if (recorderRef.current && recorderRef.current.state !== "inactive")
|
|
1633
|
+
recorderRef.current.stop();
|
|
1634
|
+
clearTimers();
|
|
1635
|
+
releaseStream();
|
|
1636
|
+
const audio = audioRef.current;
|
|
1637
|
+
if (audio) audio.pause();
|
|
1638
|
+
if (audioUrlRef.current) URL.revokeObjectURL(audioUrlRef.current);
|
|
1639
|
+
},
|
|
1640
|
+
[clearTimers, releaseStream]
|
|
1641
|
+
);
|
|
1642
|
+
return {
|
|
1643
|
+
state,
|
|
1644
|
+
error,
|
|
1645
|
+
supported,
|
|
1646
|
+
level,
|
|
1647
|
+
startListening,
|
|
1648
|
+
stopListening,
|
|
1649
|
+
cancelListening,
|
|
1650
|
+
speak,
|
|
1651
|
+
stopSpeaking
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1347
1655
|
// src/chat/chat-widget.tsx
|
|
1348
1656
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1349
1657
|
var ATTACHMENT_LIMITS = { maxFiles: 5, maxBytes: 10 * 1024 * 1024 };
|
|
1350
1658
|
function useChat(identifier, enabled = true) {
|
|
1351
1659
|
const { client, t } = useAginies();
|
|
1352
|
-
const [config, setConfig] = (0,
|
|
1353
|
-
const [authNeed, setAuthNeed] = (0,
|
|
1354
|
-
const [authTitle, setAuthTitle] = (0,
|
|
1355
|
-
const [loadError, setLoadError] = (0,
|
|
1356
|
-
const [messages, setMessages] = (0,
|
|
1357
|
-
const [busy, setBusy] = (0,
|
|
1358
|
-
const [conversationId] = (0,
|
|
1359
|
-
const abortRef = (0,
|
|
1360
|
-
const load = (0,
|
|
1660
|
+
const [config, setConfig] = (0, import_react6.useState)(null);
|
|
1661
|
+
const [authNeed, setAuthNeed] = (0, import_react6.useState)(null);
|
|
1662
|
+
const [authTitle, setAuthTitle] = (0, import_react6.useState)();
|
|
1663
|
+
const [loadError, setLoadError] = (0, import_react6.useState)(null);
|
|
1664
|
+
const [messages, setMessages] = (0, import_react6.useState)([]);
|
|
1665
|
+
const [busy, setBusy] = (0, import_react6.useState)(false);
|
|
1666
|
+
const [conversationId] = (0, import_react6.useState)(() => randomId());
|
|
1667
|
+
const abortRef = (0, import_react6.useRef)(null);
|
|
1668
|
+
const load = (0, import_react6.useCallback)(async () => {
|
|
1361
1669
|
setLoadError(null);
|
|
1362
1670
|
try {
|
|
1363
1671
|
const res = await client.getChat(identifier);
|
|
@@ -1374,11 +1682,11 @@ function useChat(identifier, enabled = true) {
|
|
|
1374
1682
|
);
|
|
1375
1683
|
}
|
|
1376
1684
|
}, [client, identifier, t]);
|
|
1377
|
-
(0,
|
|
1685
|
+
(0, import_react6.useEffect)(() => {
|
|
1378
1686
|
if (enabled) void load();
|
|
1379
1687
|
}, [load, enabled]);
|
|
1380
1688
|
const chatPath = `/api/chat/${encodeURIComponent(identifier)}`;
|
|
1381
|
-
const authenticate = (0,
|
|
1689
|
+
const authenticate = (0, import_react6.useCallback)(
|
|
1382
1690
|
async ({ password }) => {
|
|
1383
1691
|
const res = await client.fetchRaw(chatPath, {
|
|
1384
1692
|
method: "POST",
|
|
@@ -1393,7 +1701,27 @@ function useChat(identifier, enabled = true) {
|
|
|
1393
1701
|
},
|
|
1394
1702
|
[client, chatPath, conversationId, load]
|
|
1395
1703
|
);
|
|
1396
|
-
const
|
|
1704
|
+
const signInWithSso = (0, import_react6.useCallback)(
|
|
1705
|
+
async (email) => {
|
|
1706
|
+
const res = await client.fetchRaw(chatPath, {
|
|
1707
|
+
method: "POST",
|
|
1708
|
+
headers: { "Content-Type": "application/json" },
|
|
1709
|
+
body: JSON.stringify({ email, checkSSOAccess: true })
|
|
1710
|
+
});
|
|
1711
|
+
if (!res.ok) return res.status === 401 || res.status === 403 ? "unauthorized" : "error";
|
|
1712
|
+
const hostedPage = `${client.baseUrl}/chat/${encodeURIComponent(identifier)}`;
|
|
1713
|
+
if (client.hosted) {
|
|
1714
|
+
window.location.assign(
|
|
1715
|
+
`${client.baseUrl}/sso?email=${encodeURIComponent(email)}&callbackUrl=${encodeURIComponent(window.location.pathname)}`
|
|
1716
|
+
);
|
|
1717
|
+
} else {
|
|
1718
|
+
window.open(hostedPage, "_blank", "noopener");
|
|
1719
|
+
}
|
|
1720
|
+
return "redirected";
|
|
1721
|
+
},
|
|
1722
|
+
[client, chatPath, identifier]
|
|
1723
|
+
);
|
|
1724
|
+
const requestCode = (0, import_react6.useCallback)(
|
|
1397
1725
|
async (email) => {
|
|
1398
1726
|
const res = await client.fetchRaw(`${chatPath}/otp`, {
|
|
1399
1727
|
method: "POST",
|
|
@@ -1405,7 +1733,7 @@ function useChat(identifier, enabled = true) {
|
|
|
1405
1733
|
},
|
|
1406
1734
|
[client, chatPath]
|
|
1407
1735
|
);
|
|
1408
|
-
const verifyCode = (0,
|
|
1736
|
+
const verifyCode = (0, import_react6.useCallback)(
|
|
1409
1737
|
async (email, otp) => {
|
|
1410
1738
|
const res = await client.fetchRaw(`${chatPath}/otp`, {
|
|
1411
1739
|
method: "PUT",
|
|
@@ -1420,7 +1748,7 @@ function useChat(identifier, enabled = true) {
|
|
|
1420
1748
|
},
|
|
1421
1749
|
[client, chatPath, load]
|
|
1422
1750
|
);
|
|
1423
|
-
const stop = (0,
|
|
1751
|
+
const stop = (0, import_react6.useCallback)(() => {
|
|
1424
1752
|
abortRef.current?.abort();
|
|
1425
1753
|
abortRef.current = null;
|
|
1426
1754
|
setBusy(false);
|
|
@@ -1433,7 +1761,7 @@ function useChat(identifier, enabled = true) {
|
|
|
1433
1761
|
];
|
|
1434
1762
|
});
|
|
1435
1763
|
}, [t]);
|
|
1436
|
-
const send = (0,
|
|
1764
|
+
const send = (0, import_react6.useCallback)(
|
|
1437
1765
|
async (text, files = []) => {
|
|
1438
1766
|
const input = text.trim();
|
|
1439
1767
|
if (!input && files.length === 0 || busy) return;
|
|
@@ -1468,6 +1796,14 @@ function useChat(identifier, enabled = true) {
|
|
|
1468
1796
|
if (text2 && !content) {
|
|
1469
1797
|
content = text2;
|
|
1470
1798
|
update({ content });
|
|
1799
|
+
} else if (!content) {
|
|
1800
|
+
const raw = extractFinalOutput(ev.data);
|
|
1801
|
+
if (raw) {
|
|
1802
|
+
content = `\`\`\`json
|
|
1803
|
+
${raw}
|
|
1804
|
+
\`\`\``;
|
|
1805
|
+
update({ content });
|
|
1806
|
+
}
|
|
1471
1807
|
}
|
|
1472
1808
|
} else if (ev.type === "error") {
|
|
1473
1809
|
update({ content: ev.message, error: true, streaming: false });
|
|
@@ -1498,6 +1834,7 @@ function useChat(identifier, enabled = true) {
|
|
|
1498
1834
|
authenticate,
|
|
1499
1835
|
requestCode,
|
|
1500
1836
|
verifyCode,
|
|
1837
|
+
signInWithSso,
|
|
1501
1838
|
reload: load
|
|
1502
1839
|
};
|
|
1503
1840
|
}
|
|
@@ -1514,6 +1851,14 @@ function extractFinalText(data) {
|
|
|
1514
1851
|
}
|
|
1515
1852
|
return null;
|
|
1516
1853
|
}
|
|
1854
|
+
function extractFinalOutput(data) {
|
|
1855
|
+
if (!data || typeof data !== "object") return null;
|
|
1856
|
+
const d = data;
|
|
1857
|
+
const payload = d.output ?? d.error;
|
|
1858
|
+
if (payload === void 0 || payload === null) return null;
|
|
1859
|
+
const text = typeof payload === "string" ? payload : JSON.stringify(payload, null, 2);
|
|
1860
|
+
return text.trim() ? text : null;
|
|
1861
|
+
}
|
|
1517
1862
|
function randomId() {
|
|
1518
1863
|
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
1519
1864
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -1525,15 +1870,38 @@ function ChatWidget({
|
|
|
1525
1870
|
launcherLabel,
|
|
1526
1871
|
defaultOpen = false,
|
|
1527
1872
|
theme = "auto",
|
|
1873
|
+
voice = true,
|
|
1528
1874
|
className
|
|
1529
1875
|
}) {
|
|
1530
|
-
const [open, setOpen] = (0,
|
|
1531
|
-
const { t } = useAginies();
|
|
1876
|
+
const [open, setOpen] = (0, import_react6.useState)(defaultOpen || mode !== "bubble");
|
|
1877
|
+
const { t, client } = useAginies();
|
|
1532
1878
|
const enabled = useModule("chat");
|
|
1533
1879
|
const chat = useChat(identifier, enabled);
|
|
1880
|
+
const [voiceMode, setVoiceMode] = (0, import_react6.useState)(false);
|
|
1881
|
+
const [dictation, setDictation] = (0, import_react6.useState)(null);
|
|
1882
|
+
const voiceModeRef = (0, import_react6.useRef)(voiceMode);
|
|
1883
|
+
voiceModeRef.current = voiceMode;
|
|
1884
|
+
const sendRef = (0, import_react6.useRef)(chat.send);
|
|
1885
|
+
sendRef.current = chat.send;
|
|
1886
|
+
const voiceControls = useVoice({
|
|
1887
|
+
identifier,
|
|
1888
|
+
onTranscript: (text) => {
|
|
1889
|
+
if (voiceModeRef.current) void sendRef.current(text);
|
|
1890
|
+
else setDictation({ id: Date.now(), text });
|
|
1891
|
+
}
|
|
1892
|
+
});
|
|
1893
|
+
const capabilities = chat.config?.voice;
|
|
1894
|
+
const voiceAvailable = voice && voiceControls.supported && capabilities?.stt === true;
|
|
1895
|
+
const state = client.getState();
|
|
1896
|
+
const brand = state.status === "active" ? state.config.brand : { name: "Aginies", logoUrl: null };
|
|
1534
1897
|
const title = chat.config?.title ?? chat.authTitle ?? launcherLabel ?? "Aginies";
|
|
1535
1898
|
const themeClass = theme === "auto" ? void 0 : theme === "dark" ? "dark" : "light";
|
|
1536
1899
|
if (!enabled) return null;
|
|
1900
|
+
const leaveVoiceMode = () => {
|
|
1901
|
+
voiceControls.cancelListening();
|
|
1902
|
+
voiceControls.stopSpeaking();
|
|
1903
|
+
setVoiceMode(false);
|
|
1904
|
+
};
|
|
1537
1905
|
const panel = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1538
1906
|
"section",
|
|
1539
1907
|
{
|
|
@@ -1569,9 +1937,20 @@ function ChatWidget({
|
|
|
1569
1937
|
need: chat.authNeed,
|
|
1570
1938
|
onPassword: chat.authenticate,
|
|
1571
1939
|
onRequestCode: chat.requestCode,
|
|
1572
|
-
onVerifyCode: chat.verifyCode
|
|
1940
|
+
onVerifyCode: chat.verifyCode,
|
|
1941
|
+
onSso: chat.signInWithSso,
|
|
1942
|
+
onRetry: () => void chat.reload()
|
|
1943
|
+
}
|
|
1944
|
+
) : !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)(
|
|
1945
|
+
VoiceConversation,
|
|
1946
|
+
{
|
|
1947
|
+
messages: chat.messages,
|
|
1948
|
+
busy: chat.busy,
|
|
1949
|
+
voice: voiceControls,
|
|
1950
|
+
speakReplies: capabilities?.tts === true,
|
|
1951
|
+
onExit: leaveVoiceMode
|
|
1573
1952
|
}
|
|
1574
|
-
) :
|
|
1953
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1575
1954
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1576
1955
|
MessageList,
|
|
1577
1956
|
{
|
|
@@ -1580,9 +1959,24 @@ function ChatWidget({
|
|
|
1580
1959
|
onAction: (action) => void chat.send(action)
|
|
1581
1960
|
}
|
|
1582
1961
|
),
|
|
1583
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1962
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1963
|
+
Composer,
|
|
1964
|
+
{
|
|
1965
|
+
busy: chat.busy,
|
|
1966
|
+
onSend: chat.send,
|
|
1967
|
+
onStop: chat.stop,
|
|
1968
|
+
dictation,
|
|
1969
|
+
voice: voiceAvailable ? {
|
|
1970
|
+
state: voiceControls.state,
|
|
1971
|
+
error: voiceControls.error,
|
|
1972
|
+
start: voiceControls.startListening,
|
|
1973
|
+
stop: voiceControls.stopListening,
|
|
1974
|
+
onHandsFree: () => setVoiceMode(true)
|
|
1975
|
+
} : void 0
|
|
1976
|
+
}
|
|
1977
|
+
)
|
|
1584
1978
|
] }),
|
|
1585
|
-
/* @__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") }) })
|
|
1979
|
+
brand.poweredBy !== false && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("footer", { className: "agi-chat__foot", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { href: brand.siteUrl ?? "https://www.aginies.com", target: "_blank", rel: "noreferrer", children: t("chat", "poweredBy") }) })
|
|
1586
1980
|
]
|
|
1587
1981
|
}
|
|
1588
1982
|
);
|
|
@@ -1635,53 +2029,57 @@ function MessageList({
|
|
|
1635
2029
|
onAction
|
|
1636
2030
|
}) {
|
|
1637
2031
|
const { t } = useAginies();
|
|
1638
|
-
const endRef = (0,
|
|
1639
|
-
(0,
|
|
2032
|
+
const endRef = (0, import_react6.useRef)(null);
|
|
2033
|
+
(0, import_react6.useEffect)(() => {
|
|
1640
2034
|
endRef.current?.scrollIntoView?.({ block: "end" });
|
|
1641
2035
|
}, []);
|
|
1642
|
-
return (
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
"
|
|
1651
|
-
|
|
1652
|
-
className:
|
|
1653
|
-
children: [
|
|
1654
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: endRef })
|
|
1672
|
-
] })
|
|
1673
|
-
);
|
|
2036
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agi-chat__messages", role: "log", "aria-live": "polite", children: [
|
|
2037
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agi-msg agi-msg--assistant", children: [
|
|
2038
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agi-msg__who", children: t("chat", "assistant") }),
|
|
2039
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agi-msg__body", children: welcome })
|
|
2040
|
+
] }),
|
|
2041
|
+
messages.map((m) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
2042
|
+
"div",
|
|
2043
|
+
{
|
|
2044
|
+
className: cx("agi-msg", `agi-msg--${m.role}`, m.error && "agi-msg--error"),
|
|
2045
|
+
children: [
|
|
2046
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agi-msg__who", children: m.role === "user" ? t("chat", "you") : t("chat", "assistant") }),
|
|
2047
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agi-msg__body", children: [
|
|
2048
|
+
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: [
|
|
2049
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Spinner, { label: t("chat", "thinking") }),
|
|
2050
|
+
" ",
|
|
2051
|
+
t("chat", "thinking")
|
|
2052
|
+
] }) : null,
|
|
2053
|
+
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: [
|
|
2054
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FileGlyph, {}),
|
|
2055
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agi-file__name", children: file.name }),
|
|
2056
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agi-file__size", children: formatBytes(file.size) })
|
|
2057
|
+
] }, n)) })
|
|
2058
|
+
] })
|
|
2059
|
+
]
|
|
2060
|
+
},
|
|
2061
|
+
m.id
|
|
2062
|
+
)),
|
|
2063
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: endRef })
|
|
2064
|
+
] });
|
|
1674
2065
|
}
|
|
1675
2066
|
function Composer({
|
|
1676
2067
|
busy,
|
|
1677
2068
|
onSend,
|
|
1678
|
-
onStop
|
|
2069
|
+
onStop,
|
|
2070
|
+
voice,
|
|
2071
|
+
dictation
|
|
1679
2072
|
}) {
|
|
1680
2073
|
const { t } = useAginies();
|
|
1681
|
-
const [value, setValue] = (0,
|
|
1682
|
-
const [files, setFiles] = (0,
|
|
1683
|
-
const [fileError, setFileError] = (0,
|
|
1684
|
-
const fileInput = (0,
|
|
2074
|
+
const [value, setValue] = (0, import_react6.useState)("");
|
|
2075
|
+
const [files, setFiles] = (0, import_react6.useState)([]);
|
|
2076
|
+
const [fileError, setFileError] = (0, import_react6.useState)(null);
|
|
2077
|
+
const fileInput = (0, import_react6.useRef)(null);
|
|
2078
|
+
(0, import_react6.useEffect)(() => {
|
|
2079
|
+
if (dictation) setValue((v) => v.trim() ? `${v.trimEnd()} ${dictation.text}` : dictation.text);
|
|
2080
|
+
}, [dictation]);
|
|
2081
|
+
const listening = voice?.state === "listening";
|
|
2082
|
+
const transcribing = voice?.state === "transcribing";
|
|
1685
2083
|
const submit = (e) => {
|
|
1686
2084
|
e.preventDefault();
|
|
1687
2085
|
if (busy) return;
|
|
@@ -1727,6 +2125,7 @@ function Composer({
|
|
|
1727
2125
|
] }, n)),
|
|
1728
2126
|
fileError && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "agi-chat__file-error", children: fileError })
|
|
1729
2127
|
] }),
|
|
2128
|
+
voice?.error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agi-chat__file-error", children: voice.error }),
|
|
1730
2129
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agi-chat__row", children: [
|
|
1731
2130
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1732
2131
|
"input",
|
|
@@ -1770,10 +2169,156 @@ function Composer({
|
|
|
1770
2169
|
autoComplete: "off"
|
|
1771
2170
|
}
|
|
1772
2171
|
),
|
|
2172
|
+
voice && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
2173
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2174
|
+
Button,
|
|
2175
|
+
{
|
|
2176
|
+
variant: "ghost",
|
|
2177
|
+
type: "button",
|
|
2178
|
+
className: cx("agi-chat__mic", listening && "agi-chat__mic--on"),
|
|
2179
|
+
"aria-label": listening ? t("voice", "stopTalking") : t("voice", "dictate"),
|
|
2180
|
+
"aria-pressed": listening,
|
|
2181
|
+
title: listening ? t("voice", "stopTalking") : t("voice", "dictate"),
|
|
2182
|
+
disabled: busy || transcribing,
|
|
2183
|
+
onClick: () => listening ? voice.stop() : void voice.start(),
|
|
2184
|
+
children: transcribing ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Spinner, { label: t("voice", "transcribing") }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicGlyph, {})
|
|
2185
|
+
}
|
|
2186
|
+
),
|
|
2187
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2188
|
+
Button,
|
|
2189
|
+
{
|
|
2190
|
+
variant: "ghost",
|
|
2191
|
+
type: "button",
|
|
2192
|
+
"aria-label": t("voice", "handsFree"),
|
|
2193
|
+
title: t("voice", "handsFree"),
|
|
2194
|
+
disabled: busy || listening || transcribing,
|
|
2195
|
+
onClick: voice.onHandsFree,
|
|
2196
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HeadsetGlyph, {})
|
|
2197
|
+
}
|
|
2198
|
+
)
|
|
2199
|
+
] }),
|
|
1773
2200
|
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
2201
|
] })
|
|
1775
2202
|
] });
|
|
1776
2203
|
}
|
|
2204
|
+
function VoiceConversation({
|
|
2205
|
+
messages,
|
|
2206
|
+
busy,
|
|
2207
|
+
voice,
|
|
2208
|
+
speakReplies,
|
|
2209
|
+
onExit
|
|
2210
|
+
}) {
|
|
2211
|
+
const { t } = useAginies();
|
|
2212
|
+
const spokenRef = (0, import_react6.useRef)(null);
|
|
2213
|
+
const mountedRef = (0, import_react6.useRef)(true);
|
|
2214
|
+
const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant");
|
|
2215
|
+
const lastUser = [...messages].reverse().find((m) => m.role === "user");
|
|
2216
|
+
(0, import_react6.useEffect)(() => {
|
|
2217
|
+
mountedRef.current = true;
|
|
2218
|
+
spokenRef.current = lastAssistant?.id ?? null;
|
|
2219
|
+
void voice.startListening();
|
|
2220
|
+
return () => {
|
|
2221
|
+
mountedRef.current = false;
|
|
2222
|
+
};
|
|
2223
|
+
}, []);
|
|
2224
|
+
const replyId = lastAssistant?.id;
|
|
2225
|
+
const replyStreaming = lastAssistant?.streaming === true;
|
|
2226
|
+
(0, import_react6.useEffect)(() => {
|
|
2227
|
+
if (!lastAssistant || replyStreaming || replyId === spokenRef.current) return;
|
|
2228
|
+
spokenRef.current = replyId ?? null;
|
|
2229
|
+
const run = async () => {
|
|
2230
|
+
if (speakReplies && lastAssistant.content && !lastAssistant.error) {
|
|
2231
|
+
await voice.speak(lastAssistant.content);
|
|
2232
|
+
}
|
|
2233
|
+
if (mountedRef.current) await voice.startListening();
|
|
2234
|
+
};
|
|
2235
|
+
void run();
|
|
2236
|
+
}, [replyId, replyStreaming]);
|
|
2237
|
+
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");
|
|
2238
|
+
const hint = voice.state === "listening" ? t("voice", "stopTalking") : voice.state === "speaking" ? t("voice", "interrupt") : voice.state === "transcribing" || busy ? "" : t("voice", "talk");
|
|
2239
|
+
const tap = () => {
|
|
2240
|
+
if (voice.state === "listening") voice.stopListening();
|
|
2241
|
+
else if (voice.state === "speaking") {
|
|
2242
|
+
voice.stopSpeaking();
|
|
2243
|
+
void voice.startListening();
|
|
2244
|
+
} else if (voice.state === "idle" && !busy) void voice.startListening();
|
|
2245
|
+
};
|
|
2246
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { className: "agi-voice", "aria-label": t("voice", "handsFree"), children: [
|
|
2247
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2248
|
+
"button",
|
|
2249
|
+
{
|
|
2250
|
+
type: "button",
|
|
2251
|
+
className: "agi-voice__exit",
|
|
2252
|
+
onClick: onExit,
|
|
2253
|
+
"aria-label": t("voice", "exit"),
|
|
2254
|
+
children: "\xD7"
|
|
2255
|
+
}
|
|
2256
|
+
),
|
|
2257
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agi-voice__status", "aria-live": "polite", children: status }),
|
|
2258
|
+
lastUser?.content && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "agi-voice__you", children: lastUser.content }),
|
|
2259
|
+
/* @__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 }),
|
|
2260
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2261
|
+
"button",
|
|
2262
|
+
{
|
|
2263
|
+
type: "button",
|
|
2264
|
+
className: cx(
|
|
2265
|
+
"agi-voice__orb",
|
|
2266
|
+
`agi-voice__orb--${voice.state}`,
|
|
2267
|
+
busy && "agi-voice__orb--busy"
|
|
2268
|
+
),
|
|
2269
|
+
style: { ["--agi-level"]: voice.level },
|
|
2270
|
+
onClick: tap,
|
|
2271
|
+
"aria-label": hint || status,
|
|
2272
|
+
disabled: voice.state === "transcribing" || voice.state === "unsupported",
|
|
2273
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicGlyph, { size: 28 })
|
|
2274
|
+
}
|
|
2275
|
+
),
|
|
2276
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agi-voice__hint", children: hint }),
|
|
2277
|
+
voice.error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "agi-voice__error", children: voice.error })
|
|
2278
|
+
] });
|
|
2279
|
+
}
|
|
2280
|
+
function MicGlyph({ size = 16 }) {
|
|
2281
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
2282
|
+
"svg",
|
|
2283
|
+
{
|
|
2284
|
+
width: size,
|
|
2285
|
+
height: size,
|
|
2286
|
+
viewBox: "0 0 24 24",
|
|
2287
|
+
fill: "none",
|
|
2288
|
+
stroke: "currentColor",
|
|
2289
|
+
strokeWidth: "1.8",
|
|
2290
|
+
strokeLinecap: "round",
|
|
2291
|
+
strokeLinejoin: "round",
|
|
2292
|
+
"aria-hidden": "true",
|
|
2293
|
+
children: [
|
|
2294
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: "9", y: "3", width: "6", height: "11", rx: "3" }),
|
|
2295
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5 11a7 7 0 0 0 14 0M12 18v3M9 21h6" })
|
|
2296
|
+
]
|
|
2297
|
+
}
|
|
2298
|
+
);
|
|
2299
|
+
}
|
|
2300
|
+
function HeadsetGlyph() {
|
|
2301
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
2302
|
+
"svg",
|
|
2303
|
+
{
|
|
2304
|
+
width: "16",
|
|
2305
|
+
height: "16",
|
|
2306
|
+
viewBox: "0 0 24 24",
|
|
2307
|
+
fill: "none",
|
|
2308
|
+
stroke: "currentColor",
|
|
2309
|
+
strokeWidth: "1.8",
|
|
2310
|
+
strokeLinecap: "round",
|
|
2311
|
+
strokeLinejoin: "round",
|
|
2312
|
+
"aria-hidden": "true",
|
|
2313
|
+
children: [
|
|
2314
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M4 14v-2a8 8 0 0 1 16 0v2" }),
|
|
2315
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: "3", y: "13", width: "4", height: "6", rx: "1.5" }),
|
|
2316
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: "17", y: "13", width: "4", height: "6", rx: "1.5" }),
|
|
2317
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M19 19v1a2 2 0 0 1-2 2h-3" })
|
|
2318
|
+
]
|
|
2319
|
+
}
|
|
2320
|
+
);
|
|
2321
|
+
}
|
|
1777
2322
|
function readFile(file) {
|
|
1778
2323
|
return new Promise((resolve, reject) => {
|
|
1779
2324
|
const reader = new FileReader();
|
|
@@ -1811,35 +2356,62 @@ function ChatAuth({
|
|
|
1811
2356
|
need,
|
|
1812
2357
|
onPassword,
|
|
1813
2358
|
onRequestCode,
|
|
1814
|
-
onVerifyCode
|
|
2359
|
+
onVerifyCode,
|
|
2360
|
+
onSso,
|
|
2361
|
+
onRetry
|
|
1815
2362
|
}) {
|
|
1816
|
-
const { t, client } = useAginies();
|
|
1817
2363
|
if (need === "sso") {
|
|
1818
|
-
return /* @__PURE__ */ (0, import_jsx_runtime6.
|
|
1819
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { children: t("auth", "ssoTitle") }),
|
|
1820
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: t("auth", "ssoHint") }),
|
|
1821
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1822
|
-
Button,
|
|
1823
|
-
{
|
|
1824
|
-
variant: "signal",
|
|
1825
|
-
onClick: () => window.open(`${client.baseUrl}/chat/`, "_blank", "noopener"),
|
|
1826
|
-
children: t("auth", "ssoButton")
|
|
1827
|
-
}
|
|
1828
|
-
)
|
|
1829
|
-
] });
|
|
2364
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SsoAuth, { onSso, onRetry });
|
|
1830
2365
|
}
|
|
1831
2366
|
if (need === "email") {
|
|
1832
2367
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(EmailAuth, { onRequestCode, onVerifyCode });
|
|
1833
2368
|
}
|
|
1834
2369
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PasswordAuth, { onPassword });
|
|
1835
2370
|
}
|
|
2371
|
+
function SsoAuth({
|
|
2372
|
+
onSso,
|
|
2373
|
+
onRetry
|
|
2374
|
+
}) {
|
|
2375
|
+
const { t } = useAginies();
|
|
2376
|
+
const [email, setEmail] = (0, import_react6.useState)("");
|
|
2377
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
2378
|
+
const [pending, setPending] = (0, import_react6.useState)(false);
|
|
2379
|
+
const [redirected, setRedirected] = (0, import_react6.useState)(false);
|
|
2380
|
+
const submit = async (e) => {
|
|
2381
|
+
e.preventDefault();
|
|
2382
|
+
setPending(true);
|
|
2383
|
+
setError(null);
|
|
2384
|
+
const result = await onSso(email.trim());
|
|
2385
|
+
setPending(false);
|
|
2386
|
+
if (result === "redirected") setRedirected(true);
|
|
2387
|
+
else setError(t("auth", result === "unauthorized" ? "invalidEmail" : "codeError"));
|
|
2388
|
+
};
|
|
2389
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("form", { className: "agi-chat__state agi-chat__auth", onSubmit: submit, children: [
|
|
2390
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { children: t("auth", "ssoTitle") }),
|
|
2391
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: t("auth", "ssoHint") }),
|
|
2392
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Field, { label: t("auth", "email"), error: error ?? void 0, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2393
|
+
Input,
|
|
2394
|
+
{
|
|
2395
|
+
type: "email",
|
|
2396
|
+
value: email,
|
|
2397
|
+
onChange: (e) => setEmail(e.target.value),
|
|
2398
|
+
required: true,
|
|
2399
|
+
autoComplete: "email"
|
|
2400
|
+
}
|
|
2401
|
+
) }),
|
|
2402
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "agi-chat__auth-actions", children: [
|
|
2403
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Button, { variant: "signal", type: "submit", disabled: pending || !email.trim(), children: t("auth", "ssoButton") }),
|
|
2404
|
+
redirected && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Button, { variant: "ghost", type: "button", onClick: onRetry, children: t("auth", "ssoRetry") })
|
|
2405
|
+
] })
|
|
2406
|
+
] });
|
|
2407
|
+
}
|
|
1836
2408
|
function PasswordAuth({
|
|
1837
2409
|
onPassword
|
|
1838
2410
|
}) {
|
|
1839
2411
|
const { t } = useAginies();
|
|
1840
|
-
const [value, setValue] = (0,
|
|
1841
|
-
const [error, setError] = (0,
|
|
1842
|
-
const [pending, setPending] = (0,
|
|
2412
|
+
const [value, setValue] = (0, import_react6.useState)("");
|
|
2413
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
2414
|
+
const [pending, setPending] = (0, import_react6.useState)(false);
|
|
1843
2415
|
const submit = async (e) => {
|
|
1844
2416
|
e.preventDefault();
|
|
1845
2417
|
setPending(true);
|
|
@@ -1869,12 +2441,12 @@ function EmailAuth({
|
|
|
1869
2441
|
onVerifyCode
|
|
1870
2442
|
}) {
|
|
1871
2443
|
const { t } = useAginies();
|
|
1872
|
-
const [email, setEmail] = (0,
|
|
1873
|
-
const [code, setCode] = (0,
|
|
1874
|
-
const [step, setStep] = (0,
|
|
1875
|
-
const [error, setError] = (0,
|
|
1876
|
-
const [notice, setNotice] = (0,
|
|
1877
|
-
const [pending, setPending] = (0,
|
|
2444
|
+
const [email, setEmail] = (0, import_react6.useState)("");
|
|
2445
|
+
const [code, setCode] = (0, import_react6.useState)("");
|
|
2446
|
+
const [step, setStep] = (0, import_react6.useState)("email");
|
|
2447
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
2448
|
+
const [notice, setNotice] = (0, import_react6.useState)(null);
|
|
2449
|
+
const [pending, setPending] = (0, import_react6.useState)(false);
|
|
1878
2450
|
const request = async () => {
|
|
1879
2451
|
setPending(true);
|
|
1880
2452
|
setError(null);
|
|
@@ -2085,7 +2657,7 @@ function CostBars({ items, format = usd, className, ...props }) {
|
|
|
2085
2657
|
}
|
|
2086
2658
|
|
|
2087
2659
|
// src/run/agent-runner.tsx
|
|
2088
|
-
var
|
|
2660
|
+
var import_react7 = require("react");
|
|
2089
2661
|
|
|
2090
2662
|
// src/run/run-client.ts
|
|
2091
2663
|
async function* runAgent(client, workflowId, options, signal) {
|
|
@@ -2225,19 +2797,19 @@ function decodeRunFrame(frame) {
|
|
|
2225
2797
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
2226
2798
|
function useAgentRun(workflowId, apiKey) {
|
|
2227
2799
|
const { client, t } = useAginies();
|
|
2228
|
-
const [status, setStatus] = (0,
|
|
2229
|
-
const [text, setText] = (0,
|
|
2230
|
-
const [steps, setSteps] = (0,
|
|
2231
|
-
const [output, setOutput] = (0,
|
|
2232
|
-
const [error, setError] = (0,
|
|
2233
|
-
const [executionId, setExecutionId] = (0,
|
|
2234
|
-
const abortRef = (0,
|
|
2235
|
-
const cancel = (0,
|
|
2800
|
+
const [status, setStatus] = (0, import_react7.useState)("idle");
|
|
2801
|
+
const [text, setText] = (0, import_react7.useState)("");
|
|
2802
|
+
const [steps, setSteps] = (0, import_react7.useState)([]);
|
|
2803
|
+
const [output, setOutput] = (0, import_react7.useState)(null);
|
|
2804
|
+
const [error, setError] = (0, import_react7.useState)(null);
|
|
2805
|
+
const [executionId, setExecutionId] = (0, import_react7.useState)();
|
|
2806
|
+
const abortRef = (0, import_react7.useRef)(null);
|
|
2807
|
+
const cancel = (0, import_react7.useCallback)(() => {
|
|
2236
2808
|
abortRef.current?.abort();
|
|
2237
2809
|
abortRef.current = null;
|
|
2238
2810
|
setStatus((s) => s === "running" ? "idle" : s);
|
|
2239
2811
|
}, []);
|
|
2240
|
-
const run = (0,
|
|
2812
|
+
const run = (0, import_react7.useCallback)(
|
|
2241
2813
|
async (input) => {
|
|
2242
2814
|
abortRef.current?.abort();
|
|
2243
2815
|
const controller = new AbortController();
|
|
@@ -2314,7 +2886,7 @@ function AgentRunner({
|
|
|
2314
2886
|
const { t } = useAginies();
|
|
2315
2887
|
const enabled = useModule("run");
|
|
2316
2888
|
const agent = useAgentRun(workflowId, apiKey);
|
|
2317
|
-
const [values, setValues] = (0,
|
|
2889
|
+
const [values, setValues] = (0, import_react7.useState)(
|
|
2318
2890
|
() => Object.fromEntries(
|
|
2319
2891
|
fields.map((f) => [f.name, f.defaultValue ?? (f.type === "boolean" ? false : "")])
|
|
2320
2892
|
)
|
|
@@ -2326,9 +2898,9 @@ function AgentRunner({
|
|
|
2326
2898
|
const input = fields.length === 1 && fields[0]?.name === "input" && fields[0]?.type === "textarea" ? values.input : values;
|
|
2327
2899
|
await agent.run(input);
|
|
2328
2900
|
};
|
|
2329
|
-
const onResultRef = (0,
|
|
2901
|
+
const onResultRef = (0, import_react7.useRef)(onResult);
|
|
2330
2902
|
onResultRef.current = onResult;
|
|
2331
|
-
(0,
|
|
2903
|
+
(0, import_react7.useEffect)(() => {
|
|
2332
2904
|
if (agent.status === "done") onResultRef.current?.(agent.output);
|
|
2333
2905
|
}, [agent.status, agent.output]);
|
|
2334
2906
|
if (!enabled) return null;
|
|
@@ -2449,6 +3021,7 @@ function formatOutput(output) {
|
|
|
2449
3021
|
getPausedExecution,
|
|
2450
3022
|
init,
|
|
2451
3023
|
initialValues,
|
|
3024
|
+
isVoiceSupported,
|
|
2452
3025
|
listPausedExecutions,
|
|
2453
3026
|
outputOf,
|
|
2454
3027
|
parseFieldValue,
|
|
@@ -2462,6 +3035,7 @@ function formatOutput(output) {
|
|
|
2462
3035
|
useAginies,
|
|
2463
3036
|
useApproval,
|
|
2464
3037
|
useChat,
|
|
2465
|
-
useModule
|
|
3038
|
+
useModule,
|
|
3039
|
+
useVoice
|
|
2466
3040
|
});
|
|
2467
3041
|
//# sourceMappingURL=index.cjs.map
|