@firstlovecenter/ai-chat 0.5.0 → 0.6.1
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 +39 -0
- package/dist/drizzle/index.cjs +24 -0
- package/dist/drizzle/index.cjs.map +1 -1
- package/dist/drizzle/index.d.cts +35 -1
- package/dist/drizzle/index.d.ts +35 -1
- package/dist/drizzle/index.js +25 -1
- package/dist/drizzle/index.js.map +1 -1
- package/dist/prisma/index.cjs +7 -0
- package/dist/prisma/index.cjs.map +1 -1
- package/dist/prisma/index.d.cts +7 -1
- package/dist/prisma/index.d.ts +7 -1
- package/dist/prisma/index.js +7 -0
- package/dist/prisma/index.js.map +1 -1
- package/dist/server/index.cjs +47 -12
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +12 -3
- package/dist/server/index.d.ts +12 -3
- package/dist/server/index.js +47 -12
- package/dist/server/index.js.map +1 -1
- package/dist/{types-CDKxdzQc.d.cts → types-CQntnyDJ.d.cts} +15 -2
- package/dist/{types-CDKxdzQc.d.ts → types-CQntnyDJ.d.ts} +15 -2
- package/dist/ui/index.cjs +85 -9
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.js +85 -9
- package/dist/ui/index.js.map +1 -1
- package/package.json +1 -1
- package/prisma/chat-models.prisma +7 -0
|
@@ -139,16 +139,26 @@ type ChatMessage = {
|
|
|
139
139
|
createdAt: Date;
|
|
140
140
|
};
|
|
141
141
|
/**
|
|
142
|
-
* Singleton row controlling
|
|
143
|
-
* region, and which chat UI (Custom vs. Vercel) renders globally. All three
|
|
142
|
+
* Singleton row controlling global AI runtime settings. All open-string
|
|
144
143
|
* fields are validated at runtime against the registries the host configures
|
|
145
144
|
* — they are not enums in the package's types so consumers can register
|
|
146
145
|
* additional providers / interfaces without a schema change.
|
|
146
|
+
*
|
|
147
|
+
* `maxOutputTokens` caps both the agent loop's per-turn output AND each
|
|
148
|
+
* narrator's prose pass. Reasoning models (e.g. `xai/grok-4.1-fast-reasoning`)
|
|
149
|
+
* charge internal thinking against this budget — set it generously when
|
|
150
|
+
* those are in use.
|
|
151
|
+
*
|
|
152
|
+
* `rolePrompt` is the persona the assistant adopts. When non-null, it
|
|
153
|
+
* takes precedence over the host's static `rolePrompt` configureAiChat
|
|
154
|
+
* option, so admins can edit live in the settings UI.
|
|
147
155
|
*/
|
|
148
156
|
type AiSettings = {
|
|
149
157
|
toolProvider: string;
|
|
150
158
|
gcpLocation: string;
|
|
151
159
|
chatInterface: string;
|
|
160
|
+
maxOutputTokens: number;
|
|
161
|
+
rolePrompt: string | null;
|
|
152
162
|
updatedAt: Date | null;
|
|
153
163
|
updatedByUserId: number | null;
|
|
154
164
|
};
|
|
@@ -242,6 +252,9 @@ type AiSettingsPatch = {
|
|
|
242
252
|
toolProvider?: string;
|
|
243
253
|
gcpLocation?: string;
|
|
244
254
|
chatInterface?: string;
|
|
255
|
+
maxOutputTokens?: number;
|
|
256
|
+
/** Pass `null` to clear back to the host's static fallback. */
|
|
257
|
+
rolePrompt?: string | null;
|
|
245
258
|
};
|
|
246
259
|
/**
|
|
247
260
|
* The whole reason this package is ORM-agnostic. Implemented by:
|
|
@@ -139,16 +139,26 @@ type ChatMessage = {
|
|
|
139
139
|
createdAt: Date;
|
|
140
140
|
};
|
|
141
141
|
/**
|
|
142
|
-
* Singleton row controlling
|
|
143
|
-
* region, and which chat UI (Custom vs. Vercel) renders globally. All three
|
|
142
|
+
* Singleton row controlling global AI runtime settings. All open-string
|
|
144
143
|
* fields are validated at runtime against the registries the host configures
|
|
145
144
|
* — they are not enums in the package's types so consumers can register
|
|
146
145
|
* additional providers / interfaces without a schema change.
|
|
146
|
+
*
|
|
147
|
+
* `maxOutputTokens` caps both the agent loop's per-turn output AND each
|
|
148
|
+
* narrator's prose pass. Reasoning models (e.g. `xai/grok-4.1-fast-reasoning`)
|
|
149
|
+
* charge internal thinking against this budget — set it generously when
|
|
150
|
+
* those are in use.
|
|
151
|
+
*
|
|
152
|
+
* `rolePrompt` is the persona the assistant adopts. When non-null, it
|
|
153
|
+
* takes precedence over the host's static `rolePrompt` configureAiChat
|
|
154
|
+
* option, so admins can edit live in the settings UI.
|
|
147
155
|
*/
|
|
148
156
|
type AiSettings = {
|
|
149
157
|
toolProvider: string;
|
|
150
158
|
gcpLocation: string;
|
|
151
159
|
chatInterface: string;
|
|
160
|
+
maxOutputTokens: number;
|
|
161
|
+
rolePrompt: string | null;
|
|
152
162
|
updatedAt: Date | null;
|
|
153
163
|
updatedByUserId: number | null;
|
|
154
164
|
};
|
|
@@ -242,6 +252,9 @@ type AiSettingsPatch = {
|
|
|
242
252
|
toolProvider?: string;
|
|
243
253
|
gcpLocation?: string;
|
|
244
254
|
chatInterface?: string;
|
|
255
|
+
maxOutputTokens?: number;
|
|
256
|
+
/** Pass `null` to clear back to the host's static fallback. */
|
|
257
|
+
rolePrompt?: string | null;
|
|
245
258
|
};
|
|
246
259
|
/**
|
|
247
260
|
* The whole reason this package is ORM-agnostic. Implemented by:
|
package/dist/ui/index.cjs
CHANGED
|
@@ -489,17 +489,22 @@ function sanitiseBlock(input) {
|
|
|
489
489
|
}
|
|
490
490
|
return { kind: "callout", tone: input.tone, text: input.text };
|
|
491
491
|
}
|
|
492
|
+
function isBlankString(v) {
|
|
493
|
+
return typeof v !== "string" || !v.trim();
|
|
494
|
+
}
|
|
492
495
|
function isBlockEmpty(b) {
|
|
493
496
|
if (b.kind === "paragraph_brief") {
|
|
494
|
-
if (b.prose && b.prose.trim()) return false;
|
|
495
|
-
|
|
497
|
+
if (typeof b.prose === "string" && b.prose.trim()) return false;
|
|
498
|
+
const facts = Array.isArray(b.key_facts) ? b.key_facts : [];
|
|
499
|
+
return facts.length === 0 || facts.every(isBlankString);
|
|
496
500
|
}
|
|
497
501
|
if (b.kind === "list") {
|
|
498
|
-
|
|
502
|
+
const items = Array.isArray(b.items) ? b.items : [];
|
|
503
|
+
return items.length === 0 || items.every(isBlankString);
|
|
499
504
|
}
|
|
500
|
-
if (b.kind === "callout") return
|
|
501
|
-
if (b.kind === "chart") return b.data.length === 0;
|
|
502
|
-
if (b.kind === "table") return b.rows.length === 0;
|
|
505
|
+
if (b.kind === "callout") return isBlankString(b.text);
|
|
506
|
+
if (b.kind === "chart") return !Array.isArray(b.data) || b.data.length === 0;
|
|
507
|
+
if (b.kind === "table") return !Array.isArray(b.rows) || b.rows.length === 0;
|
|
503
508
|
return false;
|
|
504
509
|
}
|
|
505
510
|
function AnswerBlocks({ blocks }) {
|
|
@@ -684,6 +689,7 @@ function AiChat({
|
|
|
684
689
|
const textareaRef = React.useRef(null);
|
|
685
690
|
const lastAnswerRef = React.useRef(null);
|
|
686
691
|
const prevAnswersLen = React.useRef(0);
|
|
692
|
+
const autoOpenedRef = React.useRef(false);
|
|
687
693
|
React.useLayoutEffect(() => {
|
|
688
694
|
const el = textareaRef.current;
|
|
689
695
|
if (!el) return;
|
|
@@ -698,7 +704,34 @@ function AiChat({
|
|
|
698
704
|
const res = await fetch("/api/chat/sessions", { cache: "no-store" });
|
|
699
705
|
if (!res.ok) return;
|
|
700
706
|
const data = await res.json();
|
|
701
|
-
if (
|
|
707
|
+
if (cancelled) return;
|
|
708
|
+
const list = data.sessions ?? [];
|
|
709
|
+
setSessions(list);
|
|
710
|
+
if (!autoOpenedRef.current && list.length > 0) {
|
|
711
|
+
autoOpenedRef.current = true;
|
|
712
|
+
const mostRecentId = list[0].id;
|
|
713
|
+
setLoadingSession(true);
|
|
714
|
+
setActiveSessionId(mostRecentId);
|
|
715
|
+
try {
|
|
716
|
+
const sres = await fetch(`/api/chat/sessions/${mostRecentId}`, {
|
|
717
|
+
cache: "no-store"
|
|
718
|
+
});
|
|
719
|
+
if (cancelled) return;
|
|
720
|
+
if (!sres.ok) {
|
|
721
|
+
setAnswers([]);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const sdata = await sres.json();
|
|
725
|
+
if (cancelled) return;
|
|
726
|
+
setAnswers(messagesToAnswers(sdata.messages ?? []));
|
|
727
|
+
} catch {
|
|
728
|
+
if (!cancelled) setAnswers([]);
|
|
729
|
+
} finally {
|
|
730
|
+
if (!cancelled) setLoadingSession(false);
|
|
731
|
+
}
|
|
732
|
+
} else if (!autoOpenedRef.current) {
|
|
733
|
+
autoOpenedRef.current = true;
|
|
734
|
+
}
|
|
702
735
|
} catch {
|
|
703
736
|
}
|
|
704
737
|
}
|
|
@@ -1475,6 +1508,7 @@ function VercelChat({
|
|
|
1475
1508
|
const textareaRef = React.useRef(null);
|
|
1476
1509
|
const lastAnswerRef = React.useRef(null);
|
|
1477
1510
|
const prevAnswersLen = React.useRef(0);
|
|
1511
|
+
const autoOpenedRef = React.useRef(false);
|
|
1478
1512
|
const activeSessionIdRef = React.useRef(activeSessionId);
|
|
1479
1513
|
const providerRef = React.useRef(provider);
|
|
1480
1514
|
React.useEffect(() => {
|
|
@@ -1529,7 +1563,49 @@ function VercelChat({
|
|
|
1529
1563
|
const res = await fetch("/api/chat/sessions", { cache: "no-store" });
|
|
1530
1564
|
if (!res.ok) return;
|
|
1531
1565
|
const json = await res.json();
|
|
1532
|
-
if (
|
|
1566
|
+
if (cancelled) return;
|
|
1567
|
+
const list = json.sessions ?? [];
|
|
1568
|
+
setSessions(list);
|
|
1569
|
+
if (!autoOpenedRef.current && list.length > 0) {
|
|
1570
|
+
autoOpenedRef.current = true;
|
|
1571
|
+
const mostRecentId = list[0].id;
|
|
1572
|
+
setLoadingSession(true);
|
|
1573
|
+
setActiveSessionId(mostRecentId);
|
|
1574
|
+
try {
|
|
1575
|
+
const sres = await fetch(`/api/chat/sessions/${mostRecentId}`, {
|
|
1576
|
+
cache: "no-store"
|
|
1577
|
+
});
|
|
1578
|
+
if (cancelled) return;
|
|
1579
|
+
if (!sres.ok) {
|
|
1580
|
+
setMessages([]);
|
|
1581
|
+
setHydratedBlocks({});
|
|
1582
|
+
setHydratedProse({});
|
|
1583
|
+
setHydratedErrors({});
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
const sjson = await sres.json();
|
|
1587
|
+
if (cancelled) return;
|
|
1588
|
+
const { uiMessages, blocksMap, proseMap, errorsMap } = storedToUseChat(
|
|
1589
|
+
sjson.messages ?? []
|
|
1590
|
+
);
|
|
1591
|
+
setMessages(uiMessages);
|
|
1592
|
+
setHydratedBlocks(blocksMap);
|
|
1593
|
+
setHydratedProse(proseMap);
|
|
1594
|
+
setHydratedErrors(errorsMap);
|
|
1595
|
+
setStartedAt({});
|
|
1596
|
+
} catch {
|
|
1597
|
+
if (!cancelled) {
|
|
1598
|
+
setMessages([]);
|
|
1599
|
+
setHydratedBlocks({});
|
|
1600
|
+
setHydratedProse({});
|
|
1601
|
+
setHydratedErrors({});
|
|
1602
|
+
}
|
|
1603
|
+
} finally {
|
|
1604
|
+
if (!cancelled) setLoadingSession(false);
|
|
1605
|
+
}
|
|
1606
|
+
} else if (!autoOpenedRef.current) {
|
|
1607
|
+
autoOpenedRef.current = true;
|
|
1608
|
+
}
|
|
1533
1609
|
} catch {
|
|
1534
1610
|
}
|
|
1535
1611
|
}
|
|
@@ -1537,7 +1613,7 @@ function VercelChat({
|
|
|
1537
1613
|
return () => {
|
|
1538
1614
|
cancelled = true;
|
|
1539
1615
|
};
|
|
1540
|
-
}, []);
|
|
1616
|
+
}, [setMessages]);
|
|
1541
1617
|
const answers = React.useMemo(() => {
|
|
1542
1618
|
const liveBlocks = [];
|
|
1543
1619
|
const liveErrors = [];
|