@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/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
- syncUrl(sessionId);
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 stored = m.blocks ?? [];
1444
- stored.forEach((b, i) => {
1445
- const sanitised = sanitiseBlock({ ...b});
1446
- if (sanitised.kind === "paragraph_brief") {
1447
- sanitised.prose = m.prose?.[String(i)] ?? "";
1448
- }
1449
- blocks[i] = sanitised;
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: m.errorJson ?? void 0
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
- syncUrl(json.session.id);
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 stored2 = m.blocks ?? [];
2347
- stored2.forEach((b, i) => {
2348
- const sanitised = sanitiseBlock({ ...b});
2349
- if (sanitised.kind === "paragraph_brief") {
2350
- sanitised.prose = m.prose?.[String(i)] ?? "";
2351
- }
2352
- blocks[i] = sanitised;
2353
- });
2354
- const joinedProse = Object.values(m.prose ?? {}).filter(Boolean).join("\n\n");
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 (m.errorJson) errorsMap[id] = m.errorJson;
2449
+ if (storedError) errorsMap[id] = storedError;
2363
2450
  }
2364
2451
  }
2365
2452
  return { uiMessages, blocksMap, proseMap, errorsMap };