@btst/stack 4.2.0 → 4.4.0
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 +249 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/compaction.mjs +241 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/operations.cjs +20 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/operations.mjs +20 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +5 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +5 -0
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.cjs +38 -3
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +38 -3
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.cjs +173 -72
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.mjs +176 -75
- package/dist/packages/stack/src/plugins/ai-chat/client/context-checkpoint.cjs +30 -0
- package/dist/packages/stack/src/plugins/ai-chat/client/context-checkpoint.mjs +27 -0
- package/dist/packages/stack/src/plugins/ai-chat/schemas.cjs +2 -0
- package/dist/packages/stack/src/plugins/ai-chat/schemas.mjs +2 -0
- package/dist/packages/ui/src/components/markdown-content.cjs +9 -9
- package/dist/packages/ui/src/components/markdown-content.mjs +9 -9
- 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 +2 -2
- package/dist/plugins/blog/api/index.d.mts +2 -2
- package/dist/plugins/blog/api/index.d.ts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
- package/dist/plugins/blog/client/index.d.cts +2 -2
- package/dist/plugins/blog/client/index.d.mts +2 -2
- package/dist/plugins/blog/client/index.d.ts +2 -2
- package/dist/plugins/blog/query-keys.d.cts +2 -2
- package/dist/plugins/blog/query-keys.d.mts +2 -2
- package/dist/plugins/blog/query-keys.d.ts +2 -2
- package/dist/shared/{stack.BTStHVTy.d.cts → stack.BGzgFx3R.d.cts} +5 -5
- package/dist/shared/{stack.DBH1Bjko.d.ts → stack.C4zspX0T.d.ts} +13 -2
- package/dist/shared/{stack.94kN3pEK.d.mts → stack.CjCSDfWy.d.mts} +5 -5
- package/dist/shared/{stack.D5b9l24t.d.ts → stack.Clil75lW.d.ts} +35 -35
- package/dist/shared/{stack.xAiy2VtO.d.cts → stack.DDQlE2Pm.d.cts} +10 -3
- package/dist/shared/{stack.Ce6V7eDj.d.cts → stack.DNw6Hv5o.d.cts} +35 -35
- package/dist/shared/{stack.CTneIW0u.d.mts → stack.DhZtP2JZ.d.mts} +35 -35
- package/dist/shared/{stack.C-eQN_Jt.d.ts → stack.DlehgNZZ.d.ts} +10 -3
- package/dist/shared/{stack.DfrRYOIl.d.ts → stack.Dpg_JjIo.d.ts} +5 -5
- package/dist/shared/{stack.B7Mj74BQ.d.mts → stack.Ps3MDhTC.d.mts} +13 -2
- package/dist/shared/{stack.C5FZr-jJ.d.mts → stack.tAUeRAtd.d.mts} +10 -3
- package/dist/shared/{stack.Dgt3MCtW.d.cts → stack.ymWsP4HZ.d.cts} +13 -2
- package/package.json +2 -2
- package/src/__tests__/markdown-link-navigation.test.tsx +29 -0
- package/src/plugins/ai-chat/__tests__/classnames-overrides.test.tsx +22 -2
- package/src/plugins/ai-chat/__tests__/compaction.test.ts +272 -0
- package/src/plugins/ai-chat/__tests__/page-widget.test.tsx +103 -1
- package/src/plugins/ai-chat/api/compaction.ts +310 -0
- package/src/plugins/ai-chat/api/operations.ts +25 -0
- package/src/plugins/ai-chat/api/plugin.ts +14 -0
- package/src/plugins/ai-chat/client/components/chat-interface.tsx +58 -3
- package/src/plugins/ai-chat/client/components/chat-layout.tsx +197 -72
- package/src/plugins/ai-chat/client/context-checkpoint.ts +47 -0
- package/src/plugins/ai-chat/client/localization/index.ts +5 -0
- package/src/plugins/ai-chat/schemas.ts +2 -0
- package/dist/shared/{stack.CKc_Bais.d.cts → stack.Csi-4Mgz.d.cts} +7 -7
- package/dist/shared/{stack.CKc_Bais.d.mts → stack.Csi-4Mgz.d.mts} +7 -7
- package/dist/shared/{stack.CKc_Bais.d.ts → stack.Csi-4Mgz.d.ts} +7 -7
|
@@ -6,7 +6,14 @@ import {
|
|
|
6
6
|
useEffect,
|
|
7
7
|
useRef,
|
|
8
8
|
type CSSProperties,
|
|
9
|
+
type ReactNode,
|
|
9
10
|
} from "react";
|
|
11
|
+
import { createPortal } from "react-dom";
|
|
12
|
+
import {
|
|
13
|
+
Dialog,
|
|
14
|
+
DialogContent,
|
|
15
|
+
DialogTitle,
|
|
16
|
+
} from "@workspace/ui/components/dialog";
|
|
10
17
|
import { Button } from "@workspace/ui/components/button";
|
|
11
18
|
import { Badge } from "@workspace/ui/components/badge";
|
|
12
19
|
import {
|
|
@@ -19,6 +26,8 @@ import {
|
|
|
19
26
|
PanelLeftClose,
|
|
20
27
|
PanelLeft,
|
|
21
28
|
Sparkles,
|
|
29
|
+
Maximize2,
|
|
30
|
+
Minimize2,
|
|
22
31
|
Trash2,
|
|
23
32
|
X,
|
|
24
33
|
} from "lucide-react";
|
|
@@ -67,10 +76,12 @@ interface ChatLayoutBaseProps {
|
|
|
67
76
|
interface ChatLayoutWidgetProps extends ChatLayoutBaseProps {
|
|
68
77
|
/** Widget mode: compact embeddable panel with a floating trigger button */
|
|
69
78
|
layout: "widget";
|
|
70
|
-
/** Height of the widget panel. Default: `"min(
|
|
79
|
+
/** Height of the widget panel. Default: `"min(640px, calc(100dvh - 112px))"` */
|
|
71
80
|
widgetHeight?: string | number;
|
|
72
|
-
/** Width of the widget panel. Default: `"min(
|
|
81
|
+
/** Width of the widget panel. Default: `"min(440px, calc(100vw - 32px))"` */
|
|
73
82
|
widgetWidth?: string | number;
|
|
83
|
+
/** Show a button to expand the widget to the viewport. Default: false. Escape restores the compact size. */
|
|
84
|
+
expandable?: boolean;
|
|
74
85
|
/** Fix the widget to the bottom right. Default: false (embedded). Override offsets with className or style. */
|
|
75
86
|
floating?: boolean;
|
|
76
87
|
/** Optional dismissible introduction shown while the widget is closed. */
|
|
@@ -91,6 +102,8 @@ interface ChatLayoutWidgetProps extends ChatLayoutBaseProps {
|
|
|
91
102
|
* so that the built-in button does not appear alongside your own UI.
|
|
92
103
|
*/
|
|
93
104
|
showTrigger?: boolean;
|
|
105
|
+
/** Icon shown in the closed widget's trigger button. Defaults to Sparkles. */
|
|
106
|
+
triggerIcon?: ReactNode;
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
interface ChatLayoutFullProps extends ChatLayoutBaseProps {
|
|
@@ -139,16 +152,17 @@ function ChatLayoutContent(props: ChatLayoutProps) {
|
|
|
139
152
|
// Widget-specific props — TypeScript narrows props to ChatLayoutWidgetProps here
|
|
140
153
|
const widgetHeight =
|
|
141
154
|
props.layout === "widget"
|
|
142
|
-
? (props.widgetHeight ?? "min(
|
|
155
|
+
? (props.widgetHeight ?? "min(640px, calc(100dvh - 112px))")
|
|
143
156
|
: undefined;
|
|
144
157
|
const widgetWidth =
|
|
145
158
|
props.layout === "widget"
|
|
146
|
-
? (props.widgetWidth ?? "min(
|
|
159
|
+
? (props.widgetWidth ?? "min(440px, calc(100vw - 32px))")
|
|
147
160
|
: undefined;
|
|
148
161
|
const defaultOpen =
|
|
149
162
|
props.layout === "widget" ? (props.defaultOpen ?? false) : false;
|
|
150
163
|
const showTrigger =
|
|
151
164
|
props.layout === "widget" ? (props.showTrigger ?? true) : true;
|
|
165
|
+
const triggerIcon = props.layout === "widget" ? props.triggerIcon : undefined;
|
|
152
166
|
|
|
153
167
|
const [sidebarOpen, setSidebarOpen] = useState(true);
|
|
154
168
|
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
|
@@ -156,6 +170,40 @@ function ChatLayoutContent(props: ChatLayoutProps) {
|
|
|
156
170
|
const [chatResetKey, setChatResetKey] = useState(0);
|
|
157
171
|
// Widget open/closed state — starts with defaultOpen value
|
|
158
172
|
const [widgetOpen, setWidgetOpen] = useState(defaultOpen);
|
|
173
|
+
const [expanded, setExpanded] = useState(false);
|
|
174
|
+
const expandable = props.layout === "widget" && props.expandable === true;
|
|
175
|
+
const widgetExpanded = expandable && expanded && widgetOpen;
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (!expandable) setExpanded(false);
|
|
178
|
+
}, [expandable]);
|
|
179
|
+
const compactPanelRef = useRef<HTMLDivElement>(null);
|
|
180
|
+
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
181
|
+
const expandRef = useRef<HTMLButtonElement>(null);
|
|
182
|
+
const [panelContainer, setPanelContainer] = useState<HTMLDivElement | null>(
|
|
183
|
+
null,
|
|
184
|
+
);
|
|
185
|
+
const expandLabel = widgetExpanded
|
|
186
|
+
? tr("A11Y_COLLAPSE_CHAT", "aiChat.a11y.collapseChat", "Collapse chat")
|
|
187
|
+
: tr("A11Y_EXPAND_CHAT", "aiChat.a11y.expandChat", "Expand chat");
|
|
188
|
+
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
if (layout !== "widget") return;
|
|
191
|
+
const container = document.createElement("div");
|
|
192
|
+
container.className = "flex h-full min-h-0 flex-col";
|
|
193
|
+
compactPanelRef.current?.appendChild(container);
|
|
194
|
+
setPanelContainer(container);
|
|
195
|
+
return () => container.remove();
|
|
196
|
+
}, [layout]);
|
|
197
|
+
// The portal target stays the same; only its DOM parent changes. This keeps
|
|
198
|
+
// the draft, attachments and active stream mounted while Dialog owns modality.
|
|
199
|
+
const mountExpandedPanel = useCallback(
|
|
200
|
+
(node: HTMLDivElement | null) => {
|
|
201
|
+
if (panelContainer) {
|
|
202
|
+
(node ?? compactPanelRef.current)?.appendChild(panelContainer);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
[panelContainer],
|
|
206
|
+
);
|
|
159
207
|
// Key to force widget ChatInterface remount on clear
|
|
160
208
|
const [widgetResetKey, setWidgetResetKey] = useState(0);
|
|
161
209
|
// Only mount the widget ChatInterface after the widget has been opened at least once.
|
|
@@ -243,90 +291,167 @@ function ChatLayoutContent(props: ChatLayoutProps) {
|
|
|
243
291
|
</Button>
|
|
244
292
|
</div>
|
|
245
293
|
)}
|
|
246
|
-
{/*
|
|
294
|
+
{/* Compact panel remains available when the fullscreen dialog closes. */}
|
|
247
295
|
<div
|
|
296
|
+
ref={compactPanelRef}
|
|
297
|
+
role="dialog"
|
|
298
|
+
tabIndex={-1}
|
|
299
|
+
aria-label={tr("A11Y_CHAT_TITLE", "aiChat.a11y.title", "AI Chat")}
|
|
248
300
|
className={cn(
|
|
249
|
-
"
|
|
250
|
-
widgetOpen
|
|
301
|
+
"overflow-hidden rounded-xl border bg-background shadow-xl",
|
|
302
|
+
(!widgetOpen || widgetExpanded) && "hidden",
|
|
251
303
|
)}
|
|
252
304
|
style={{ height: widgetHeight, width: widgetWidth }}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
<
|
|
256
|
-
|
|
257
|
-
{
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
305
|
+
/>
|
|
306
|
+
<Dialog open={widgetExpanded} onOpenChange={setExpanded}>
|
|
307
|
+
<DialogContent
|
|
308
|
+
showCloseButton={false}
|
|
309
|
+
style={{
|
|
310
|
+
...style,
|
|
311
|
+
position: "fixed",
|
|
312
|
+
inset: 0,
|
|
313
|
+
top: 0,
|
|
314
|
+
right: 0,
|
|
315
|
+
bottom: 0,
|
|
316
|
+
left: 0,
|
|
317
|
+
width: "100vw",
|
|
318
|
+
height: "100dvh",
|
|
319
|
+
minWidth: 0,
|
|
320
|
+
minHeight: 0,
|
|
321
|
+
maxWidth: "none",
|
|
322
|
+
maxHeight: "none",
|
|
323
|
+
margin: 0,
|
|
324
|
+
transform: "none",
|
|
325
|
+
translate: "none",
|
|
326
|
+
zIndex: 50,
|
|
327
|
+
animation: "none",
|
|
328
|
+
}}
|
|
329
|
+
aria-describedby={undefined}
|
|
330
|
+
className={cn(
|
|
331
|
+
className,
|
|
332
|
+
classNames?.container,
|
|
333
|
+
"fixed inset-0 z-50 flex h-dvh w-screen max-w-none translate-x-0 translate-y-0 flex-col gap-0 rounded-none border-0 p-0 sm:max-w-none pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]",
|
|
269
334
|
)}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
335
|
+
onCloseAutoFocus={(event) => {
|
|
336
|
+
const target = widgetOpen
|
|
337
|
+
? (expandRef.current ?? compactPanelRef.current)
|
|
338
|
+
: triggerRef.current;
|
|
339
|
+
if (target) {
|
|
340
|
+
event.preventDefault();
|
|
341
|
+
target.focus();
|
|
342
|
+
}
|
|
343
|
+
}}
|
|
344
|
+
>
|
|
345
|
+
<DialogTitle className="sr-only">
|
|
346
|
+
{tr("A11Y_CHAT_TITLE", "aiChat.a11y.title", "AI Chat")}
|
|
347
|
+
</DialogTitle>
|
|
348
|
+
<div ref={mountExpandedPanel} className="min-h-0 flex-1" />
|
|
349
|
+
</DialogContent>
|
|
350
|
+
</Dialog>
|
|
351
|
+
{panelContainer &&
|
|
352
|
+
createPortal(
|
|
353
|
+
<>
|
|
354
|
+
{/* Widget header with page context badge and action buttons */}
|
|
355
|
+
<div className="flex shrink-0 items-center gap-1.5 px-3 py-1.5 border-b bg-muted/40">
|
|
356
|
+
<Sparkles className="h-3 w-3 text-muted-foreground" />
|
|
357
|
+
{pageAIContext ? (
|
|
358
|
+
<Badge
|
|
359
|
+
variant="secondary"
|
|
360
|
+
className="text-xs"
|
|
361
|
+
data-testid="page-context-badge"
|
|
362
|
+
>
|
|
363
|
+
{pageAIContext.routeName}
|
|
364
|
+
</Badge>
|
|
365
|
+
) : (
|
|
366
|
+
<span className="text-xs text-muted-foreground font-medium">
|
|
367
|
+
{tr("A11Y_CHAT_TITLE", "aiChat.a11y.title", "AI Chat")}
|
|
368
|
+
</span>
|
|
369
|
+
)}
|
|
370
|
+
<div className="flex-1" />
|
|
371
|
+
{expandable && (
|
|
372
|
+
<Button
|
|
373
|
+
type="button"
|
|
374
|
+
variant="ghost"
|
|
375
|
+
size="icon"
|
|
376
|
+
className="h-7 w-7"
|
|
377
|
+
ref={expandRef}
|
|
378
|
+
onClick={() => setExpanded((prev) => !prev)}
|
|
379
|
+
aria-label={expandLabel}
|
|
380
|
+
title={expandLabel}
|
|
381
|
+
aria-expanded={widgetExpanded}
|
|
382
|
+
>
|
|
383
|
+
{widgetExpanded ? (
|
|
384
|
+
<Minimize2 className="h-3.5 w-3.5" aria-hidden="true" />
|
|
385
|
+
) : (
|
|
386
|
+
<Maximize2 className="h-3.5 w-3.5" aria-hidden="true" />
|
|
387
|
+
)}
|
|
388
|
+
</Button>
|
|
389
|
+
)}
|
|
390
|
+
<Button
|
|
391
|
+
variant="ghost"
|
|
392
|
+
size="icon"
|
|
393
|
+
className="h-5 w-5"
|
|
394
|
+
onClick={() => {
|
|
395
|
+
track({ type: "conversation_cleared" });
|
|
396
|
+
onClear?.();
|
|
397
|
+
setWidgetResetKey((prev) => prev + 1);
|
|
398
|
+
}}
|
|
399
|
+
aria-label={tr(
|
|
400
|
+
"A11Y_CLEAR_CHAT",
|
|
401
|
+
"aiChat.a11y.clearChat",
|
|
402
|
+
"Clear chat",
|
|
403
|
+
)}
|
|
404
|
+
title={tr(
|
|
405
|
+
"A11Y_CLEAR_CHAT",
|
|
406
|
+
"aiChat.a11y.clearChat",
|
|
407
|
+
"Clear chat",
|
|
408
|
+
)}
|
|
409
|
+
>
|
|
410
|
+
<Trash2 className="h-3.5 w-3.5" />
|
|
411
|
+
</Button>
|
|
412
|
+
<Button
|
|
413
|
+
variant="ghost"
|
|
414
|
+
size="icon"
|
|
415
|
+
className="h-5 w-5"
|
|
416
|
+
onClick={() => {
|
|
417
|
+
setWidgetOpen(false);
|
|
418
|
+
setExpanded(false);
|
|
419
|
+
triggerRef.current?.focus();
|
|
420
|
+
track({ type: "widget_closed" });
|
|
421
|
+
}}
|
|
422
|
+
aria-label={tr(
|
|
423
|
+
"A11Y_CLOSE_CHAT",
|
|
424
|
+
"aiChat.a11y.closeChat",
|
|
425
|
+
"Close chat",
|
|
426
|
+
)}
|
|
427
|
+
>
|
|
428
|
+
<X className="h-3.5 w-3.5" />
|
|
429
|
+
</Button>
|
|
430
|
+
</div>
|
|
431
|
+
{widgetEverOpened && (
|
|
432
|
+
<ChatInterface
|
|
433
|
+
key={`widget-${conversationId ?? "new"}-${widgetResetKey}`}
|
|
434
|
+
id={conversationId}
|
|
435
|
+
variant="widget"
|
|
436
|
+
initialMessages={initialMessages}
|
|
437
|
+
onMessagesChange={onMessagesChange}
|
|
438
|
+
/>
|
|
305
439
|
)}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
</Button>
|
|
309
|
-
</div>
|
|
310
|
-
{widgetEverOpened && (
|
|
311
|
-
<ChatInterface
|
|
312
|
-
key={`widget-${conversationId ?? "new"}-${widgetResetKey}`}
|
|
313
|
-
id={conversationId}
|
|
314
|
-
variant="widget"
|
|
315
|
-
initialMessages={initialMessages}
|
|
316
|
-
onMessagesChange={onMessagesChange}
|
|
317
|
-
/>
|
|
440
|
+
</>,
|
|
441
|
+
panelContainer,
|
|
318
442
|
)}
|
|
319
|
-
</div>
|
|
320
443
|
|
|
321
444
|
{/* Trigger button — rendered only when showTrigger is true */}
|
|
322
445
|
{showTrigger && (
|
|
323
446
|
<Button
|
|
447
|
+
ref={triggerRef}
|
|
324
448
|
size="icon"
|
|
325
449
|
className="h-12 w-12 rounded-full shadow-lg"
|
|
326
450
|
onClick={() => {
|
|
327
451
|
dismissTip("open");
|
|
328
452
|
track({ type: widgetOpen ? "widget_closed" : "widget_opened" });
|
|
329
453
|
setWidgetOpen((prev) => !prev);
|
|
454
|
+
setExpanded(false);
|
|
330
455
|
setWidgetEverOpened(true);
|
|
331
456
|
}}
|
|
332
457
|
aria-label={
|
|
@@ -339,7 +464,7 @@ function ChatLayoutContent(props: ChatLayoutProps) {
|
|
|
339
464
|
{widgetOpen ? (
|
|
340
465
|
<X className="h-5 w-5" />
|
|
341
466
|
) : (
|
|
342
|
-
<Sparkles className="h-5 w-5" />
|
|
467
|
+
(triggerIcon ?? <Sparkles className="h-5 w-5" />)
|
|
343
468
|
)}
|
|
344
469
|
</Button>
|
|
345
470
|
)}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { UIMessage } from "ai";
|
|
2
|
+
|
|
3
|
+
/** Browser-only snapshot; never replaces or mutates the visible transcript. */
|
|
4
|
+
export interface ContextCheckpoint {
|
|
5
|
+
summary: string;
|
|
6
|
+
throughMessageId: string;
|
|
7
|
+
prefix: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function captureContextCheckpoint(
|
|
11
|
+
messages: UIMessage[],
|
|
12
|
+
summary: string,
|
|
13
|
+
throughMessageId: string,
|
|
14
|
+
): ContextCheckpoint | undefined {
|
|
15
|
+
const index = messages.findIndex(
|
|
16
|
+
(message) => message.id === throughMessageId,
|
|
17
|
+
);
|
|
18
|
+
if (index < 0) return undefined;
|
|
19
|
+
return {
|
|
20
|
+
summary,
|
|
21
|
+
throughMessageId,
|
|
22
|
+
prefix: JSON.stringify(messages.slice(0, index + 1)),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function contextRequest(
|
|
27
|
+
messages: UIMessage[],
|
|
28
|
+
checkpoint?: ContextCheckpoint,
|
|
29
|
+
) {
|
|
30
|
+
if (checkpoint) {
|
|
31
|
+
const index = messages.findIndex(
|
|
32
|
+
(message) => message.id === checkpoint.throughMessageId,
|
|
33
|
+
);
|
|
34
|
+
if (
|
|
35
|
+
index >= 0 &&
|
|
36
|
+
JSON.stringify(messages.slice(0, index + 1)) === checkpoint.prefix
|
|
37
|
+
) {
|
|
38
|
+
return {
|
|
39
|
+
messages: messages.slice(index + 1),
|
|
40
|
+
contextSummary: checkpoint.summary,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Editing or retrying a summarized turn invalidates the snapshot. Resend
|
|
45
|
+
// the original transcript so the server builds a fresh summary if needed.
|
|
46
|
+
return { messages };
|
|
47
|
+
}
|
|
@@ -12,6 +12,7 @@ export interface AiChatLocalization {
|
|
|
12
12
|
CHAT_SEND_BUTTON: string;
|
|
13
13
|
CHAT_EMPTY_STATE: string;
|
|
14
14
|
CHAT_LOADING: string;
|
|
15
|
+
CHAT_COMPACTING?: string;
|
|
15
16
|
CHAT_ERROR: string;
|
|
16
17
|
CHAT_RETRY: string;
|
|
17
18
|
|
|
@@ -86,6 +87,8 @@ export interface AiChatLocalization {
|
|
|
86
87
|
A11Y_CHAT_TITLE: string;
|
|
87
88
|
A11Y_CLEAR_CHAT: string;
|
|
88
89
|
A11Y_CLOSE_CHAT: string;
|
|
90
|
+
A11Y_EXPAND_CHAT: string;
|
|
91
|
+
A11Y_COLLAPSE_CHAT: string;
|
|
89
92
|
A11Y_OPEN_CHAT: string;
|
|
90
93
|
A11Y_DISMISS_CHAT_TIP: string;
|
|
91
94
|
PAGE_CONTENT_READING: string;
|
|
@@ -194,6 +197,8 @@ export const AI_CHAT_LOCALIZATION: AiChatLocalization = {
|
|
|
194
197
|
A11Y_CHAT_TITLE: "AI Chat",
|
|
195
198
|
A11Y_CLEAR_CHAT: "Clear chat",
|
|
196
199
|
A11Y_CLOSE_CHAT: "Close chat",
|
|
200
|
+
A11Y_EXPAND_CHAT: "Expand chat",
|
|
201
|
+
A11Y_COLLAPSE_CHAT: "Collapse chat",
|
|
197
202
|
A11Y_OPEN_CHAT: "Open chat",
|
|
198
203
|
A11Y_DISMISS_CHAT_TIP: "Dismiss chat tip",
|
|
199
204
|
PAGE_CONTENT_READING: "Reading page content…",
|
|
@@ -36,6 +36,8 @@ export const chatRequestSchema = z.object({
|
|
|
36
36
|
]),
|
|
37
37
|
),
|
|
38
38
|
conversationId: z.string().optional(),
|
|
39
|
+
/** Untrusted continuation summary for opt-in public context compaction. */
|
|
40
|
+
contextSummary: z.string().max(32_000).optional(),
|
|
39
41
|
model: z.string().optional(),
|
|
40
42
|
/**
|
|
41
43
|
* Description of the current page context, injected into the AI system prompt.
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
3
|
declare const createPostSchema: z.ZodObject<{
|
|
4
|
+
tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
5
|
+
name: z.ZodString;
|
|
6
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
7
|
+
id: z.ZodString;
|
|
8
|
+
name: z.ZodString;
|
|
9
|
+
slug: z.ZodString;
|
|
10
|
+
}, z.core.$strip>]>>>>;
|
|
4
11
|
slug: z.ZodOptional<z.ZodString>;
|
|
5
12
|
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
6
13
|
createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
@@ -10,13 +17,6 @@ declare const createPostSchema: z.ZodObject<{
|
|
|
10
17
|
excerpt: z.ZodString;
|
|
11
18
|
image: z.ZodOptional<z.ZodString>;
|
|
12
19
|
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
13
|
-
tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
14
|
-
name: z.ZodString;
|
|
15
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
16
|
-
id: z.ZodString;
|
|
17
|
-
name: z.ZodString;
|
|
18
|
-
slug: z.ZodString;
|
|
19
|
-
}, z.core.$strip>]>>>>;
|
|
20
20
|
}, z.core.$strip>;
|
|
21
21
|
declare const updatePostSchema: z.ZodObject<{
|
|
22
22
|
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
3
|
declare const createPostSchema: z.ZodObject<{
|
|
4
|
+
tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
5
|
+
name: z.ZodString;
|
|
6
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
7
|
+
id: z.ZodString;
|
|
8
|
+
name: z.ZodString;
|
|
9
|
+
slug: z.ZodString;
|
|
10
|
+
}, z.core.$strip>]>>>>;
|
|
4
11
|
slug: z.ZodOptional<z.ZodString>;
|
|
5
12
|
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
6
13
|
createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
@@ -10,13 +17,6 @@ declare const createPostSchema: z.ZodObject<{
|
|
|
10
17
|
excerpt: z.ZodString;
|
|
11
18
|
image: z.ZodOptional<z.ZodString>;
|
|
12
19
|
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
13
|
-
tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
14
|
-
name: z.ZodString;
|
|
15
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
16
|
-
id: z.ZodString;
|
|
17
|
-
name: z.ZodString;
|
|
18
|
-
slug: z.ZodString;
|
|
19
|
-
}, z.core.$strip>]>>>>;
|
|
20
20
|
}, z.core.$strip>;
|
|
21
21
|
declare const updatePostSchema: z.ZodObject<{
|
|
22
22
|
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
3
|
declare const createPostSchema: z.ZodObject<{
|
|
4
|
+
tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
5
|
+
name: z.ZodString;
|
|
6
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
7
|
+
id: z.ZodString;
|
|
8
|
+
name: z.ZodString;
|
|
9
|
+
slug: z.ZodString;
|
|
10
|
+
}, z.core.$strip>]>>>>;
|
|
4
11
|
slug: z.ZodOptional<z.ZodString>;
|
|
5
12
|
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
6
13
|
createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
@@ -10,13 +17,6 @@ declare const createPostSchema: z.ZodObject<{
|
|
|
10
17
|
excerpt: z.ZodString;
|
|
11
18
|
image: z.ZodOptional<z.ZodString>;
|
|
12
19
|
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
13
|
-
tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
14
|
-
name: z.ZodString;
|
|
15
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
16
|
-
id: z.ZodString;
|
|
17
|
-
name: z.ZodString;
|
|
18
|
-
slug: z.ZodString;
|
|
19
|
-
}, z.core.$strip>]>>>>;
|
|
20
20
|
}, z.core.$strip>;
|
|
21
21
|
declare const updatePostSchema: z.ZodObject<{
|
|
22
22
|
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|