@assistant-ui/react 0.11.47 → 0.11.49
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/client/types/Thread.d.ts +4 -0
- package/dist/client/types/Thread.d.ts.map +1 -1
- package/dist/context/react/index.d.ts +1 -1
- package/dist/context/react/index.d.ts.map +1 -1
- package/dist/context/react/index.js.map +1 -1
- package/dist/context/stores/ThreadViewport.js +1 -1
- package/dist/context/stores/ThreadViewport.js.map +1 -1
- package/dist/legacy-runtime/client/ThreadRuntimeClient.d.ts.map +1 -1
- package/dist/legacy-runtime/client/ThreadRuntimeClient.js +1 -0
- package/dist/legacy-runtime/client/ThreadRuntimeClient.js.map +1 -1
- package/dist/legacy-runtime/runtime/subscribable/SKIP_UPDATE.js +1 -1
- package/dist/legacy-runtime/runtime/subscribable/SKIP_UPDATE.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +2 -2
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.d.ts +1 -4
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js +34 -12
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/external-store/auto-status.js +1 -1
- package/dist/legacy-runtime/runtime-cores/external-store/auto-status.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/external-store/getExternalStoreMessage.js +2 -2
- package/dist/legacy-runtime/runtime-cores/external-store/getExternalStoreMessage.js.map +1 -1
- package/dist/model-context/registry/ModelContextRegistry.js +3 -3
- package/dist/model-context/registry/ModelContextRegistry.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.d.ts +17 -0
- package/dist/primitives/actionBar/ActionBarExportMarkdown.d.ts.map +1 -0
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js +54 -0
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -0
- package/dist/primitives/actionBar/index.d.ts +1 -0
- package/dist/primitives/actionBar/index.d.ts.map +1 -1
- package/dist/primitives/actionBar/index.js +2 -0
- package/dist/primitives/actionBar/index.js.map +1 -1
- package/dist/primitives/assistant/AssistantIf.d.ts +12 -0
- package/dist/primitives/assistant/AssistantIf.d.ts.map +1 -0
- package/dist/primitives/assistant/AssistantIf.js +16 -0
- package/dist/primitives/assistant/AssistantIf.js.map +1 -0
- package/dist/primitives/composer/ComposerIf.d.ts +3 -0
- package/dist/primitives/composer/ComposerIf.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerIf.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +1 -0
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +2 -0
- package/dist/primitives/index.js.map +1 -1
- package/dist/primitives/message/MessageIf.d.ts +3 -0
- package/dist/primitives/message/MessageIf.d.ts.map +1 -1
- package/dist/primitives/message/MessageIf.js.map +1 -1
- package/dist/primitives/message/MessageParts.js +2 -2
- package/dist/primitives/message/MessageParts.js.map +1 -1
- package/dist/primitives/thread/ThreadIf.d.ts +3 -0
- package/dist/primitives/thread/ThreadIf.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadIf.js +2 -3
- package/dist/primitives/thread/ThreadIf.js.map +1 -1
- package/dist/primitives/thread/ThreadViewport.d.ts +36 -0
- package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +21 -12
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/ThreadViewportSlack.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewportSlack.js +4 -1
- package/dist/primitives/thread/ThreadViewportSlack.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts +20 -2
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +21 -2
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/tests/setup.js +44 -42
- package/dist/tests/setup.js.map +1 -1
- package/package.json +7 -7
- package/src/client/types/Thread.ts +5 -0
- package/src/context/react/index.ts +1 -0
- package/src/legacy-runtime/client/ThreadRuntimeClient.ts +1 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.tsx +1 -1
- package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.ts +40 -17
- package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +70 -0
- package/src/primitives/actionBar/index.ts +1 -0
- package/src/primitives/assistant/AssistantIf.tsx +25 -0
- package/src/primitives/composer/ComposerIf.tsx +3 -0
- package/src/primitives/composer/ComposerInput.tsx +3 -0
- package/src/primitives/index.ts +2 -0
- package/src/primitives/message/MessageIf.tsx +3 -0
- package/src/primitives/message/MessageParts.tsx +2 -2
- package/src/primitives/thread/ThreadIf.tsx +5 -3
- package/src/primitives/thread/ThreadViewport.tsx +49 -18
- package/src/primitives/thread/ThreadViewportSlack.tsx +4 -1
- package/src/primitives/thread/useThreadViewportAutoScroll.tsx +48 -2
package/package.json
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"conversational-ui",
|
|
29
29
|
"conversational-ai"
|
|
30
30
|
],
|
|
31
|
-
"version": "0.11.
|
|
31
|
+
"version": "0.11.49",
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"type": "module",
|
|
34
34
|
"exports": {
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
],
|
|
49
49
|
"sideEffects": false,
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"assistant-cloud": "^0.1.
|
|
52
|
-
"@assistant-ui/tap": "^0.3.
|
|
51
|
+
"assistant-cloud": "^0.1.10",
|
|
52
|
+
"@assistant-ui/tap": "^0.3.2",
|
|
53
53
|
"@radix-ui/primitive": "^1.1.3",
|
|
54
54
|
"@radix-ui/react-compose-refs": "^1.1.2",
|
|
55
55
|
"@radix-ui/react-context": "^1.1.3",
|
|
@@ -59,11 +59,11 @@
|
|
|
59
59
|
"@radix-ui/react-use-callback-ref": "^1.1.1",
|
|
60
60
|
"@radix-ui/react-use-escape-keydown": "^1.1.1",
|
|
61
61
|
"@standard-schema/spec": "^1.0.0",
|
|
62
|
-
"assistant-stream": "^0.2.
|
|
62
|
+
"assistant-stream": "^0.2.44",
|
|
63
63
|
"nanoid": "5.1.6",
|
|
64
64
|
"react-textarea-autosize": "^8.5.9",
|
|
65
65
|
"zod": "^4.1.13",
|
|
66
|
-
"zustand": "^5.0.
|
|
66
|
+
"zustand": "^5.0.9"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"@types/react": "*",
|
|
@@ -84,8 +84,8 @@
|
|
|
84
84
|
"@stryker-mutator/vitest-runner": "^9.4.0",
|
|
85
85
|
"@types/json-schema": "^7.0.15",
|
|
86
86
|
"@types/node": "^24.10.1",
|
|
87
|
-
"tsx": "^4.
|
|
88
|
-
"vitest": "^4.0.
|
|
87
|
+
"tsx": "^4.21.0",
|
|
88
|
+
"vitest": "^4.0.15",
|
|
89
89
|
"@assistant-ui/x-buildutils": "0.0.1"
|
|
90
90
|
},
|
|
91
91
|
"publishConfig": {
|
|
@@ -19,6 +19,11 @@ import { CreateResumeRunConfig } from "../../legacy-runtime/runtime/ThreadRuntim
|
|
|
19
19
|
import { ModelContext } from "../../model-context";
|
|
20
20
|
|
|
21
21
|
export type ThreadClientState = {
|
|
22
|
+
/**
|
|
23
|
+
* Whether the thread is empty. A thread is considered empty when it has no messages and is not loading.
|
|
24
|
+
*/
|
|
25
|
+
readonly isEmpty: boolean;
|
|
26
|
+
|
|
22
27
|
/**
|
|
23
28
|
* Whether the thread is disabled. Disabled threads cannot receive new messages.
|
|
24
29
|
*/
|
|
@@ -97,6 +97,7 @@ export const ThreadClient = resource(
|
|
|
97
97
|
|
|
98
98
|
const state = tapMemo<ThreadClientState>(() => {
|
|
99
99
|
return {
|
|
100
|
+
isEmpty: messages.state.length === 0 && !runtimeState.isLoading,
|
|
100
101
|
isDisabled: runtimeState.isDisabled,
|
|
101
102
|
isLoading: runtimeState.isLoading,
|
|
102
103
|
isRunning: runtimeState.isRunning,
|
|
@@ -39,8 +39,7 @@ type UseToolInvocationsParams = {
|
|
|
39
39
|
|
|
40
40
|
export type ToolExecutionStatus =
|
|
41
41
|
| { type: "executing" }
|
|
42
|
-
| { type: "interrupt"; payload: { type: "human"; payload: unknown } }
|
|
43
|
-
| { type: "cancelled"; reason: string };
|
|
42
|
+
| { type: "interrupt"; payload: { type: "human"; payload: unknown } };
|
|
44
43
|
|
|
45
44
|
export function useToolInvocations({
|
|
46
45
|
state,
|
|
@@ -71,6 +70,9 @@ export function useToolInvocations({
|
|
|
71
70
|
>(new Map());
|
|
72
71
|
|
|
73
72
|
const acRef = useRef<AbortController>(new AbortController());
|
|
73
|
+
const executingCountRef = useRef(0);
|
|
74
|
+
const settledResolversRef = useRef<Array<() => void>>([]);
|
|
75
|
+
|
|
74
76
|
const [controller] = useState(() => {
|
|
75
77
|
const [stream, controller] = createAssistantStreamController();
|
|
76
78
|
const transform = unstable_toolResultStream(
|
|
@@ -96,6 +98,28 @@ export function useToolInvocations({
|
|
|
96
98
|
}));
|
|
97
99
|
});
|
|
98
100
|
},
|
|
101
|
+
{
|
|
102
|
+
onExecutionStart: (toolCallId: string) => {
|
|
103
|
+
executingCountRef.current++;
|
|
104
|
+
setToolStatuses((prev) => ({
|
|
105
|
+
...prev,
|
|
106
|
+
[toolCallId]: { type: "executing" },
|
|
107
|
+
}));
|
|
108
|
+
},
|
|
109
|
+
onExecutionEnd: (toolCallId: string) => {
|
|
110
|
+
executingCountRef.current--;
|
|
111
|
+
setToolStatuses((prev) => {
|
|
112
|
+
const next = { ...prev };
|
|
113
|
+
delete next[toolCallId];
|
|
114
|
+
return next;
|
|
115
|
+
});
|
|
116
|
+
// Resolve any waiting abort promises when all tools have settled
|
|
117
|
+
if (executingCountRef.current === 0) {
|
|
118
|
+
settledResolversRef.current.forEach((resolve) => resolve());
|
|
119
|
+
settledResolversRef.current = [];
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
},
|
|
99
123
|
);
|
|
100
124
|
stream
|
|
101
125
|
.pipeThrough(transform)
|
|
@@ -116,13 +140,6 @@ export function useToolInvocations({
|
|
|
116
140
|
isError: chunk.isError,
|
|
117
141
|
...(chunk.artifact && { artifact: chunk.artifact }),
|
|
118
142
|
});
|
|
119
|
-
|
|
120
|
-
// Clear status when result is set
|
|
121
|
-
setToolStatuses((prev) => {
|
|
122
|
-
const next = { ...prev };
|
|
123
|
-
delete next[chunk.meta.toolCallId];
|
|
124
|
-
return next;
|
|
125
|
-
});
|
|
126
143
|
}
|
|
127
144
|
},
|
|
128
145
|
}),
|
|
@@ -234,20 +251,27 @@ export function useToolInvocations({
|
|
|
234
251
|
}
|
|
235
252
|
}, [state, controller, onResult]);
|
|
236
253
|
|
|
237
|
-
const abort = () => {
|
|
254
|
+
const abort = (): Promise<void> => {
|
|
238
255
|
humanInputRef.current.forEach(({ reject }) => {
|
|
239
256
|
reject(new Error("Tool execution aborted"));
|
|
240
257
|
});
|
|
241
258
|
humanInputRef.current.clear();
|
|
242
|
-
setToolStatuses({});
|
|
243
259
|
|
|
244
260
|
acRef.current.abort();
|
|
245
261
|
acRef.current = new AbortController();
|
|
262
|
+
|
|
263
|
+
// Return a promise that resolves when all executing tools have settled
|
|
264
|
+
if (executingCountRef.current === 0) {
|
|
265
|
+
return Promise.resolve();
|
|
266
|
+
}
|
|
267
|
+
return new Promise<void>((resolve) => {
|
|
268
|
+
settledResolversRef.current.push(resolve);
|
|
269
|
+
});
|
|
246
270
|
};
|
|
247
271
|
|
|
248
272
|
return {
|
|
249
273
|
reset: () => {
|
|
250
|
-
abort();
|
|
274
|
+
void abort();
|
|
251
275
|
isInitialState.current = true;
|
|
252
276
|
},
|
|
253
277
|
abort,
|
|
@@ -255,11 +279,10 @@ export function useToolInvocations({
|
|
|
255
279
|
const handlers = humanInputRef.current.get(toolCallId);
|
|
256
280
|
if (handlers) {
|
|
257
281
|
humanInputRef.current.delete(toolCallId);
|
|
258
|
-
setToolStatuses((prev) => {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
});
|
|
282
|
+
setToolStatuses((prev) => ({
|
|
283
|
+
...prev,
|
|
284
|
+
[toolCallId]: { type: "executing" },
|
|
285
|
+
}));
|
|
263
286
|
handlers.resolve(payload);
|
|
264
287
|
} else {
|
|
265
288
|
throw new Error(
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { forwardRef, useCallback } from "react";
|
|
4
|
+
import { ActionButtonProps } from "../../utils/createActionButton";
|
|
5
|
+
import { composeEventHandlers } from "@radix-ui/primitive";
|
|
6
|
+
import { Primitive } from "@radix-ui/react-primitive";
|
|
7
|
+
import { useAssistantState, useAssistantApi } from "../../context";
|
|
8
|
+
|
|
9
|
+
const useActionBarExportMarkdown = ({
|
|
10
|
+
filename,
|
|
11
|
+
onExport,
|
|
12
|
+
}: {
|
|
13
|
+
filename?: string | undefined;
|
|
14
|
+
onExport?: ((content: string) => void | Promise<void>) | undefined;
|
|
15
|
+
} = {}) => {
|
|
16
|
+
const api = useAssistantApi();
|
|
17
|
+
const hasExportableContent = useAssistantState(({ message }) => {
|
|
18
|
+
return (
|
|
19
|
+
(message.role !== "assistant" || message.status?.type !== "running") &&
|
|
20
|
+
message.parts.some((c) => c.type === "text" && c.text.length > 0)
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const callback = useCallback(async () => {
|
|
25
|
+
const content = api.message().getCopyText();
|
|
26
|
+
if (!content) return;
|
|
27
|
+
|
|
28
|
+
if (onExport) {
|
|
29
|
+
await onExport(content);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const blob = new Blob([content], { type: "text/markdown" });
|
|
34
|
+
const url = URL.createObjectURL(blob);
|
|
35
|
+
const a = document.createElement("a");
|
|
36
|
+
a.href = url;
|
|
37
|
+
a.download = filename ?? `message-${Date.now()}.md`;
|
|
38
|
+
a.click();
|
|
39
|
+
URL.revokeObjectURL(url);
|
|
40
|
+
}, [api, filename, onExport]);
|
|
41
|
+
|
|
42
|
+
if (!hasExportableContent) return null;
|
|
43
|
+
return callback;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export namespace ActionBarPrimitiveExportMarkdown {
|
|
47
|
+
export type Element = HTMLButtonElement;
|
|
48
|
+
export type Props = ActionButtonProps<typeof useActionBarExportMarkdown>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const ActionBarPrimitiveExportMarkdown = forwardRef<
|
|
52
|
+
ActionBarPrimitiveExportMarkdown.Element,
|
|
53
|
+
ActionBarPrimitiveExportMarkdown.Props
|
|
54
|
+
>(({ filename, onExport, onClick, disabled, ...props }, forwardedRef) => {
|
|
55
|
+
const callback = useActionBarExportMarkdown({ filename, onExport });
|
|
56
|
+
return (
|
|
57
|
+
<Primitive.button
|
|
58
|
+
type="button"
|
|
59
|
+
{...props}
|
|
60
|
+
ref={forwardedRef}
|
|
61
|
+
disabled={disabled || !callback}
|
|
62
|
+
onClick={composeEventHandlers(onClick, () => {
|
|
63
|
+
callback?.();
|
|
64
|
+
})}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
ActionBarPrimitiveExportMarkdown.displayName =
|
|
70
|
+
"ActionBarPrimitive.ExportMarkdown";
|
|
@@ -6,3 +6,4 @@ export { ActionBarPrimitiveSpeak as Speak } from "./ActionBarSpeak";
|
|
|
6
6
|
export { ActionBarPrimitiveStopSpeaking as StopSpeaking } from "./ActionBarStopSpeaking";
|
|
7
7
|
export { ActionBarPrimitiveFeedbackPositive as FeedbackPositive } from "./ActionBarFeedbackPositive";
|
|
8
8
|
export { ActionBarPrimitiveFeedbackNegative as FeedbackNegative } from "./ActionBarFeedbackNegative";
|
|
9
|
+
export { ActionBarPrimitiveExportMarkdown as ExportMarkdown } from "./ActionBarExportMarkdown";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { FC, PropsWithChildren } from "react";
|
|
4
|
+
import { useAssistantState } from "../../context";
|
|
5
|
+
import type { AssistantState } from "../../context/react/AssistantApiContext";
|
|
6
|
+
|
|
7
|
+
type UseAssistantIfProps = {
|
|
8
|
+
condition: AssistantIf.Condition;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const useAssistantIf = (props: UseAssistantIfProps) => {
|
|
12
|
+
return useAssistantState(props.condition);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export namespace AssistantIf {
|
|
16
|
+
export type Props = PropsWithChildren<UseAssistantIfProps>;
|
|
17
|
+
export type Condition = (state: AssistantState) => boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const AssistantIf: FC<AssistantIf.Props> = ({ children, condition }) => {
|
|
21
|
+
const result = useAssistantIf({ condition });
|
|
22
|
+
return result ? children : null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
AssistantIf.displayName = "AssistantIf";
|
|
@@ -23,6 +23,9 @@ export namespace ComposerPrimitiveIf {
|
|
|
23
23
|
export type Props = PropsWithChildren<UseComposerIfProps>;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated Use `<AssistantIf condition={({ composer }) => ...} />` instead.
|
|
28
|
+
*/
|
|
26
29
|
export const ComposerPrimitiveIf: FC<ComposerPrimitiveIf.Props> = ({
|
|
27
30
|
children,
|
|
28
31
|
...query
|
|
@@ -114,6 +114,9 @@ export const ComposerPrimitiveInput = forwardRef<
|
|
|
114
114
|
useEscapeKeydown((e) => {
|
|
115
115
|
if (!cancelOnEscape) return;
|
|
116
116
|
|
|
117
|
+
// Only handle ESC if it originated from within this input
|
|
118
|
+
if (!textareaRef.current?.contains(e.target as Node)) return;
|
|
119
|
+
|
|
117
120
|
const composer = api.composer();
|
|
118
121
|
if (composer.getState().canCancel) {
|
|
119
122
|
composer.cancel();
|
package/src/primitives/index.ts
CHANGED
|
@@ -10,6 +10,8 @@ export * as ThreadPrimitive from "./thread";
|
|
|
10
10
|
export * as ThreadListPrimitive from "./threadList";
|
|
11
11
|
export * as ThreadListItemPrimitive from "./threadListItem";
|
|
12
12
|
|
|
13
|
+
export { AssistantIf } from "./assistant/AssistantIf";
|
|
14
|
+
|
|
13
15
|
export { useMessagePartText } from "./messagePart/useMessagePartText";
|
|
14
16
|
export { useMessagePartReasoning } from "./messagePart/useMessagePartReasoning";
|
|
15
17
|
export { useMessagePartSource } from "./messagePart/useMessagePartSource";
|
|
@@ -77,6 +77,9 @@ export namespace MessagePrimitiveIf {
|
|
|
77
77
|
export type Props = PropsWithChildren<UseMessageIfProps>;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* @deprecated Use `<AssistantIf condition={({ message }) => ...} />` instead.
|
|
82
|
+
*/
|
|
80
83
|
export const MessagePrimitiveIf: FC<MessagePrimitiveIf.Props> = ({
|
|
81
84
|
children,
|
|
82
85
|
...query
|
|
@@ -478,7 +478,7 @@ export const MessagePrimitiveParts: FC<MessagePrimitiveParts.Props> = ({
|
|
|
478
478
|
);
|
|
479
479
|
} else if (range.type === "toolGroup") {
|
|
480
480
|
const ToolGroupComponent =
|
|
481
|
-
components
|
|
481
|
+
components?.ToolGroup ?? defaultComponents.ToolGroup;
|
|
482
482
|
return (
|
|
483
483
|
<ToolGroupComponent
|
|
484
484
|
key={`tool-${range.startIndex}`}
|
|
@@ -500,7 +500,7 @@ export const MessagePrimitiveParts: FC<MessagePrimitiveParts.Props> = ({
|
|
|
500
500
|
} else {
|
|
501
501
|
// reasoningGroup
|
|
502
502
|
const ReasoningGroupComponent =
|
|
503
|
-
components
|
|
503
|
+
components?.ReasoningGroup ?? defaultComponents.ReasoningGroup;
|
|
504
504
|
return (
|
|
505
505
|
<ReasoningGroupComponent
|
|
506
506
|
key={`reasoning-${range.startIndex}`}
|
|
@@ -14,9 +14,8 @@ type UseThreadIfProps = RequireAtLeastOne<ThreadIfFilters>;
|
|
|
14
14
|
|
|
15
15
|
const useThreadIf = (props: UseThreadIfProps) => {
|
|
16
16
|
return useAssistantState(({ thread }) => {
|
|
17
|
-
|
|
18
|
-
if (props.empty ===
|
|
19
|
-
if (props.empty === false && isEmpty) return false;
|
|
17
|
+
if (props.empty === true && !thread.isEmpty) return false;
|
|
18
|
+
if (props.empty === false && thread.isEmpty) return false;
|
|
20
19
|
|
|
21
20
|
if (props.running === true && !thread.isRunning) return false;
|
|
22
21
|
if (props.running === false && thread.isRunning) return false;
|
|
@@ -31,6 +30,9 @@ export namespace ThreadPrimitiveIf {
|
|
|
31
30
|
export type Props = PropsWithChildren<UseThreadIfProps>;
|
|
32
31
|
}
|
|
33
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @deprecated Use `<AssistantIf condition={({ thread }) => ...} />` instead.
|
|
35
|
+
*/
|
|
34
36
|
export const ThreadPrimitiveIf: FC<ThreadPrimitiveIf.Props> = ({
|
|
35
37
|
children,
|
|
36
38
|
...query
|
|
@@ -30,36 +30,67 @@ export namespace ThreadPrimitiveViewport {
|
|
|
30
30
|
* - "top": New user messages anchor at the top of the viewport for a focused reading experience.
|
|
31
31
|
*/
|
|
32
32
|
turnAnchor?: "top" | "bottom" | undefined;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether to scroll to bottom when a new run starts.
|
|
36
|
+
*
|
|
37
|
+
* Defaults to true.
|
|
38
|
+
*/
|
|
39
|
+
scrollToBottomOnRunStart?: boolean | undefined;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Whether to scroll to bottom when thread history is first loaded.
|
|
43
|
+
*
|
|
44
|
+
* Defaults to true.
|
|
45
|
+
*/
|
|
46
|
+
scrollToBottomOnInitialize?: boolean | undefined;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whether to scroll to bottom when switching to a different thread.
|
|
50
|
+
*
|
|
51
|
+
* Defaults to true.
|
|
52
|
+
*/
|
|
53
|
+
scrollToBottomOnThreadSwitch?: boolean | undefined;
|
|
33
54
|
};
|
|
34
55
|
}
|
|
35
56
|
|
|
36
57
|
const useViewportSizeRef = () => {
|
|
37
58
|
const register = useThreadViewport((s) => s.registerViewport);
|
|
38
|
-
const getHeight = useCallback(
|
|
39
|
-
(el: HTMLElement) =>
|
|
40
|
-
el.clientHeight - parseFloat(getComputedStyle(el).paddingTop),
|
|
41
|
-
[],
|
|
42
|
-
);
|
|
43
|
-
|
|
59
|
+
const getHeight = useCallback((el: HTMLElement) => el.clientHeight, []);
|
|
44
60
|
return useSizeHandle(register, getHeight);
|
|
45
61
|
};
|
|
46
62
|
|
|
47
63
|
const ThreadPrimitiveViewportScrollable = forwardRef<
|
|
48
64
|
ThreadPrimitiveViewport.Element,
|
|
49
65
|
ThreadPrimitiveViewport.Props
|
|
50
|
-
>(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
>(
|
|
67
|
+
(
|
|
68
|
+
{
|
|
69
|
+
autoScroll,
|
|
70
|
+
scrollToBottomOnRunStart,
|
|
71
|
+
scrollToBottomOnInitialize,
|
|
72
|
+
scrollToBottomOnThreadSwitch,
|
|
73
|
+
children,
|
|
74
|
+
...rest
|
|
75
|
+
},
|
|
76
|
+
forwardedRef,
|
|
77
|
+
) => {
|
|
78
|
+
const autoScrollRef = useThreadViewportAutoScroll<HTMLDivElement>({
|
|
79
|
+
autoScroll,
|
|
80
|
+
scrollToBottomOnRunStart,
|
|
81
|
+
scrollToBottomOnInitialize,
|
|
82
|
+
scrollToBottomOnThreadSwitch,
|
|
83
|
+
});
|
|
84
|
+
const viewportSizeRef = useViewportSizeRef();
|
|
85
|
+
const ref = useComposedRefs(forwardedRef, autoScrollRef, viewportSizeRef);
|
|
56
86
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
87
|
+
return (
|
|
88
|
+
<Primitive.div {...rest} ref={ref}>
|
|
89
|
+
{children}
|
|
90
|
+
</Primitive.div>
|
|
91
|
+
);
|
|
92
|
+
},
|
|
93
|
+
);
|
|
63
94
|
|
|
64
95
|
ThreadPrimitiveViewportScrollable.displayName =
|
|
65
96
|
"ThreadPrimitive.ViewportScrollable";
|
|
@@ -57,7 +57,10 @@ export const ThreadPrimitiveViewportSlack: FC<ThreadViewportSlackProps> = ({
|
|
|
57
57
|
fillClampThreshold = "10em",
|
|
58
58
|
fillClampOffset = "6em",
|
|
59
59
|
}) => {
|
|
60
|
-
const isLast = useAssistantState(
|
|
60
|
+
const isLast = useAssistantState(
|
|
61
|
+
// only add slack if the message is the last message and we already have at least 3 messages
|
|
62
|
+
({ message }) => message.isLast && message.index >= 2,
|
|
63
|
+
);
|
|
61
64
|
const threadViewportStore = useThreadViewportStore({ optional: true });
|
|
62
65
|
const isNested = useContext(SlackNestingContext);
|
|
63
66
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useComposedRefs } from "@radix-ui/react-compose-refs";
|
|
4
|
-
import {
|
|
4
|
+
import { useCallback, useRef, type RefCallback } from "react";
|
|
5
5
|
import { useAssistantEvent } from "../../context";
|
|
6
6
|
import { useOnResizeContent } from "../../utils/hooks/useOnResizeContent";
|
|
7
7
|
import { useOnScrollToBottom } from "../../utils/hooks/useOnScrollToBottom";
|
|
@@ -18,11 +18,35 @@ export namespace useThreadViewportAutoScroll {
|
|
|
18
18
|
* Default false if `turnAnchor` is "top", otherwise defaults to true.
|
|
19
19
|
*/
|
|
20
20
|
autoScroll?: boolean | undefined;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Whether to scroll to bottom when a new run starts.
|
|
24
|
+
*
|
|
25
|
+
* Defaults to true.
|
|
26
|
+
*/
|
|
27
|
+
scrollToBottomOnRunStart?: boolean | undefined;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether to scroll to bottom when thread history is first loaded.
|
|
31
|
+
*
|
|
32
|
+
* Defaults to true.
|
|
33
|
+
*/
|
|
34
|
+
scrollToBottomOnInitialize?: boolean | undefined;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Whether to scroll to bottom when switching to a different thread.
|
|
38
|
+
*
|
|
39
|
+
* Defaults to true.
|
|
40
|
+
*/
|
|
41
|
+
scrollToBottomOnThreadSwitch?: boolean | undefined;
|
|
21
42
|
};
|
|
22
43
|
}
|
|
23
44
|
|
|
24
45
|
export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
25
46
|
autoScroll,
|
|
47
|
+
scrollToBottomOnRunStart = true,
|
|
48
|
+
scrollToBottomOnInitialize = true,
|
|
49
|
+
scrollToBottomOnThreadSwitch = true,
|
|
26
50
|
}: useThreadViewportAutoScroll.Options): RefCallback<TElement> => {
|
|
27
51
|
const divRef = useRef<TElement>(null);
|
|
28
52
|
|
|
@@ -62,7 +86,10 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
|
62
86
|
scrollingToBottomBehaviorRef.current = null;
|
|
63
87
|
}
|
|
64
88
|
|
|
65
|
-
|
|
89
|
+
const shouldUpdate =
|
|
90
|
+
newIsAtBottom || scrollingToBottomBehaviorRef.current === null;
|
|
91
|
+
|
|
92
|
+
if (shouldUpdate && newIsAtBottom !== isAtBottom) {
|
|
66
93
|
writableStore(threadViewportStore).setState({
|
|
67
94
|
isAtBottom: newIsAtBottom,
|
|
68
95
|
});
|
|
@@ -96,12 +123,31 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
|
96
123
|
|
|
97
124
|
// autoscroll on run start
|
|
98
125
|
useAssistantEvent("thread.run-start", () => {
|
|
126
|
+
if (!scrollToBottomOnRunStart) return;
|
|
99
127
|
scrollingToBottomBehaviorRef.current = "auto";
|
|
100
128
|
requestAnimationFrame(() => {
|
|
101
129
|
scrollToBottom("auto");
|
|
102
130
|
});
|
|
103
131
|
});
|
|
104
132
|
|
|
133
|
+
// scroll to bottom instantly when thread history is first loaded
|
|
134
|
+
useAssistantEvent("thread.initialize", () => {
|
|
135
|
+
if (!scrollToBottomOnInitialize) return;
|
|
136
|
+
scrollingToBottomBehaviorRef.current = "instant";
|
|
137
|
+
requestAnimationFrame(() => {
|
|
138
|
+
scrollToBottom("instant");
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// scroll to bottom instantly when switching threads
|
|
143
|
+
useAssistantEvent("thread-list-item.switched-to", () => {
|
|
144
|
+
if (!scrollToBottomOnThreadSwitch) return;
|
|
145
|
+
scrollingToBottomBehaviorRef.current = "instant";
|
|
146
|
+
requestAnimationFrame(() => {
|
|
147
|
+
scrollToBottom("instant");
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
105
151
|
const autoScrollRef = useComposedRefs<TElement>(resizeRef, scrollRef, divRef);
|
|
106
152
|
return autoScrollRef as RefCallback<TElement>;
|
|
107
153
|
};
|