@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.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
|
|
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
|
|
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] =
|
|
1283
|
-
const [authNeed, setAuthNeed] =
|
|
1284
|
-
const [authTitle, setAuthTitle] =
|
|
1285
|
-
const [loadError, setLoadError] =
|
|
1286
|
-
const [messages, setMessages] =
|
|
1287
|
-
const [busy, setBusy] =
|
|
1288
|
-
const [conversationId] =
|
|
1289
|
-
const abortRef =
|
|
1290
|
-
const load =
|
|
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
|
-
|
|
1613
|
+
useEffect4(() => {
|
|
1308
1614
|
if (enabled) void load();
|
|
1309
1615
|
}, [load, enabled]);
|
|
1310
1616
|
const chatPath = `/api/chat/${encodeURIComponent(identifier)}`;
|
|
1311
|
-
const authenticate =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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] =
|
|
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
|
-
) :
|
|
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(
|
|
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 =
|
|
1569
|
-
|
|
1960
|
+
const endRef = useRef3(null);
|
|
1961
|
+
useEffect4(() => {
|
|
1570
1962
|
endRef.current?.scrollIntoView?.({ block: "end" });
|
|
1571
1963
|
}, []);
|
|
1572
|
-
return (
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
/* @__PURE__ */
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
"
|
|
1581
|
-
|
|
1582
|
-
className:
|
|
1583
|
-
children: [
|
|
1584
|
-
/* @__PURE__ */ jsx6("
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
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] =
|
|
1612
|
-
const [files, setFiles] =
|
|
1613
|
-
const [fileError, setFileError] =
|
|
1614
|
-
const fileInput =
|
|
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__ */
|
|
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] =
|
|
1771
|
-
const [error, setError] =
|
|
1772
|
-
const [pending, setPending] =
|
|
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] =
|
|
1803
|
-
const [code, setCode] =
|
|
1804
|
-
const [step, setStep] =
|
|
1805
|
-
const [error, setError] =
|
|
1806
|
-
const [notice, setNotice] =
|
|
1807
|
-
const [pending, setPending] =
|
|
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
|
|
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] =
|
|
2159
|
-
const [text, setText] =
|
|
2160
|
-
const [steps, setSteps] =
|
|
2161
|
-
const [output, setOutput] =
|
|
2162
|
-
const [error, setError] =
|
|
2163
|
-
const [executionId, setExecutionId] =
|
|
2164
|
-
const abortRef =
|
|
2165
|
-
const cancel =
|
|
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 =
|
|
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] =
|
|
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 =
|
|
2829
|
+
const onResultRef = useRef4(onResult);
|
|
2260
2830
|
onResultRef.current = onResult;
|
|
2261
|
-
|
|
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
|