@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.cjs
CHANGED
|
@@ -723,6 +723,11 @@ function AiChat({
|
|
|
723
723
|
cancelled = true;
|
|
724
724
|
};
|
|
725
725
|
}, []);
|
|
726
|
+
React.useEffect(() => {
|
|
727
|
+
if (initialSessionId != null && activeSessionId !== initialSessionId) {
|
|
728
|
+
setActiveSessionId(initialSessionId);
|
|
729
|
+
}
|
|
730
|
+
}, [initialSessionId]);
|
|
726
731
|
React.useEffect(() => {
|
|
727
732
|
if (initialSessionId == null) {
|
|
728
733
|
setAnswers([]);
|
|
@@ -735,13 +740,18 @@ function AiChat({
|
|
|
735
740
|
const res = await fetch(`/api/chat/sessions/${id}`, { cache: "no-store" });
|
|
736
741
|
if (cancelled) return;
|
|
737
742
|
if (!res.ok) {
|
|
743
|
+
console.warn(
|
|
744
|
+
"[AiChat] hydration: GET /api/chat/sessions failed",
|
|
745
|
+
{ id, status: res.status }
|
|
746
|
+
);
|
|
738
747
|
setAnswers([]);
|
|
739
748
|
return;
|
|
740
749
|
}
|
|
741
750
|
const data = await res.json();
|
|
742
751
|
if (cancelled) return;
|
|
743
752
|
setAnswers(messagesToAnswers(data.messages ?? []));
|
|
744
|
-
} catch {
|
|
753
|
+
} catch (err) {
|
|
754
|
+
console.error("[AiChat] hydration threw \u2014 setting empty answers", err);
|
|
745
755
|
if (!cancelled) setAnswers([]);
|
|
746
756
|
} finally {
|
|
747
757
|
if (!cancelled) setLoadingSession(false);
|
|
@@ -874,7 +884,9 @@ function AiChat({
|
|
|
874
884
|
const data = await create.json();
|
|
875
885
|
sessionId = data.session.id;
|
|
876
886
|
setActiveSessionId(sessionId);
|
|
877
|
-
|
|
887
|
+
if (typeof window !== "undefined") {
|
|
888
|
+
window.history.replaceState(null, "", `${basePath}/${sessionId}`);
|
|
889
|
+
}
|
|
878
890
|
setSessions((prev) => [
|
|
879
891
|
{ id: data.session.id, title: data.session.title, updatedAt: null },
|
|
880
892
|
...prev
|
|
@@ -936,6 +948,17 @@ function AiChat({
|
|
|
936
948
|
const events = buffer.split("\n\n");
|
|
937
949
|
buffer = events.pop() ?? "";
|
|
938
950
|
for (const raw of events) {
|
|
951
|
+
const meta = parseMetaChatSessionId(raw);
|
|
952
|
+
if (meta != null) {
|
|
953
|
+
setActiveSessionId((prev) => prev ?? meta);
|
|
954
|
+
if (typeof window !== "undefined" && !window.location.pathname.endsWith(`/${meta}`)) {
|
|
955
|
+
window.history.replaceState(
|
|
956
|
+
null,
|
|
957
|
+
"",
|
|
958
|
+
`${basePath}/${meta}`
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
939
962
|
handleEvent(raw, setAnswers);
|
|
940
963
|
}
|
|
941
964
|
}
|
|
@@ -1394,6 +1417,22 @@ function UserChip({ text }) {
|
|
|
1394
1417
|
)
|
|
1395
1418
|
] });
|
|
1396
1419
|
}
|
|
1420
|
+
function parseMetaChatSessionId(raw) {
|
|
1421
|
+
const lines = raw.split("\n");
|
|
1422
|
+
let event = "";
|
|
1423
|
+
let dataStr = "";
|
|
1424
|
+
for (const line of lines) {
|
|
1425
|
+
if (line.startsWith("event: ")) event = line.slice(7).trim();
|
|
1426
|
+
else if (line.startsWith("data: ")) dataStr += line.slice(6);
|
|
1427
|
+
}
|
|
1428
|
+
if (event !== "meta") return null;
|
|
1429
|
+
try {
|
|
1430
|
+
const parsed = JSON.parse(dataStr || "{}");
|
|
1431
|
+
return typeof parsed.chatSessionId === "string" && parsed.chatSessionId.length > 0 ? parsed.chatSessionId : null;
|
|
1432
|
+
} catch {
|
|
1433
|
+
return null;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1397
1436
|
function handleEvent(raw, setAnswers) {
|
|
1398
1437
|
const lines = raw.split("\n");
|
|
1399
1438
|
let event = "";
|
|
@@ -1454,6 +1493,15 @@ function updateLast(prev, updater) {
|
|
|
1454
1493
|
next[next.length - 1] = updater(next[next.length - 1]);
|
|
1455
1494
|
return next;
|
|
1456
1495
|
}
|
|
1496
|
+
function coerceJsonField(v) {
|
|
1497
|
+
if (v == null) return null;
|
|
1498
|
+
if (typeof v !== "string") return v;
|
|
1499
|
+
try {
|
|
1500
|
+
return JSON.parse(v);
|
|
1501
|
+
} catch {
|
|
1502
|
+
return null;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1457
1505
|
function messagesToAnswers(messages) {
|
|
1458
1506
|
const out = [];
|
|
1459
1507
|
let pendingUser = null;
|
|
@@ -1464,19 +1512,24 @@ function messagesToAnswers(messages) {
|
|
|
1464
1512
|
const question = pendingUser ?? "";
|
|
1465
1513
|
pendingUser = null;
|
|
1466
1514
|
const blocks = [];
|
|
1467
|
-
const
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1515
|
+
const storedBlocks = coerceJsonField(m.blocks) ?? [];
|
|
1516
|
+
const storedProse = coerceJsonField(m.prose) ?? {};
|
|
1517
|
+
const storedError = coerceJsonField(m.errorJson);
|
|
1518
|
+
if (Array.isArray(storedBlocks)) {
|
|
1519
|
+
storedBlocks.forEach((b, i) => {
|
|
1520
|
+
const sanitised = sanitiseBlock({
|
|
1521
|
+
...b});
|
|
1522
|
+
if (sanitised.kind === "paragraph_brief") {
|
|
1523
|
+
sanitised.prose = storedProse[String(i)] ?? "";
|
|
1524
|
+
}
|
|
1525
|
+
blocks[i] = sanitised;
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1475
1528
|
out.push({
|
|
1476
1529
|
question,
|
|
1477
1530
|
blocks,
|
|
1478
1531
|
done: true,
|
|
1479
|
-
error:
|
|
1532
|
+
error: storedError ?? void 0
|
|
1480
1533
|
});
|
|
1481
1534
|
}
|
|
1482
1535
|
}
|
|
@@ -1646,6 +1699,20 @@ function VercelChat({
|
|
|
1646
1699
|
cancelled = true;
|
|
1647
1700
|
};
|
|
1648
1701
|
}, [initialSessionId, setMessages]);
|
|
1702
|
+
React.useEffect(() => {
|
|
1703
|
+
if (!Array.isArray(data)) return;
|
|
1704
|
+
for (const raw of data) {
|
|
1705
|
+
const part = asDataPart(raw);
|
|
1706
|
+
if (!part || part.type !== "meta") continue;
|
|
1707
|
+
const id = part.value.chatSessionId;
|
|
1708
|
+
if (!id) continue;
|
|
1709
|
+
setActiveSessionId((prev) => prev ?? id);
|
|
1710
|
+
if (typeof window !== "undefined" && !window.location.pathname.endsWith(`/${id}`)) {
|
|
1711
|
+
window.history.replaceState(null, "", `${basePath}/${id}`);
|
|
1712
|
+
}
|
|
1713
|
+
break;
|
|
1714
|
+
}
|
|
1715
|
+
}, [data, basePath]);
|
|
1649
1716
|
const answers = React.useMemo(() => {
|
|
1650
1717
|
const liveBlocks = [];
|
|
1651
1718
|
const liveErrors = [];
|
|
@@ -1868,7 +1935,13 @@ function VercelChat({
|
|
|
1868
1935
|
const json = await create.json();
|
|
1869
1936
|
activeSessionIdRef.current = json.session.id;
|
|
1870
1937
|
setActiveSessionId(json.session.id);
|
|
1871
|
-
|
|
1938
|
+
if (typeof window !== "undefined") {
|
|
1939
|
+
window.history.replaceState(
|
|
1940
|
+
null,
|
|
1941
|
+
"",
|
|
1942
|
+
`${basePath}/${json.session.id}`
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1872
1945
|
setSessions((prev) => [
|
|
1873
1946
|
{ id: json.session.id, title: json.session.title, updatedAt: null },
|
|
1874
1947
|
...prev
|
|
@@ -2352,6 +2425,15 @@ function UserChip2({ text }) {
|
|
|
2352
2425
|
)
|
|
2353
2426
|
] });
|
|
2354
2427
|
}
|
|
2428
|
+
function coerceJsonField2(v) {
|
|
2429
|
+
if (v == null) return null;
|
|
2430
|
+
if (typeof v !== "string") return v;
|
|
2431
|
+
try {
|
|
2432
|
+
return JSON.parse(v);
|
|
2433
|
+
} catch {
|
|
2434
|
+
return null;
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2355
2437
|
function storedToUseChat(stored) {
|
|
2356
2438
|
const uiMessages = [];
|
|
2357
2439
|
const blocksMap = {};
|
|
@@ -2367,15 +2449,20 @@ function storedToUseChat(stored) {
|
|
|
2367
2449
|
if (m.role === "assistant") {
|
|
2368
2450
|
const id = `assistant-${m.id}`;
|
|
2369
2451
|
const blocks = [];
|
|
2370
|
-
const
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2452
|
+
const storedBlocks = coerceJsonField2(m.blocks) ?? [];
|
|
2453
|
+
const storedProse = coerceJsonField2(m.prose) ?? {};
|
|
2454
|
+
const storedError = coerceJsonField2(m.errorJson);
|
|
2455
|
+
if (Array.isArray(storedBlocks)) {
|
|
2456
|
+
storedBlocks.forEach((b, i) => {
|
|
2457
|
+
const sanitised = sanitiseBlock({
|
|
2458
|
+
...b});
|
|
2459
|
+
if (sanitised.kind === "paragraph_brief") {
|
|
2460
|
+
sanitised.prose = storedProse[String(i)] ?? "";
|
|
2461
|
+
}
|
|
2462
|
+
blocks[i] = sanitised;
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
const joinedProse = Object.values(storedProse).filter(Boolean).join("\n\n");
|
|
2379
2466
|
uiMessages.push({
|
|
2380
2467
|
id,
|
|
2381
2468
|
role: "assistant",
|
|
@@ -2383,7 +2470,7 @@ function storedToUseChat(stored) {
|
|
|
2383
2470
|
});
|
|
2384
2471
|
blocksMap[id] = blocks;
|
|
2385
2472
|
if (joinedProse) proseMap[id] = joinedProse;
|
|
2386
|
-
if (
|
|
2473
|
+
if (storedError) errorsMap[id] = storedError;
|
|
2387
2474
|
}
|
|
2388
2475
|
}
|
|
2389
2476
|
return { uiMessages, blocksMap, proseMap, errorsMap };
|