@assistant-ui/react 0.0.9 → 0.0.10
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/index.d.mts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +131 -95
- package/dist/index.mjs +134 -96
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
@@ -21,7 +21,7 @@ type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyo
|
|
21
21
|
|
22
22
|
type ThreadIfFilters = {
|
23
23
|
empty: boolean | undefined;
|
24
|
-
|
24
|
+
running: boolean | undefined;
|
25
25
|
};
|
26
26
|
type ThreadIfProps = PropsWithChildren<RequireAtLeastOne<ThreadIfFilters>>;
|
27
27
|
declare const ThreadIf: FC<ThreadIfProps>;
|
@@ -91,17 +91,16 @@ declare namespace index$3 {
|
|
91
91
|
export { ComposerCancel as Cancel, ComposerIf as If, ComposerInput as Input, ComposerRoot as Root, ComposerSend as Send };
|
92
92
|
}
|
93
93
|
|
94
|
-
type
|
94
|
+
type BaseComposerState = {
|
95
|
+
value: string;
|
96
|
+
setValue: (value: string) => void;
|
97
|
+
};
|
98
|
+
type MessageComposerState = BaseComposerState & {
|
95
99
|
isEditing: boolean;
|
96
|
-
canCancel:
|
100
|
+
canCancel: true;
|
97
101
|
edit: () => void;
|
98
102
|
send: () => void;
|
99
103
|
cancel: () => void;
|
100
|
-
value: string;
|
101
|
-
setValue: (value: string) => void;
|
102
|
-
};
|
103
|
-
type ComposerStore = {
|
104
|
-
useComposer: UseBoundStore<StoreApi<ComposerState>>;
|
105
104
|
};
|
106
105
|
|
107
106
|
type ThreadMessageTextPart = {
|
@@ -226,7 +225,7 @@ declare const ActionBarRoot: react.ForwardRefExoticComponent<Pick<Omit<react.Det
|
|
226
225
|
} & {
|
227
226
|
asChild?: boolean;
|
228
227
|
}, "key" | keyof react.HTMLAttributes<HTMLDivElement> | "asChild"> & {
|
229
|
-
|
228
|
+
hideWhenRunning?: boolean;
|
230
229
|
autohide?: "always" | "not-last" | "never";
|
231
230
|
autohideFloat?: "always" | "single-branch" | "never";
|
232
231
|
} & react.RefAttributes<HTMLDivElement>>;
|
@@ -283,8 +282,9 @@ type MessageState = {
|
|
283
282
|
isHovering: boolean;
|
284
283
|
setIsHovering: (value: boolean) => void;
|
285
284
|
};
|
286
|
-
type MessageStore =
|
285
|
+
type MessageStore = {
|
287
286
|
useMessage: UseBoundStore<StoreApi<MessageState>>;
|
287
|
+
useComposer: UseBoundStore<StoreApi<MessageComposerState>>;
|
288
288
|
};
|
289
289
|
|
290
290
|
declare const useMessageContext: () => MessageStore;
|
package/dist/index.d.ts
CHANGED
@@ -21,7 +21,7 @@ type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyo
|
|
21
21
|
|
22
22
|
type ThreadIfFilters = {
|
23
23
|
empty: boolean | undefined;
|
24
|
-
|
24
|
+
running: boolean | undefined;
|
25
25
|
};
|
26
26
|
type ThreadIfProps = PropsWithChildren<RequireAtLeastOne<ThreadIfFilters>>;
|
27
27
|
declare const ThreadIf: FC<ThreadIfProps>;
|
@@ -91,17 +91,16 @@ declare namespace index$3 {
|
|
91
91
|
export { ComposerCancel as Cancel, ComposerIf as If, ComposerInput as Input, ComposerRoot as Root, ComposerSend as Send };
|
92
92
|
}
|
93
93
|
|
94
|
-
type
|
94
|
+
type BaseComposerState = {
|
95
|
+
value: string;
|
96
|
+
setValue: (value: string) => void;
|
97
|
+
};
|
98
|
+
type MessageComposerState = BaseComposerState & {
|
95
99
|
isEditing: boolean;
|
96
|
-
canCancel:
|
100
|
+
canCancel: true;
|
97
101
|
edit: () => void;
|
98
102
|
send: () => void;
|
99
103
|
cancel: () => void;
|
100
|
-
value: string;
|
101
|
-
setValue: (value: string) => void;
|
102
|
-
};
|
103
|
-
type ComposerStore = {
|
104
|
-
useComposer: UseBoundStore<StoreApi<ComposerState>>;
|
105
104
|
};
|
106
105
|
|
107
106
|
type ThreadMessageTextPart = {
|
@@ -226,7 +225,7 @@ declare const ActionBarRoot: react.ForwardRefExoticComponent<Pick<Omit<react.Det
|
|
226
225
|
} & {
|
227
226
|
asChild?: boolean;
|
228
227
|
}, "key" | keyof react.HTMLAttributes<HTMLDivElement> | "asChild"> & {
|
229
|
-
|
228
|
+
hideWhenRunning?: boolean;
|
230
229
|
autohide?: "always" | "not-last" | "never";
|
231
230
|
autohideFloat?: "always" | "single-branch" | "never";
|
232
231
|
} & react.RefAttributes<HTMLDivElement>>;
|
@@ -283,8 +282,9 @@ type MessageState = {
|
|
283
282
|
isHovering: boolean;
|
284
283
|
setIsHovering: (value: boolean) => void;
|
285
284
|
};
|
286
|
-
type MessageStore =
|
285
|
+
type MessageStore = {
|
287
286
|
useMessage: UseBoundStore<StoreApi<MessageState>>;
|
287
|
+
useComposer: UseBoundStore<StoreApi<MessageComposerState>>;
|
288
288
|
};
|
289
289
|
|
290
290
|
declare const useMessageContext: () => MessageStore;
|
package/dist/index.js
CHANGED
@@ -86,9 +86,9 @@ var useThreadIf = (props) => {
|
|
86
86
|
return false;
|
87
87
|
if (props.empty === false && thread.messages.length === 0)
|
88
88
|
return false;
|
89
|
-
if (props.
|
89
|
+
if (props.running === true && !thread.isRunning)
|
90
90
|
return false;
|
91
|
-
if (props.
|
91
|
+
if (props.running === false && thread.isRunning)
|
92
92
|
return false;
|
93
93
|
return true;
|
94
94
|
});
|
@@ -153,12 +153,12 @@ var import_react4 = require("react");
|
|
153
153
|
var useOnScrollToBottom = (callback) => {
|
154
154
|
const callbackRef = (0, import_react4.useRef)(callback);
|
155
155
|
callbackRef.current = callback;
|
156
|
-
const {
|
156
|
+
const { useViewport } = useAssistantContext();
|
157
157
|
(0, import_react4.useEffect)(() => {
|
158
|
-
return
|
158
|
+
return useViewport.getState().onScrollToBottom(() => {
|
159
159
|
callbackRef.current();
|
160
160
|
});
|
161
|
-
}, [
|
161
|
+
}, [useViewport]);
|
162
162
|
};
|
163
163
|
|
164
164
|
// src/primitives/thread/ThreadViewport.tsx
|
@@ -166,7 +166,7 @@ var ThreadViewport = (0, import_react5.forwardRef)(({ autoScroll = true, onScrol
|
|
166
166
|
const messagesEndRef = (0, import_react5.useRef)(null);
|
167
167
|
const divRef = (0, import_react5.useRef)(null);
|
168
168
|
const ref = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, divRef);
|
169
|
-
const {
|
169
|
+
const { useViewport } = useAssistantContext();
|
170
170
|
const firstRenderRef = (0, import_react5.useRef)(true);
|
171
171
|
const lastScrollTop = (0, import_react5.useRef)(0);
|
172
172
|
const scrollToBottom = () => {
|
@@ -175,11 +175,11 @@ var ThreadViewport = (0, import_react5.forwardRef)(({ autoScroll = true, onScrol
|
|
175
175
|
return;
|
176
176
|
const behavior = firstRenderRef.current ? "instant" : "auto";
|
177
177
|
firstRenderRef.current = false;
|
178
|
-
|
178
|
+
useViewport.setState({ isAtBottom: true });
|
179
179
|
div.scrollIntoView({ behavior });
|
180
180
|
};
|
181
181
|
useOnResizeContent(divRef, () => {
|
182
|
-
if (!
|
182
|
+
if (!useViewport.getState().isAtBottom)
|
183
183
|
return;
|
184
184
|
scrollToBottom();
|
185
185
|
});
|
@@ -190,11 +190,11 @@ var ThreadViewport = (0, import_react5.forwardRef)(({ autoScroll = true, onScrol
|
|
190
190
|
const div = divRef.current;
|
191
191
|
if (!div)
|
192
192
|
return;
|
193
|
-
const isAtBottom =
|
193
|
+
const isAtBottom = useViewport.getState().isAtBottom;
|
194
194
|
const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight;
|
195
195
|
if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
|
196
196
|
} else if (newIsAtBottom !== isAtBottom) {
|
197
|
-
|
197
|
+
useViewport.setState({ isAtBottom: newIsAtBottom });
|
198
198
|
}
|
199
199
|
lastScrollTop.current = div.scrollTop;
|
200
200
|
};
|
@@ -310,13 +310,13 @@ var useVercelAIBranches = (chat, context) => {
|
|
310
310
|
[data, chat.messages, chat.setMessages]
|
311
311
|
);
|
312
312
|
const reloadMaybe = "reload" in chat ? chat.reload : void 0;
|
313
|
-
const
|
314
|
-
async (
|
313
|
+
const startRun = (0, import_react6.useCallback)(
|
314
|
+
async (parentId) => {
|
315
315
|
if (!reloadMaybe)
|
316
316
|
throw new Error("Reload not supported by Vercel AI SDK's useAssistant");
|
317
|
-
const newMessages = sliceMessagesUntil(chat.messages,
|
317
|
+
const newMessages = sliceMessagesUntil(chat.messages, parentId);
|
318
318
|
chat.setMessages(newMessages);
|
319
|
-
context.
|
319
|
+
context.useViewport.getState().scrollToBottom();
|
320
320
|
await reloadMaybe();
|
321
321
|
},
|
322
322
|
[context, chat.messages, chat.setMessages, reloadMaybe]
|
@@ -327,7 +327,7 @@ var useVercelAIBranches = (chat, context) => {
|
|
327
327
|
chat.setMessages(newMessages);
|
328
328
|
if (message.content.length !== 1 || message.content[0]?.type !== "text")
|
329
329
|
throw new Error("Only text content is currently supported");
|
330
|
-
context.
|
330
|
+
context.useViewport.getState().scrollToBottom();
|
331
331
|
await chat.append({
|
332
332
|
role: "user",
|
333
333
|
content: message.content[0].text
|
@@ -340,13 +340,13 @@ var useVercelAIBranches = (chat, context) => {
|
|
340
340
|
getBranchState,
|
341
341
|
switchToBranch,
|
342
342
|
append,
|
343
|
-
|
343
|
+
startRun
|
344
344
|
}),
|
345
|
-
[getBranchState, switchToBranch, append,
|
345
|
+
[getBranchState, switchToBranch, append, startRun]
|
346
346
|
);
|
347
347
|
};
|
348
348
|
var hasUpcomingMessage = (thread) => {
|
349
|
-
return thread.
|
349
|
+
return thread.isRunning && thread.messages[thread.messages.length - 1]?.role !== "assistant";
|
350
350
|
};
|
351
351
|
|
352
352
|
// src/utils/context/useComposerContext.ts
|
@@ -399,7 +399,54 @@ __export(message_exports, {
|
|
399
399
|
|
400
400
|
// src/primitives/message/MessageProvider.tsx
|
401
401
|
var import_react9 = require("react");
|
402
|
+
var import_zustand2 = require("zustand");
|
403
|
+
|
404
|
+
// src/utils/context/stores/ComposerStore.ts
|
402
405
|
var import_zustand = require("zustand");
|
406
|
+
var makeBaseComposer = (set) => ({
|
407
|
+
value: "",
|
408
|
+
setValue: (value) => {
|
409
|
+
set({ value });
|
410
|
+
}
|
411
|
+
});
|
412
|
+
var makeMessageComposerStore = ({
|
413
|
+
onEdit,
|
414
|
+
onSend
|
415
|
+
}) => (0, import_zustand.create)()((set, get, store) => ({
|
416
|
+
...makeBaseComposer(set, get, store),
|
417
|
+
canCancel: true,
|
418
|
+
isEditing: false,
|
419
|
+
edit: () => {
|
420
|
+
const value = onEdit();
|
421
|
+
set({ isEditing: true, value });
|
422
|
+
},
|
423
|
+
send: () => {
|
424
|
+
const value = get().value;
|
425
|
+
set({ isEditing: false });
|
426
|
+
return onSend(value);
|
427
|
+
},
|
428
|
+
cancel: () => {
|
429
|
+
set({ isEditing: false });
|
430
|
+
}
|
431
|
+
}));
|
432
|
+
var makeThreadComposerStore = ({
|
433
|
+
onSend,
|
434
|
+
onCancel
|
435
|
+
}) => (0, import_zustand.create)()((set, get, store) => ({
|
436
|
+
...makeBaseComposer(set, get, store),
|
437
|
+
isEditing: true,
|
438
|
+
canCancel: false,
|
439
|
+
send: () => {
|
440
|
+
const value = get().value;
|
441
|
+
set({ value: "", canCancel: true });
|
442
|
+
onSend(value).then(() => {
|
443
|
+
set({ canCancel: false });
|
444
|
+
});
|
445
|
+
},
|
446
|
+
cancel: onCancel
|
447
|
+
}));
|
448
|
+
|
449
|
+
// src/primitives/message/MessageProvider.tsx
|
403
450
|
var getIsLast = (thread, message) => {
|
404
451
|
const hasUpcoming = hasUpcomingMessage(thread);
|
405
452
|
return hasUpcoming ? message.id === UPCOMING_MESSAGE_ID : thread.messages[thread.messages.length - 1]?.id === message.id;
|
@@ -407,7 +454,7 @@ var getIsLast = (thread, message) => {
|
|
407
454
|
var useMessageContext2 = () => {
|
408
455
|
const [context] = (0, import_react9.useState)(() => {
|
409
456
|
const { useThread } = useAssistantContext();
|
410
|
-
const useMessage = (0,
|
457
|
+
const useMessage = (0, import_zustand2.create)(() => ({
|
411
458
|
message: null,
|
412
459
|
isLast: false,
|
413
460
|
isCopied: false,
|
@@ -417,34 +464,23 @@ var useMessageContext2 = () => {
|
|
417
464
|
setIsHovering: () => {
|
418
465
|
}
|
419
466
|
}));
|
420
|
-
const useComposer = (
|
421
|
-
|
422
|
-
canCancel: true,
|
423
|
-
edit: () => {
|
467
|
+
const useComposer = makeMessageComposerStore({
|
468
|
+
onEdit: () => {
|
424
469
|
const message = useMessage.getState().message;
|
425
470
|
if (message.role !== "user")
|
426
471
|
throw new Error("Editing is only supported for user messages");
|
427
472
|
if (message.content[0]?.type !== "text")
|
428
473
|
throw new Error("Editing is only supported for text-only messages");
|
429
|
-
return
|
430
|
-
isEditing: true,
|
431
|
-
value: message.content[0].text
|
432
|
-
});
|
474
|
+
return message.content[0].text;
|
433
475
|
},
|
434
|
-
|
435
|
-
send: () => {
|
476
|
+
onSend: (text) => {
|
436
477
|
const message = useMessage.getState().message;
|
437
|
-
|
438
|
-
throw new Error("Editing is only supported for user messages");
|
439
|
-
useThread.getState().append({
|
478
|
+
return useThread.getState().append({
|
440
479
|
parentId: message.parentId,
|
441
|
-
content: [{ type: "text", text
|
480
|
+
content: [{ type: "text", text }]
|
442
481
|
});
|
443
|
-
|
444
|
-
|
445
|
-
value: "",
|
446
|
-
setValue: (value) => set({ value })
|
447
|
-
}));
|
482
|
+
}
|
483
|
+
});
|
448
484
|
return { useMessage, useComposer };
|
449
485
|
});
|
450
486
|
return context;
|
@@ -605,11 +641,10 @@ var import_primitive3 = require("@radix-ui/primitive");
|
|
605
641
|
var import_react_primitive4 = require("@radix-ui/react-primitive");
|
606
642
|
var import_react11 = require("react");
|
607
643
|
var ThreadScrollToBottom = (0, import_react11.forwardRef)(({ onClick, ...rest }, ref) => {
|
608
|
-
const {
|
609
|
-
const isAtBottom =
|
644
|
+
const { useViewport } = useAssistantContext();
|
645
|
+
const isAtBottom = useViewport((s) => s.isAtBottom);
|
610
646
|
const handleScrollToBottom = () => {
|
611
|
-
|
612
|
-
thread.scrollToBottom();
|
647
|
+
useViewport.getState().scrollToBottom();
|
613
648
|
};
|
614
649
|
return /* @__PURE__ */ React.createElement(
|
615
650
|
import_react_primitive4.Primitive.button,
|
@@ -685,8 +720,8 @@ var ComposerInput = (0, import_react13.forwardRef)(
|
|
685
720
|
useComposer.getState().cancel();
|
686
721
|
}
|
687
722
|
if (e.key === "Enter" && e.shiftKey === false) {
|
688
|
-
const
|
689
|
-
if (!
|
723
|
+
const isRunning = useThread.getState().isRunning;
|
724
|
+
if (!isRunning) {
|
690
725
|
e.preventDefault();
|
691
726
|
composer.send();
|
692
727
|
}
|
@@ -813,7 +848,7 @@ var useGoToNextBranch = () => {
|
|
813
848
|
const { useComposer, useMessage } = useMessageContext();
|
814
849
|
const disabled = useCombinedStore(
|
815
850
|
[useThread, useComposer, useMessage],
|
816
|
-
(t, c, m) => t.
|
851
|
+
(t, c, m) => t.isRunning || c.isEditing || m.message.branchId + 1 >= m.message.branchCount
|
817
852
|
);
|
818
853
|
if (disabled)
|
819
854
|
return null;
|
@@ -854,7 +889,7 @@ var useGoToPreviousBranch = () => {
|
|
854
889
|
const { useComposer, useMessage } = useMessageContext();
|
855
890
|
const disabled = useCombinedStore(
|
856
891
|
[useThread, useComposer, useMessage],
|
857
|
-
(t, c, m) => t.
|
892
|
+
(t, c, m) => t.isRunning || c.isEditing || m.message.branchId <= 0
|
858
893
|
);
|
859
894
|
if (disabled)
|
860
895
|
return null;
|
@@ -900,13 +935,13 @@ __export(actionBar_exports, {
|
|
900
935
|
// src/primitives/actionBar/ActionBarRoot.tsx
|
901
936
|
var import_react_primitive10 = require("@radix-ui/react-primitive");
|
902
937
|
var import_react20 = require("react");
|
903
|
-
var ActionBarRoot = (0, import_react20.forwardRef)(({
|
938
|
+
var ActionBarRoot = (0, import_react20.forwardRef)(({ hideWhenRunning, autohide, autohideFloat, ...rest }, ref) => {
|
904
939
|
const { useThread } = useAssistantContext();
|
905
940
|
const { useMessage } = useMessageContext();
|
906
941
|
const hideAndfloatStatus = useCombinedStore(
|
907
942
|
[useThread, useMessage],
|
908
943
|
(t, m) => {
|
909
|
-
if (
|
944
|
+
if (hideWhenRunning && t.isRunning)
|
910
945
|
return "hidden" /* Hidden */;
|
911
946
|
const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
|
912
947
|
if (!autohideEnabled)
|
@@ -955,7 +990,7 @@ var useReloadMessage = () => {
|
|
955
990
|
const { useMessage } = useMessageContext();
|
956
991
|
const disabled = useCombinedStore(
|
957
992
|
[useThread, useMessage],
|
958
|
-
(t, m) => t.
|
993
|
+
(t, m) => t.isRunning || m.message.role !== "assistant"
|
959
994
|
);
|
960
995
|
if (disabled)
|
961
996
|
return null;
|
@@ -963,7 +998,7 @@ var useReloadMessage = () => {
|
|
963
998
|
const message = useMessage.getState().message;
|
964
999
|
if (message.role !== "assistant")
|
965
1000
|
throw new Error("Reloading is only supported on assistant messages");
|
966
|
-
useThread.getState().
|
1001
|
+
useThread.getState().startRun(message.parentId);
|
967
1002
|
};
|
968
1003
|
};
|
969
1004
|
|
@@ -993,60 +1028,61 @@ var import_react22 = require("react");
|
|
993
1028
|
|
994
1029
|
// src/adapters/vercel/useDummyAIAssistantContext.tsx
|
995
1030
|
var import_react21 = require("react");
|
996
|
-
var
|
1031
|
+
var import_zustand4 = require("zustand");
|
1032
|
+
|
1033
|
+
// src/utils/context/stores/ViewportStore.tsx
|
1034
|
+
var import_zustand3 = require("zustand");
|
1035
|
+
var makeViewportStore = () => {
|
1036
|
+
const scrollToBottomListeners = /* @__PURE__ */ new Set();
|
1037
|
+
return (0, import_zustand3.create)(() => ({
|
1038
|
+
isAtBottom: true,
|
1039
|
+
scrollToBottom: () => {
|
1040
|
+
for (const listener of scrollToBottomListeners) {
|
1041
|
+
listener();
|
1042
|
+
}
|
1043
|
+
},
|
1044
|
+
onScrollToBottom: (callback) => {
|
1045
|
+
scrollToBottomListeners.add(callback);
|
1046
|
+
return () => {
|
1047
|
+
scrollToBottomListeners.delete(callback);
|
1048
|
+
};
|
1049
|
+
}
|
1050
|
+
}));
|
1051
|
+
};
|
1052
|
+
|
1053
|
+
// src/adapters/vercel/useDummyAIAssistantContext.tsx
|
997
1054
|
var useDummyAIAssistantContext = () => {
|
998
1055
|
const [context] = (0, import_react21.useState)(() => {
|
999
|
-
const
|
1000
|
-
|
1056
|
+
const useThread = (0, import_zustand4.create)()(() => ({
|
1057
|
+
id: "",
|
1001
1058
|
messages: [],
|
1002
|
-
|
1059
|
+
isRunning: false,
|
1003
1060
|
append: async () => {
|
1004
1061
|
throw new Error("Not implemented");
|
1005
1062
|
},
|
1006
|
-
|
1063
|
+
cancelRun: () => {
|
1007
1064
|
throw new Error("Not implemented");
|
1008
1065
|
},
|
1009
1066
|
switchToBranch: () => {
|
1010
1067
|
throw new Error("Not implemented");
|
1011
1068
|
},
|
1012
|
-
|
1069
|
+
startRun: async () => {
|
1013
1070
|
throw new Error("Not implemented");
|
1014
|
-
},
|
1015
|
-
isAtBottom: true,
|
1016
|
-
scrollToBottom: () => {
|
1017
|
-
for (const listener of scrollToBottomListeners) {
|
1018
|
-
listener();
|
1019
|
-
}
|
1020
|
-
},
|
1021
|
-
onScrollToBottom: (callback) => {
|
1022
|
-
scrollToBottomListeners.add(callback);
|
1023
|
-
return () => {
|
1024
|
-
scrollToBottomListeners.delete(callback);
|
1025
|
-
};
|
1026
1071
|
}
|
1027
1072
|
}));
|
1028
|
-
const
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
setValue: (value) => {
|
1033
|
-
useComposer.setState({ value });
|
1034
|
-
},
|
1035
|
-
edit: () => {
|
1036
|
-
throw new Error("Not implemented");
|
1037
|
-
},
|
1038
|
-
send: () => {
|
1039
|
-
useThread.getState().append({
|
1073
|
+
const useViewport = makeViewportStore();
|
1074
|
+
const useComposer = makeThreadComposerStore({
|
1075
|
+
onSend: async (text) => {
|
1076
|
+
await useThread.getState().append({
|
1040
1077
|
parentId: useThread.getState().messages.at(-1)?.id ?? ROOT_PARENT_ID,
|
1041
|
-
content: [{ type: "text", text
|
1078
|
+
content: [{ type: "text", text }]
|
1042
1079
|
});
|
1043
|
-
useComposer.getState().setValue("");
|
1044
1080
|
},
|
1045
|
-
|
1046
|
-
useThread.getState().
|
1081
|
+
onCancel: () => {
|
1082
|
+
useThread.getState().cancelRun();
|
1047
1083
|
}
|
1048
|
-
})
|
1049
|
-
return { useThread, useComposer };
|
1084
|
+
});
|
1085
|
+
return { useThread, useViewport, useComposer };
|
1050
1086
|
});
|
1051
1087
|
return context;
|
1052
1088
|
};
|
@@ -1096,31 +1132,31 @@ var VercelAIAssistantProvider = ({
|
|
1096
1132
|
branches.getBranchState
|
1097
1133
|
);
|
1098
1134
|
}, [vercel.messages, branches.getBranchState]);
|
1099
|
-
const
|
1135
|
+
const cancelRun = (0, import_react22.useCallback)(() => {
|
1100
1136
|
const lastMessage = vercel.messages.at(-1);
|
1101
1137
|
vercel.stop();
|
1102
1138
|
if (lastMessage?.role === "user") {
|
1103
1139
|
vercel.setInput(lastMessage.content);
|
1104
1140
|
}
|
1105
1141
|
}, [vercel.messages, vercel.stop, vercel.setInput]);
|
1106
|
-
const
|
1142
|
+
const isRunning = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
|
1107
1143
|
(0, import_react22.useMemo)(() => {
|
1108
1144
|
context.useThread.setState({
|
1109
1145
|
messages,
|
1110
|
-
|
1111
|
-
|
1146
|
+
isRunning,
|
1147
|
+
cancelRun,
|
1112
1148
|
switchToBranch: branches.switchToBranch,
|
1113
1149
|
append: branches.append,
|
1114
|
-
|
1150
|
+
startRun: branches.startRun
|
1115
1151
|
});
|
1116
|
-
}, [context, messages,
|
1152
|
+
}, [context, messages, isRunning, cancelRun, branches]);
|
1117
1153
|
(0, import_react22.useMemo)(() => {
|
1118
1154
|
context.useComposer.setState({
|
1119
|
-
canCancel:
|
1155
|
+
canCancel: isRunning,
|
1120
1156
|
value: vercel.input,
|
1121
1157
|
setValue: vercel.setInput
|
1122
1158
|
});
|
1123
|
-
}, [context,
|
1159
|
+
}, [context, isRunning, vercel.input, vercel.setInput]);
|
1124
1160
|
return /* @__PURE__ */ React.createElement(AssistantContext.Provider, { value: context }, children);
|
1125
1161
|
};
|
1126
1162
|
|
@@ -1167,7 +1203,7 @@ var VercelRSCAssistantProvider = ({
|
|
1167
1203
|
if (message.content[0]?.type !== "text") {
|
1168
1204
|
throw new Error("Only text content is currently supported");
|
1169
1205
|
}
|
1170
|
-
context.
|
1206
|
+
context.useViewport.getState().scrollToBottom();
|
1171
1207
|
await vercelAppend(message);
|
1172
1208
|
},
|
1173
1209
|
[context, vercelAppend]
|
package/dist/index.mjs
CHANGED
@@ -46,9 +46,9 @@ var useThreadIf = (props) => {
|
|
46
46
|
return false;
|
47
47
|
if (props.empty === false && thread.messages.length === 0)
|
48
48
|
return false;
|
49
|
-
if (props.
|
49
|
+
if (props.running === true && !thread.isRunning)
|
50
50
|
return false;
|
51
|
-
if (props.
|
51
|
+
if (props.running === false && thread.isRunning)
|
52
52
|
return false;
|
53
53
|
return true;
|
54
54
|
});
|
@@ -115,12 +115,12 @@ import { useEffect, useRef as useRef2 } from "react";
|
|
115
115
|
var useOnScrollToBottom = (callback) => {
|
116
116
|
const callbackRef = useRef2(callback);
|
117
117
|
callbackRef.current = callback;
|
118
|
-
const {
|
118
|
+
const { useViewport } = useAssistantContext();
|
119
119
|
useEffect(() => {
|
120
|
-
return
|
120
|
+
return useViewport.getState().onScrollToBottom(() => {
|
121
121
|
callbackRef.current();
|
122
122
|
});
|
123
|
-
}, [
|
123
|
+
}, [useViewport]);
|
124
124
|
};
|
125
125
|
|
126
126
|
// src/primitives/thread/ThreadViewport.tsx
|
@@ -128,7 +128,7 @@ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...re
|
|
128
128
|
const messagesEndRef = useRef3(null);
|
129
129
|
const divRef = useRef3(null);
|
130
130
|
const ref = useComposedRefs(forwardedRef, divRef);
|
131
|
-
const {
|
131
|
+
const { useViewport } = useAssistantContext();
|
132
132
|
const firstRenderRef = useRef3(true);
|
133
133
|
const lastScrollTop = useRef3(0);
|
134
134
|
const scrollToBottom = () => {
|
@@ -137,11 +137,11 @@ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...re
|
|
137
137
|
return;
|
138
138
|
const behavior = firstRenderRef.current ? "instant" : "auto";
|
139
139
|
firstRenderRef.current = false;
|
140
|
-
|
140
|
+
useViewport.setState({ isAtBottom: true });
|
141
141
|
div.scrollIntoView({ behavior });
|
142
142
|
};
|
143
143
|
useOnResizeContent(divRef, () => {
|
144
|
-
if (!
|
144
|
+
if (!useViewport.getState().isAtBottom)
|
145
145
|
return;
|
146
146
|
scrollToBottom();
|
147
147
|
});
|
@@ -152,11 +152,11 @@ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...re
|
|
152
152
|
const div = divRef.current;
|
153
153
|
if (!div)
|
154
154
|
return;
|
155
|
-
const isAtBottom =
|
155
|
+
const isAtBottom = useViewport.getState().isAtBottom;
|
156
156
|
const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight;
|
157
157
|
if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
|
158
158
|
} else if (newIsAtBottom !== isAtBottom) {
|
159
|
-
|
159
|
+
useViewport.setState({ isAtBottom: newIsAtBottom });
|
160
160
|
}
|
161
161
|
lastScrollTop.current = div.scrollTop;
|
162
162
|
};
|
@@ -272,13 +272,13 @@ var useVercelAIBranches = (chat, context) => {
|
|
272
272
|
[data, chat.messages, chat.setMessages]
|
273
273
|
);
|
274
274
|
const reloadMaybe = "reload" in chat ? chat.reload : void 0;
|
275
|
-
const
|
276
|
-
async (
|
275
|
+
const startRun = useCallback(
|
276
|
+
async (parentId) => {
|
277
277
|
if (!reloadMaybe)
|
278
278
|
throw new Error("Reload not supported by Vercel AI SDK's useAssistant");
|
279
|
-
const newMessages = sliceMessagesUntil(chat.messages,
|
279
|
+
const newMessages = sliceMessagesUntil(chat.messages, parentId);
|
280
280
|
chat.setMessages(newMessages);
|
281
|
-
context.
|
281
|
+
context.useViewport.getState().scrollToBottom();
|
282
282
|
await reloadMaybe();
|
283
283
|
},
|
284
284
|
[context, chat.messages, chat.setMessages, reloadMaybe]
|
@@ -289,7 +289,7 @@ var useVercelAIBranches = (chat, context) => {
|
|
289
289
|
chat.setMessages(newMessages);
|
290
290
|
if (message.content.length !== 1 || message.content[0]?.type !== "text")
|
291
291
|
throw new Error("Only text content is currently supported");
|
292
|
-
context.
|
292
|
+
context.useViewport.getState().scrollToBottom();
|
293
293
|
await chat.append({
|
294
294
|
role: "user",
|
295
295
|
content: message.content[0].text
|
@@ -302,13 +302,13 @@ var useVercelAIBranches = (chat, context) => {
|
|
302
302
|
getBranchState,
|
303
303
|
switchToBranch,
|
304
304
|
append,
|
305
|
-
|
305
|
+
startRun
|
306
306
|
}),
|
307
|
-
[getBranchState, switchToBranch, append,
|
307
|
+
[getBranchState, switchToBranch, append, startRun]
|
308
308
|
);
|
309
309
|
};
|
310
310
|
var hasUpcomingMessage = (thread) => {
|
311
|
-
return thread.
|
311
|
+
return thread.isRunning && thread.messages[thread.messages.length - 1]?.role !== "assistant";
|
312
312
|
};
|
313
313
|
|
314
314
|
// src/utils/context/useComposerContext.ts
|
@@ -361,7 +361,56 @@ __export(message_exports, {
|
|
361
361
|
|
362
362
|
// src/primitives/message/MessageProvider.tsx
|
363
363
|
import { useMemo as useMemo2, useState } from "react";
|
364
|
-
import { create } from "zustand";
|
364
|
+
import { create as create2 } from "zustand";
|
365
|
+
|
366
|
+
// src/utils/context/stores/ComposerStore.ts
|
367
|
+
import {
|
368
|
+
create
|
369
|
+
} from "zustand";
|
370
|
+
var makeBaseComposer = (set) => ({
|
371
|
+
value: "",
|
372
|
+
setValue: (value) => {
|
373
|
+
set({ value });
|
374
|
+
}
|
375
|
+
});
|
376
|
+
var makeMessageComposerStore = ({
|
377
|
+
onEdit,
|
378
|
+
onSend
|
379
|
+
}) => create()((set, get, store) => ({
|
380
|
+
...makeBaseComposer(set, get, store),
|
381
|
+
canCancel: true,
|
382
|
+
isEditing: false,
|
383
|
+
edit: () => {
|
384
|
+
const value = onEdit();
|
385
|
+
set({ isEditing: true, value });
|
386
|
+
},
|
387
|
+
send: () => {
|
388
|
+
const value = get().value;
|
389
|
+
set({ isEditing: false });
|
390
|
+
return onSend(value);
|
391
|
+
},
|
392
|
+
cancel: () => {
|
393
|
+
set({ isEditing: false });
|
394
|
+
}
|
395
|
+
}));
|
396
|
+
var makeThreadComposerStore = ({
|
397
|
+
onSend,
|
398
|
+
onCancel
|
399
|
+
}) => create()((set, get, store) => ({
|
400
|
+
...makeBaseComposer(set, get, store),
|
401
|
+
isEditing: true,
|
402
|
+
canCancel: false,
|
403
|
+
send: () => {
|
404
|
+
const value = get().value;
|
405
|
+
set({ value: "", canCancel: true });
|
406
|
+
onSend(value).then(() => {
|
407
|
+
set({ canCancel: false });
|
408
|
+
});
|
409
|
+
},
|
410
|
+
cancel: onCancel
|
411
|
+
}));
|
412
|
+
|
413
|
+
// src/primitives/message/MessageProvider.tsx
|
365
414
|
var getIsLast = (thread, message) => {
|
366
415
|
const hasUpcoming = hasUpcomingMessage(thread);
|
367
416
|
return hasUpcoming ? message.id === UPCOMING_MESSAGE_ID : thread.messages[thread.messages.length - 1]?.id === message.id;
|
@@ -369,7 +418,7 @@ var getIsLast = (thread, message) => {
|
|
369
418
|
var useMessageContext2 = () => {
|
370
419
|
const [context] = useState(() => {
|
371
420
|
const { useThread } = useAssistantContext();
|
372
|
-
const useMessage =
|
421
|
+
const useMessage = create2(() => ({
|
373
422
|
message: null,
|
374
423
|
isLast: false,
|
375
424
|
isCopied: false,
|
@@ -379,34 +428,23 @@ var useMessageContext2 = () => {
|
|
379
428
|
setIsHovering: () => {
|
380
429
|
}
|
381
430
|
}));
|
382
|
-
const useComposer =
|
383
|
-
|
384
|
-
canCancel: true,
|
385
|
-
edit: () => {
|
431
|
+
const useComposer = makeMessageComposerStore({
|
432
|
+
onEdit: () => {
|
386
433
|
const message = useMessage.getState().message;
|
387
434
|
if (message.role !== "user")
|
388
435
|
throw new Error("Editing is only supported for user messages");
|
389
436
|
if (message.content[0]?.type !== "text")
|
390
437
|
throw new Error("Editing is only supported for text-only messages");
|
391
|
-
return
|
392
|
-
isEditing: true,
|
393
|
-
value: message.content[0].text
|
394
|
-
});
|
438
|
+
return message.content[0].text;
|
395
439
|
},
|
396
|
-
|
397
|
-
send: () => {
|
440
|
+
onSend: (text) => {
|
398
441
|
const message = useMessage.getState().message;
|
399
|
-
|
400
|
-
throw new Error("Editing is only supported for user messages");
|
401
|
-
useThread.getState().append({
|
442
|
+
return useThread.getState().append({
|
402
443
|
parentId: message.parentId,
|
403
|
-
content: [{ type: "text", text
|
444
|
+
content: [{ type: "text", text }]
|
404
445
|
});
|
405
|
-
|
406
|
-
|
407
|
-
value: "",
|
408
|
-
setValue: (value) => set({ value })
|
409
|
-
}));
|
446
|
+
}
|
447
|
+
});
|
410
448
|
return { useMessage, useComposer };
|
411
449
|
});
|
412
450
|
return context;
|
@@ -571,11 +609,10 @@ import {
|
|
571
609
|
} from "@radix-ui/react-primitive";
|
572
610
|
import { forwardRef as forwardRef4 } from "react";
|
573
611
|
var ThreadScrollToBottom = forwardRef4(({ onClick, ...rest }, ref) => {
|
574
|
-
const {
|
575
|
-
const isAtBottom =
|
612
|
+
const { useViewport } = useAssistantContext();
|
613
|
+
const isAtBottom = useViewport((s) => s.isAtBottom);
|
576
614
|
const handleScrollToBottom = () => {
|
577
|
-
|
578
|
-
thread.scrollToBottom();
|
615
|
+
useViewport.getState().scrollToBottom();
|
579
616
|
};
|
580
617
|
return /* @__PURE__ */ React.createElement(
|
581
618
|
Primitive4.button,
|
@@ -658,8 +695,8 @@ var ComposerInput = forwardRef6(
|
|
658
695
|
useComposer.getState().cancel();
|
659
696
|
}
|
660
697
|
if (e.key === "Enter" && e.shiftKey === false) {
|
661
|
-
const
|
662
|
-
if (!
|
698
|
+
const isRunning = useThread.getState().isRunning;
|
699
|
+
if (!isRunning) {
|
663
700
|
e.preventDefault();
|
664
701
|
composer.send();
|
665
702
|
}
|
@@ -790,7 +827,7 @@ var useGoToNextBranch = () => {
|
|
790
827
|
const { useComposer, useMessage } = useMessageContext();
|
791
828
|
const disabled = useCombinedStore(
|
792
829
|
[useThread, useComposer, useMessage],
|
793
|
-
(t, c, m) => t.
|
830
|
+
(t, c, m) => t.isRunning || c.isEditing || m.message.branchId + 1 >= m.message.branchCount
|
794
831
|
);
|
795
832
|
if (disabled)
|
796
833
|
return null;
|
@@ -833,7 +870,7 @@ var useGoToPreviousBranch = () => {
|
|
833
870
|
const { useComposer, useMessage } = useMessageContext();
|
834
871
|
const disabled = useCombinedStore(
|
835
872
|
[useThread, useComposer, useMessage],
|
836
|
-
(t, c, m) => t.
|
873
|
+
(t, c, m) => t.isRunning || c.isEditing || m.message.branchId <= 0
|
837
874
|
);
|
838
875
|
if (disabled)
|
839
876
|
return null;
|
@@ -883,13 +920,13 @@ import {
|
|
883
920
|
Primitive as Primitive10
|
884
921
|
} from "@radix-ui/react-primitive";
|
885
922
|
import { forwardRef as forwardRef11 } from "react";
|
886
|
-
var ActionBarRoot = forwardRef11(({
|
923
|
+
var ActionBarRoot = forwardRef11(({ hideWhenRunning, autohide, autohideFloat, ...rest }, ref) => {
|
887
924
|
const { useThread } = useAssistantContext();
|
888
925
|
const { useMessage } = useMessageContext();
|
889
926
|
const hideAndfloatStatus = useCombinedStore(
|
890
927
|
[useThread, useMessage],
|
891
928
|
(t, m) => {
|
892
|
-
if (
|
929
|
+
if (hideWhenRunning && t.isRunning)
|
893
930
|
return "hidden" /* Hidden */;
|
894
931
|
const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
|
895
932
|
if (!autohideEnabled)
|
@@ -938,7 +975,7 @@ var useReloadMessage = () => {
|
|
938
975
|
const { useMessage } = useMessageContext();
|
939
976
|
const disabled = useCombinedStore(
|
940
977
|
[useThread, useMessage],
|
941
|
-
(t, m) => t.
|
978
|
+
(t, m) => t.isRunning || m.message.role !== "assistant"
|
942
979
|
);
|
943
980
|
if (disabled)
|
944
981
|
return null;
|
@@ -946,7 +983,7 @@ var useReloadMessage = () => {
|
|
946
983
|
const message = useMessage.getState().message;
|
947
984
|
if (message.role !== "assistant")
|
948
985
|
throw new Error("Reloading is only supported on assistant messages");
|
949
|
-
useThread.getState().
|
986
|
+
useThread.getState().startRun(message.parentId);
|
950
987
|
};
|
951
988
|
};
|
952
989
|
|
@@ -976,60 +1013,61 @@ import { useCallback as useCallback3, useMemo as useMemo4 } from "react";
|
|
976
1013
|
|
977
1014
|
// src/adapters/vercel/useDummyAIAssistantContext.tsx
|
978
1015
|
import { useState as useState2 } from "react";
|
979
|
-
import { create as
|
1016
|
+
import { create as create4 } from "zustand";
|
1017
|
+
|
1018
|
+
// src/utils/context/stores/ViewportStore.tsx
|
1019
|
+
import { create as create3 } from "zustand";
|
1020
|
+
var makeViewportStore = () => {
|
1021
|
+
const scrollToBottomListeners = /* @__PURE__ */ new Set();
|
1022
|
+
return create3(() => ({
|
1023
|
+
isAtBottom: true,
|
1024
|
+
scrollToBottom: () => {
|
1025
|
+
for (const listener of scrollToBottomListeners) {
|
1026
|
+
listener();
|
1027
|
+
}
|
1028
|
+
},
|
1029
|
+
onScrollToBottom: (callback) => {
|
1030
|
+
scrollToBottomListeners.add(callback);
|
1031
|
+
return () => {
|
1032
|
+
scrollToBottomListeners.delete(callback);
|
1033
|
+
};
|
1034
|
+
}
|
1035
|
+
}));
|
1036
|
+
};
|
1037
|
+
|
1038
|
+
// src/adapters/vercel/useDummyAIAssistantContext.tsx
|
980
1039
|
var useDummyAIAssistantContext = () => {
|
981
1040
|
const [context] = useState2(() => {
|
982
|
-
const
|
983
|
-
|
1041
|
+
const useThread = create4()(() => ({
|
1042
|
+
id: "",
|
984
1043
|
messages: [],
|
985
|
-
|
1044
|
+
isRunning: false,
|
986
1045
|
append: async () => {
|
987
1046
|
throw new Error("Not implemented");
|
988
1047
|
},
|
989
|
-
|
1048
|
+
cancelRun: () => {
|
990
1049
|
throw new Error("Not implemented");
|
991
1050
|
},
|
992
1051
|
switchToBranch: () => {
|
993
1052
|
throw new Error("Not implemented");
|
994
1053
|
},
|
995
|
-
|
1054
|
+
startRun: async () => {
|
996
1055
|
throw new Error("Not implemented");
|
997
|
-
},
|
998
|
-
isAtBottom: true,
|
999
|
-
scrollToBottom: () => {
|
1000
|
-
for (const listener of scrollToBottomListeners) {
|
1001
|
-
listener();
|
1002
|
-
}
|
1003
|
-
},
|
1004
|
-
onScrollToBottom: (callback) => {
|
1005
|
-
scrollToBottomListeners.add(callback);
|
1006
|
-
return () => {
|
1007
|
-
scrollToBottomListeners.delete(callback);
|
1008
|
-
};
|
1009
1056
|
}
|
1010
1057
|
}));
|
1011
|
-
const
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
setValue: (value) => {
|
1016
|
-
useComposer.setState({ value });
|
1017
|
-
},
|
1018
|
-
edit: () => {
|
1019
|
-
throw new Error("Not implemented");
|
1020
|
-
},
|
1021
|
-
send: () => {
|
1022
|
-
useThread.getState().append({
|
1058
|
+
const useViewport = makeViewportStore();
|
1059
|
+
const useComposer = makeThreadComposerStore({
|
1060
|
+
onSend: async (text) => {
|
1061
|
+
await useThread.getState().append({
|
1023
1062
|
parentId: useThread.getState().messages.at(-1)?.id ?? ROOT_PARENT_ID,
|
1024
|
-
content: [{ type: "text", text
|
1063
|
+
content: [{ type: "text", text }]
|
1025
1064
|
});
|
1026
|
-
useComposer.getState().setValue("");
|
1027
1065
|
},
|
1028
|
-
|
1029
|
-
useThread.getState().
|
1066
|
+
onCancel: () => {
|
1067
|
+
useThread.getState().cancelRun();
|
1030
1068
|
}
|
1031
|
-
})
|
1032
|
-
return { useThread, useComposer };
|
1069
|
+
});
|
1070
|
+
return { useThread, useViewport, useComposer };
|
1033
1071
|
});
|
1034
1072
|
return context;
|
1035
1073
|
};
|
@@ -1079,31 +1117,31 @@ var VercelAIAssistantProvider = ({
|
|
1079
1117
|
branches.getBranchState
|
1080
1118
|
);
|
1081
1119
|
}, [vercel.messages, branches.getBranchState]);
|
1082
|
-
const
|
1120
|
+
const cancelRun = useCallback3(() => {
|
1083
1121
|
const lastMessage = vercel.messages.at(-1);
|
1084
1122
|
vercel.stop();
|
1085
1123
|
if (lastMessage?.role === "user") {
|
1086
1124
|
vercel.setInput(lastMessage.content);
|
1087
1125
|
}
|
1088
1126
|
}, [vercel.messages, vercel.stop, vercel.setInput]);
|
1089
|
-
const
|
1127
|
+
const isRunning = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
|
1090
1128
|
useMemo4(() => {
|
1091
1129
|
context.useThread.setState({
|
1092
1130
|
messages,
|
1093
|
-
|
1094
|
-
|
1131
|
+
isRunning,
|
1132
|
+
cancelRun,
|
1095
1133
|
switchToBranch: branches.switchToBranch,
|
1096
1134
|
append: branches.append,
|
1097
|
-
|
1135
|
+
startRun: branches.startRun
|
1098
1136
|
});
|
1099
|
-
}, [context, messages,
|
1137
|
+
}, [context, messages, isRunning, cancelRun, branches]);
|
1100
1138
|
useMemo4(() => {
|
1101
1139
|
context.useComposer.setState({
|
1102
|
-
canCancel:
|
1140
|
+
canCancel: isRunning,
|
1103
1141
|
value: vercel.input,
|
1104
1142
|
setValue: vercel.setInput
|
1105
1143
|
});
|
1106
|
-
}, [context,
|
1144
|
+
}, [context, isRunning, vercel.input, vercel.setInput]);
|
1107
1145
|
return /* @__PURE__ */ React.createElement(AssistantContext.Provider, { value: context }, children);
|
1108
1146
|
};
|
1109
1147
|
|
@@ -1153,7 +1191,7 @@ var VercelRSCAssistantProvider = ({
|
|
1153
1191
|
if (message.content[0]?.type !== "text") {
|
1154
1192
|
throw new Error("Only text content is currently supported");
|
1155
1193
|
}
|
1156
|
-
context.
|
1194
|
+
context.useViewport.getState().scrollToBottom();
|
1157
1195
|
await vercelAppend(message);
|
1158
1196
|
},
|
1159
1197
|
[context, vercelAppend]
|