@btst/stack 4.4.0 → 4.5.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/README.md +2 -2
- package/dist/packages/stack/src/plugins/ai-chat/api/compaction.cjs +166 -39
- package/dist/packages/stack/src/plugins/ai-chat/api/compaction.mjs +167 -40
- package/dist/packages/stack/src/plugins/ai-chat/api/context-checkpoint.cjs +54 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/context-checkpoint.mjs +51 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/getters.cjs +2 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/getters.mjs +2 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/operations.cjs +275 -95
- package/dist/packages/stack/src/plugins/ai-chat/api/operations.mjs +276 -96
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +8 -5
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +8 -5
- package/dist/packages/stack/src/plugins/ai-chat/api/serializers.cjs +1 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/serializers.mjs +1 -0
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.cjs +3 -2
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.mjs +3 -2
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.cjs +26 -17
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +26 -17
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.cjs +22 -2
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.mjs +22 -2
- package/dist/packages/stack/src/plugins/ai-chat/db.cjs +8 -0
- package/dist/packages/stack/src/plugins/ai-chat/db.mjs +8 -0
- package/dist/packages/stack/src/plugins/ai-chat/history.cjs +47 -0
- package/dist/packages/stack/src/plugins/ai-chat/history.mjs +45 -0
- package/dist/plugins/ai-chat/api/index.d.cts +2 -2
- package/dist/plugins/ai-chat/api/index.d.mts +2 -2
- package/dist/plugins/ai-chat/api/index.d.ts +2 -2
- package/dist/plugins/ai-chat/client/components/index.d.cts +1 -1
- package/dist/plugins/ai-chat/client/components/index.d.mts +1 -1
- package/dist/plugins/ai-chat/client/components/index.d.ts +1 -1
- package/dist/plugins/ai-chat/client/hooks/index.d.cts +2 -2
- package/dist/plugins/ai-chat/client/hooks/index.d.mts +2 -2
- package/dist/plugins/ai-chat/client/hooks/index.d.ts +2 -2
- package/dist/plugins/ai-chat/client/index.d.cts +3 -3
- package/dist/plugins/ai-chat/client/index.d.mts +3 -3
- package/dist/plugins/ai-chat/client/index.d.ts +3 -3
- package/dist/plugins/ai-chat/query-keys.d.cts +1 -1
- package/dist/plugins/ai-chat/query-keys.d.mts +1 -1
- package/dist/plugins/ai-chat/query-keys.d.ts +1 -1
- package/dist/plugins/blog/api/index.d.cts +1 -1
- package/dist/plugins/blog/api/index.d.mts +1 -1
- package/dist/plugins/blog/api/index.d.ts +1 -1
- package/dist/plugins/blog/query-keys.d.cts +1 -1
- package/dist/plugins/blog/query-keys.d.mts +1 -1
- package/dist/plugins/blog/query-keys.d.ts +1 -1
- package/dist/plugins/cms/api/index.d.cts +1 -1
- package/dist/plugins/cms/api/index.d.mts +1 -1
- package/dist/plugins/cms/api/index.d.ts +1 -1
- package/dist/plugins/cms/query-keys.d.cts +1 -1
- package/dist/plugins/cms/query-keys.d.mts +1 -1
- package/dist/plugins/cms/query-keys.d.ts +1 -1
- package/dist/plugins/form-builder/api/index.d.cts +1 -1
- package/dist/plugins/form-builder/api/index.d.mts +1 -1
- package/dist/plugins/form-builder/api/index.d.ts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/form-builder/query-keys.d.cts +1 -1
- package/dist/plugins/form-builder/query-keys.d.mts +1 -1
- package/dist/plugins/form-builder/query-keys.d.ts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/ui-builder/client/index.d.cts +1 -1
- package/dist/plugins/ui-builder/client/index.d.mts +1 -1
- package/dist/plugins/ui-builder/client/index.d.ts +1 -1
- package/dist/plugins/ui-builder/query-keys.d.cts +1 -1
- package/dist/plugins/ui-builder/query-keys.d.mts +1 -1
- package/dist/plugins/ui-builder/query-keys.d.ts +1 -1
- package/dist/shared/{stack.DDQlE2Pm.d.cts → stack.Cei2T2gc.d.cts} +1 -0
- package/dist/shared/{stack.C4zspX0T.d.ts → stack.DDQTbADz.d.ts} +7 -2
- package/dist/shared/{stack.tAUeRAtd.d.mts → stack.DZRZAzcx.d.mts} +1 -0
- package/dist/shared/{stack.ymWsP4HZ.d.cts → stack.IY-ngxuw.d.cts} +7 -2
- package/dist/shared/{stack.DlehgNZZ.d.ts → stack.byjorQi5.d.ts} +1 -0
- package/dist/shared/{stack.Ps3MDhTC.d.mts → stack.mU9iMF2j.d.mts} +7 -2
- package/package.json +1 -1
- package/src/plugins/ai-chat/__tests__/authorization.test.ts +56 -30
- package/src/plugins/ai-chat/__tests__/chat-input-authorization.test.tsx +42 -0
- package/src/plugins/ai-chat/__tests__/classnames-overrides.test.tsx +25 -0
- package/src/plugins/ai-chat/__tests__/client-sweep.test.tsx +101 -0
- package/src/plugins/ai-chat/__tests__/compaction.test.ts +79 -19
- package/src/plugins/ai-chat/__tests__/persisted-compaction.test.ts +948 -0
- package/src/plugins/ai-chat/api/compaction.ts +243 -52
- package/src/plugins/ai-chat/api/context-checkpoint.ts +66 -0
- package/src/plugins/ai-chat/api/getters.ts +2 -0
- package/src/plugins/ai-chat/api/operations.ts +377 -101
- package/src/plugins/ai-chat/api/plugin.ts +14 -8
- package/src/plugins/ai-chat/api/serializers.ts +1 -0
- package/src/plugins/ai-chat/client/components/chat-input.tsx +3 -3
- package/src/plugins/ai-chat/client/components/chat-interface.tsx +43 -23
- package/src/plugins/ai-chat/client/components/chat-message.tsx +32 -2
- package/src/plugins/ai-chat/client/localization/index.ts +1 -0
- package/src/plugins/ai-chat/db.ts +8 -0
- package/src/plugins/ai-chat/history.ts +68 -0
- package/src/plugins/ai-chat/types.ts +2 -0
- package/dist/shared/{stack.B8PG21zX.d.mts → stack.BLYaUcQa.d.mts} +88 -88
- package/dist/shared/{stack.DNw6Hv5o.d.cts → stack.BOZRaVpu.d.cts} +2 -2
- package/dist/shared/{stack.D6Pg-ufX.d.cts → stack.Bf6BGZNk.d.cts} +88 -88
- package/dist/shared/{stack.CGmh-Lpe.d.ts → stack.BuxUQqL8.d.ts} +12 -12
- package/dist/shared/{stack.zch70Loj.d.cts → stack.CX83YNuS.d.cts} +12 -12
- package/dist/shared/{stack.CyalJ0j6.d.ts → stack.CyltNNZZ.d.ts} +88 -88
- package/dist/shared/{stack.Clil75lW.d.ts → stack.DlBoNqYu.d.ts} +2 -2
- package/dist/shared/{stack.DhZtP2JZ.d.mts → stack.Dvy7FmaA.d.mts} +2 -2
- package/dist/shared/{stack.Doy2_wgl.d.mts → stack.Egr3RWhA.d.mts} +12 -12
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { persistedHistory } from "../../history";
|
|
4
|
+
|
|
3
5
|
import { useChat } from "@ai-sdk/react";
|
|
4
6
|
import {
|
|
5
7
|
useEffect,
|
|
@@ -94,30 +96,28 @@ function reconcilePersistedMessageIds(
|
|
|
94
96
|
return messages.map((message) => ({
|
|
95
97
|
...message,
|
|
96
98
|
id: persistedIds.get(message) ?? message.id,
|
|
99
|
+
...(persistedMessages.some(
|
|
100
|
+
(saved) => saved.id === message.id && saved.interrupted,
|
|
101
|
+
)
|
|
102
|
+
? { metadata: { ...(message.metadata as object), interrupted: true } }
|
|
103
|
+
: {}),
|
|
97
104
|
}));
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
function persistedMessagesToUiMessages(
|
|
101
108
|
messages: readonly SerializedMessage[],
|
|
102
109
|
): UIMessage[] {
|
|
103
|
-
return messages
|
|
104
|
-
|
|
105
|
-
.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
return {
|
|
116
|
-
id: message.id,
|
|
117
|
-
role: message.role as "user" | "assistant" | "system",
|
|
118
|
-
parts,
|
|
119
|
-
};
|
|
120
|
-
});
|
|
110
|
+
return persistedHistory(messages).map((message) => {
|
|
111
|
+
// Browser tool callbacks do not resume when saved history is restored.
|
|
112
|
+
const abandoned = message.parts.some(
|
|
113
|
+
(part) =>
|
|
114
|
+
"toolCallId" in part &&
|
|
115
|
+
(part.state === "input-streaming" || part.state === "input-available"),
|
|
116
|
+
);
|
|
117
|
+
return abandoned
|
|
118
|
+
? { ...message, metadata: { interrupted: true } }
|
|
119
|
+
: message;
|
|
120
|
+
});
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
function ChatActionCheck({
|
|
@@ -566,17 +566,19 @@ export function ChatInterface({
|
|
|
566
566
|
id: `${chatInstanceId}:${isPublicMode ? "public" : identitySessionVersion}`,
|
|
567
567
|
transport,
|
|
568
568
|
onData: ({ type, data }) => {
|
|
569
|
+
if (!mounted.current || !data || typeof data !== "object") return;
|
|
569
570
|
if (
|
|
570
|
-
!isPublicMode
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
571
|
+
!isPublicMode &&
|
|
572
|
+
(identitySessionVersion !== identitySessionGeneration.current ||
|
|
573
|
+
activeStreamPartitionKey.current !==
|
|
574
|
+
latestIdentityPartitionKey.current)
|
|
574
575
|
)
|
|
575
576
|
return;
|
|
576
577
|
if (type === "data-context-status" && "compacting" in data) {
|
|
577
578
|
setIsCompacting(data.compacting === true);
|
|
578
579
|
}
|
|
579
580
|
if (
|
|
581
|
+
isPublicMode &&
|
|
580
582
|
type === "data-context-checkpoint" &&
|
|
581
583
|
"summary" in data &&
|
|
582
584
|
"throughMessageId" in data &&
|
|
@@ -726,6 +728,25 @@ export function ChatInterface({
|
|
|
726
728
|
).length,
|
|
727
729
|
});
|
|
728
730
|
}
|
|
731
|
+
if (
|
|
732
|
+
completion?.isAbort ||
|
|
733
|
+
completion?.isError ||
|
|
734
|
+
completion?.isDisconnect
|
|
735
|
+
) {
|
|
736
|
+
setMessages((current) =>
|
|
737
|
+
current.map((message) =>
|
|
738
|
+
message.id === completion.message.id
|
|
739
|
+
? {
|
|
740
|
+
...message,
|
|
741
|
+
metadata: {
|
|
742
|
+
...(message.metadata as object),
|
|
743
|
+
interrupted: true,
|
|
744
|
+
},
|
|
745
|
+
}
|
|
746
|
+
: message,
|
|
747
|
+
),
|
|
748
|
+
);
|
|
749
|
+
}
|
|
729
750
|
// In public mode, skip all persistence-related operations
|
|
730
751
|
if (isPublicMode) {
|
|
731
752
|
activeStreamPartitionKey.current = undefined;
|
|
@@ -1449,7 +1470,6 @@ export function ChatInterface({
|
|
|
1449
1470
|
variant={isWidget ? "compact" : "default"}
|
|
1450
1471
|
onFilesAttached={setAttachedFiles}
|
|
1451
1472
|
attachedFiles={attachedFiles}
|
|
1452
|
-
allowAttachments={!isPublicMode}
|
|
1453
1473
|
attachmentPermissionFacts={{
|
|
1454
1474
|
...(currentConversationId
|
|
1455
1475
|
? { conversationId: currentConversationId }
|
|
@@ -132,6 +132,12 @@ export function ChatMessage({
|
|
|
132
132
|
isRetrying = false,
|
|
133
133
|
}: ChatMessageProps) {
|
|
134
134
|
const isUser = message.role === "user";
|
|
135
|
+
const interrupted = Boolean(
|
|
136
|
+
message.metadata &&
|
|
137
|
+
typeof message.metadata === "object" &&
|
|
138
|
+
"interrupted" in message.metadata &&
|
|
139
|
+
message.metadata.interrupted,
|
|
140
|
+
);
|
|
135
141
|
|
|
136
142
|
const {
|
|
137
143
|
localization: customLocalization,
|
|
@@ -479,10 +485,22 @@ export function ChatMessage({
|
|
|
479
485
|
{toolParts.map((part: any) => {
|
|
480
486
|
const toolName = getToolName(part);
|
|
481
487
|
const toolCallId = part.toolCallId;
|
|
482
|
-
const
|
|
488
|
+
const unfinished =
|
|
489
|
+
interrupted &&
|
|
490
|
+
(part.state === "input-streaming" ||
|
|
491
|
+
part.state === "input-available");
|
|
492
|
+
const state = (
|
|
493
|
+
unfinished ? "output-error" : part.state
|
|
494
|
+
) as ToolCallState;
|
|
483
495
|
const input = part.input;
|
|
484
496
|
const output = part.output;
|
|
485
|
-
const errorText =
|
|
497
|
+
const errorText = unfinished
|
|
498
|
+
? tr(
|
|
499
|
+
"CHAT_INTERRUPTED",
|
|
500
|
+
"aiChat.interrupted",
|
|
501
|
+
"Response interrupted",
|
|
502
|
+
)
|
|
503
|
+
: part.errorText;
|
|
486
504
|
const isLoading =
|
|
487
505
|
state === "input-streaming" ||
|
|
488
506
|
state === "input-available";
|
|
@@ -513,6 +531,18 @@ export function ChatMessage({
|
|
|
513
531
|
</div>
|
|
514
532
|
)}
|
|
515
533
|
{/* Text content */}
|
|
534
|
+
{interrupted && !isStreaming && (
|
|
535
|
+
<p
|
|
536
|
+
className="text-xs text-muted-foreground"
|
|
537
|
+
data-testid="chat-interrupted"
|
|
538
|
+
>
|
|
539
|
+
{tr(
|
|
540
|
+
"CHAT_INTERRUPTED",
|
|
541
|
+
"aiChat.interrupted",
|
|
542
|
+
"Response interrupted",
|
|
543
|
+
)}
|
|
544
|
+
</p>
|
|
545
|
+
)}
|
|
516
546
|
{displayContent ? (
|
|
517
547
|
<MarkdownContent
|
|
518
548
|
markdown={displayContent}
|
|
@@ -21,6 +21,10 @@ export const aiChatSchema = createDbPlugin("aiChat", {
|
|
|
21
21
|
type: "string",
|
|
22
22
|
required: true,
|
|
23
23
|
},
|
|
24
|
+
contextCheckpoint: {
|
|
25
|
+
type: "string",
|
|
26
|
+
required: false,
|
|
27
|
+
},
|
|
24
28
|
createdAt: {
|
|
25
29
|
type: "date",
|
|
26
30
|
defaultValue: () => new Date(),
|
|
@@ -56,6 +60,10 @@ export const aiChatSchema = createDbPlugin("aiChat", {
|
|
|
56
60
|
type: "string",
|
|
57
61
|
required: true,
|
|
58
62
|
},
|
|
63
|
+
interrupted: {
|
|
64
|
+
type: "boolean",
|
|
65
|
+
required: false,
|
|
66
|
+
},
|
|
59
67
|
createdAt: {
|
|
60
68
|
type: "date",
|
|
61
69
|
defaultValue: () => new Date(),
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { UIMessage } from "ai";
|
|
2
|
+
import type { Message } from "./types";
|
|
3
|
+
|
|
4
|
+
function isMessagePart(value: unknown): boolean {
|
|
5
|
+
if (!value || typeof value !== "object" || !("type" in value)) return false;
|
|
6
|
+
const part = value as Record<string, unknown>;
|
|
7
|
+
if (typeof part.type !== "string") return false;
|
|
8
|
+
switch (part.type) {
|
|
9
|
+
case "text":
|
|
10
|
+
case "reasoning":
|
|
11
|
+
return typeof part.text === "string";
|
|
12
|
+
case "file":
|
|
13
|
+
return typeof part.url === "string" && typeof part.mediaType === "string";
|
|
14
|
+
case "source-url":
|
|
15
|
+
return typeof part.sourceId === "string" && typeof part.url === "string";
|
|
16
|
+
case "source-document":
|
|
17
|
+
return (
|
|
18
|
+
typeof part.sourceId === "string" &&
|
|
19
|
+
typeof part.mediaType === "string" &&
|
|
20
|
+
typeof part.title === "string"
|
|
21
|
+
);
|
|
22
|
+
case "step-start":
|
|
23
|
+
return true;
|
|
24
|
+
default:
|
|
25
|
+
if (part.type.startsWith("data-")) return "data" in part;
|
|
26
|
+
return (
|
|
27
|
+
(part.type.startsWith("tool-") ||
|
|
28
|
+
(part.type === "dynamic-tool" &&
|
|
29
|
+
typeof part.toolName === "string")) &&
|
|
30
|
+
typeof part.toolCallId === "string" &&
|
|
31
|
+
[
|
|
32
|
+
"input-streaming",
|
|
33
|
+
"input-available",
|
|
34
|
+
"output-available",
|
|
35
|
+
"output-error",
|
|
36
|
+
].includes(String(part.state))
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function persistedHistory(
|
|
42
|
+
messages: readonly Pick<Message, "id" | "role" | "content" | "interrupted">[],
|
|
43
|
+
): UIMessage[] {
|
|
44
|
+
return messages
|
|
45
|
+
.filter((message) => message.role !== "data")
|
|
46
|
+
.map((message) => {
|
|
47
|
+
let parts: UIMessage["parts"];
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(message.content);
|
|
50
|
+
// The old completion writer saved [] for empty assistant replies.
|
|
51
|
+
// Empty arrays in legacy user text must remain literal text.
|
|
52
|
+
parts =
|
|
53
|
+
Array.isArray(parsed) &&
|
|
54
|
+
(parsed.length > 0 || message.role === "assistant") &&
|
|
55
|
+
parsed.every(isMessagePart)
|
|
56
|
+
? parsed
|
|
57
|
+
: [{ type: "text", text: message.content }];
|
|
58
|
+
} catch {
|
|
59
|
+
parts = [{ type: "text", text: message.content }];
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
id: message.id,
|
|
63
|
+
role: message.role as UIMessage["role"],
|
|
64
|
+
parts,
|
|
65
|
+
...(message.interrupted ? { metadata: { interrupted: true } } : {}),
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
}
|