@firstlovecenter/ai-chat 0.9.1 → 0.9.3
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/CHANGELOG.md +25 -0
- package/dist/drizzle/index.cjs +11 -3
- package/dist/drizzle/index.cjs.map +1 -1
- package/dist/drizzle/index.js +11 -3
- package/dist/drizzle/index.js.map +1 -1
- package/dist/ui/index.cjs +109 -22
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.js +109 -22
- package/dist/ui/index.js.map +1 -1
- package/package.json +1 -1
package/dist/ui/index.js
CHANGED
|
@@ -699,6 +699,11 @@ function AiChat({
|
|
|
699
699
|
cancelled = true;
|
|
700
700
|
};
|
|
701
701
|
}, []);
|
|
702
|
+
useEffect(() => {
|
|
703
|
+
if (initialSessionId != null && activeSessionId !== initialSessionId) {
|
|
704
|
+
setActiveSessionId(initialSessionId);
|
|
705
|
+
}
|
|
706
|
+
}, [initialSessionId]);
|
|
702
707
|
useEffect(() => {
|
|
703
708
|
if (initialSessionId == null) {
|
|
704
709
|
setAnswers([]);
|
|
@@ -711,13 +716,18 @@ function AiChat({
|
|
|
711
716
|
const res = await fetch(`/api/chat/sessions/${id}`, { cache: "no-store" });
|
|
712
717
|
if (cancelled) return;
|
|
713
718
|
if (!res.ok) {
|
|
719
|
+
console.warn(
|
|
720
|
+
"[AiChat] hydration: GET /api/chat/sessions failed",
|
|
721
|
+
{ id, status: res.status }
|
|
722
|
+
);
|
|
714
723
|
setAnswers([]);
|
|
715
724
|
return;
|
|
716
725
|
}
|
|
717
726
|
const data = await res.json();
|
|
718
727
|
if (cancelled) return;
|
|
719
728
|
setAnswers(messagesToAnswers(data.messages ?? []));
|
|
720
|
-
} catch {
|
|
729
|
+
} catch (err) {
|
|
730
|
+
console.error("[AiChat] hydration threw \u2014 setting empty answers", err);
|
|
721
731
|
if (!cancelled) setAnswers([]);
|
|
722
732
|
} finally {
|
|
723
733
|
if (!cancelled) setLoadingSession(false);
|
|
@@ -850,7 +860,9 @@ function AiChat({
|
|
|
850
860
|
const data = await create.json();
|
|
851
861
|
sessionId = data.session.id;
|
|
852
862
|
setActiveSessionId(sessionId);
|
|
853
|
-
|
|
863
|
+
if (typeof window !== "undefined") {
|
|
864
|
+
window.history.replaceState(null, "", `${basePath}/${sessionId}`);
|
|
865
|
+
}
|
|
854
866
|
setSessions((prev) => [
|
|
855
867
|
{ id: data.session.id, title: data.session.title, updatedAt: null },
|
|
856
868
|
...prev
|
|
@@ -912,6 +924,17 @@ function AiChat({
|
|
|
912
924
|
const events = buffer.split("\n\n");
|
|
913
925
|
buffer = events.pop() ?? "";
|
|
914
926
|
for (const raw of events) {
|
|
927
|
+
const meta = parseMetaChatSessionId(raw);
|
|
928
|
+
if (meta != null) {
|
|
929
|
+
setActiveSessionId((prev) => prev ?? meta);
|
|
930
|
+
if (typeof window !== "undefined" && !window.location.pathname.endsWith(`/${meta}`)) {
|
|
931
|
+
window.history.replaceState(
|
|
932
|
+
null,
|
|
933
|
+
"",
|
|
934
|
+
`${basePath}/${meta}`
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
915
938
|
handleEvent(raw, setAnswers);
|
|
916
939
|
}
|
|
917
940
|
}
|
|
@@ -1370,6 +1393,22 @@ function UserChip({ text }) {
|
|
|
1370
1393
|
)
|
|
1371
1394
|
] });
|
|
1372
1395
|
}
|
|
1396
|
+
function parseMetaChatSessionId(raw) {
|
|
1397
|
+
const lines = raw.split("\n");
|
|
1398
|
+
let event = "";
|
|
1399
|
+
let dataStr = "";
|
|
1400
|
+
for (const line of lines) {
|
|
1401
|
+
if (line.startsWith("event: ")) event = line.slice(7).trim();
|
|
1402
|
+
else if (line.startsWith("data: ")) dataStr += line.slice(6);
|
|
1403
|
+
}
|
|
1404
|
+
if (event !== "meta") return null;
|
|
1405
|
+
try {
|
|
1406
|
+
const parsed = JSON.parse(dataStr || "{}");
|
|
1407
|
+
return typeof parsed.chatSessionId === "string" && parsed.chatSessionId.length > 0 ? parsed.chatSessionId : null;
|
|
1408
|
+
} catch {
|
|
1409
|
+
return null;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1373
1412
|
function handleEvent(raw, setAnswers) {
|
|
1374
1413
|
const lines = raw.split("\n");
|
|
1375
1414
|
let event = "";
|
|
@@ -1430,6 +1469,15 @@ function updateLast(prev, updater) {
|
|
|
1430
1469
|
next[next.length - 1] = updater(next[next.length - 1]);
|
|
1431
1470
|
return next;
|
|
1432
1471
|
}
|
|
1472
|
+
function coerceJsonField(v) {
|
|
1473
|
+
if (v == null) return null;
|
|
1474
|
+
if (typeof v !== "string") return v;
|
|
1475
|
+
try {
|
|
1476
|
+
return JSON.parse(v);
|
|
1477
|
+
} catch {
|
|
1478
|
+
return null;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1433
1481
|
function messagesToAnswers(messages) {
|
|
1434
1482
|
const out = [];
|
|
1435
1483
|
let pendingUser = null;
|
|
@@ -1440,19 +1488,24 @@ function messagesToAnswers(messages) {
|
|
|
1440
1488
|
const question = pendingUser ?? "";
|
|
1441
1489
|
pendingUser = null;
|
|
1442
1490
|
const blocks = [];
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1491
|
+
const storedBlocks = coerceJsonField(m.blocks) ?? [];
|
|
1492
|
+
const storedProse = coerceJsonField(m.prose) ?? {};
|
|
1493
|
+
const storedError = coerceJsonField(m.errorJson);
|
|
1494
|
+
if (Array.isArray(storedBlocks)) {
|
|
1495
|
+
storedBlocks.forEach((b, i) => {
|
|
1496
|
+
const sanitised = sanitiseBlock({
|
|
1497
|
+
...b});
|
|
1498
|
+
if (sanitised.kind === "paragraph_brief") {
|
|
1499
|
+
sanitised.prose = storedProse[String(i)] ?? "";
|
|
1500
|
+
}
|
|
1501
|
+
blocks[i] = sanitised;
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1451
1504
|
out.push({
|
|
1452
1505
|
question,
|
|
1453
1506
|
blocks,
|
|
1454
1507
|
done: true,
|
|
1455
|
-
error:
|
|
1508
|
+
error: storedError ?? void 0
|
|
1456
1509
|
});
|
|
1457
1510
|
}
|
|
1458
1511
|
}
|
|
@@ -1622,6 +1675,20 @@ function VercelChat({
|
|
|
1622
1675
|
cancelled = true;
|
|
1623
1676
|
};
|
|
1624
1677
|
}, [initialSessionId, setMessages]);
|
|
1678
|
+
useEffect(() => {
|
|
1679
|
+
if (!Array.isArray(data)) return;
|
|
1680
|
+
for (const raw of data) {
|
|
1681
|
+
const part = asDataPart(raw);
|
|
1682
|
+
if (!part || part.type !== "meta") continue;
|
|
1683
|
+
const id = part.value.chatSessionId;
|
|
1684
|
+
if (!id) continue;
|
|
1685
|
+
setActiveSessionId((prev) => prev ?? id);
|
|
1686
|
+
if (typeof window !== "undefined" && !window.location.pathname.endsWith(`/${id}`)) {
|
|
1687
|
+
window.history.replaceState(null, "", `${basePath}/${id}`);
|
|
1688
|
+
}
|
|
1689
|
+
break;
|
|
1690
|
+
}
|
|
1691
|
+
}, [data, basePath]);
|
|
1625
1692
|
const answers = useMemo(() => {
|
|
1626
1693
|
const liveBlocks = [];
|
|
1627
1694
|
const liveErrors = [];
|
|
@@ -1844,7 +1911,13 @@ function VercelChat({
|
|
|
1844
1911
|
const json = await create.json();
|
|
1845
1912
|
activeSessionIdRef.current = json.session.id;
|
|
1846
1913
|
setActiveSessionId(json.session.id);
|
|
1847
|
-
|
|
1914
|
+
if (typeof window !== "undefined") {
|
|
1915
|
+
window.history.replaceState(
|
|
1916
|
+
null,
|
|
1917
|
+
"",
|
|
1918
|
+
`${basePath}/${json.session.id}`
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1848
1921
|
setSessions((prev) => [
|
|
1849
1922
|
{ id: json.session.id, title: json.session.title, updatedAt: null },
|
|
1850
1923
|
...prev
|
|
@@ -2328,6 +2401,15 @@ function UserChip2({ text }) {
|
|
|
2328
2401
|
)
|
|
2329
2402
|
] });
|
|
2330
2403
|
}
|
|
2404
|
+
function coerceJsonField2(v) {
|
|
2405
|
+
if (v == null) return null;
|
|
2406
|
+
if (typeof v !== "string") return v;
|
|
2407
|
+
try {
|
|
2408
|
+
return JSON.parse(v);
|
|
2409
|
+
} catch {
|
|
2410
|
+
return null;
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2331
2413
|
function storedToUseChat(stored) {
|
|
2332
2414
|
const uiMessages = [];
|
|
2333
2415
|
const blocksMap = {};
|
|
@@ -2343,15 +2425,20 @@ function storedToUseChat(stored) {
|
|
|
2343
2425
|
if (m.role === "assistant") {
|
|
2344
2426
|
const id = `assistant-${m.id}`;
|
|
2345
2427
|
const blocks = [];
|
|
2346
|
-
const
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2428
|
+
const storedBlocks = coerceJsonField2(m.blocks) ?? [];
|
|
2429
|
+
const storedProse = coerceJsonField2(m.prose) ?? {};
|
|
2430
|
+
const storedError = coerceJsonField2(m.errorJson);
|
|
2431
|
+
if (Array.isArray(storedBlocks)) {
|
|
2432
|
+
storedBlocks.forEach((b, i) => {
|
|
2433
|
+
const sanitised = sanitiseBlock({
|
|
2434
|
+
...b});
|
|
2435
|
+
if (sanitised.kind === "paragraph_brief") {
|
|
2436
|
+
sanitised.prose = storedProse[String(i)] ?? "";
|
|
2437
|
+
}
|
|
2438
|
+
blocks[i] = sanitised;
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
const joinedProse = Object.values(storedProse).filter(Boolean).join("\n\n");
|
|
2355
2442
|
uiMessages.push({
|
|
2356
2443
|
id,
|
|
2357
2444
|
role: "assistant",
|
|
@@ -2359,7 +2446,7 @@ function storedToUseChat(stored) {
|
|
|
2359
2446
|
});
|
|
2360
2447
|
blocksMap[id] = blocks;
|
|
2361
2448
|
if (joinedProse) proseMap[id] = joinedProse;
|
|
2362
|
-
if (
|
|
2449
|
+
if (storedError) errorsMap[id] = storedError;
|
|
2363
2450
|
}
|
|
2364
2451
|
}
|
|
2365
2452
|
return { uiMessages, blocksMap, proseMap, errorsMap };
|