@assistant-ui/react 0.11.36 → 0.11.38
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/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.js +34 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/adapter/cloud.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/adapter/cloud.js +9 -0
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/adapter/cloud.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/adapter/in-memory.d.ts +2 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/adapter/in-memory.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/adapter/in-memory.js +3 -0
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/adapter/in-memory.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/types.d.ts +1 -0
- package/dist/legacy-runtime/runtime-cores/remote-thread-list/types.d.ts.map +1 -1
- package/dist/primitives/assistantModal/scope.d.ts +2 -3
- package/dist/primitives/assistantModal/scope.d.ts.map +1 -1
- package/dist/primitives/assistantModal/scope.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/MessageParts.d.ts +31 -1
- package/dist/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/primitives/message/MessageParts.js +66 -23
- package/dist/primitives/message/MessageParts.js.map +1 -1
- package/dist/primitives/reasoning/index.d.ts +2 -0
- package/dist/primitives/reasoning/index.d.ts.map +1 -0
- package/dist/primitives/reasoning/index.js +6 -0
- package/dist/primitives/reasoning/index.js.map +1 -0
- package/dist/primitives/reasoning/useScrollLock.d.ts +29 -0
- package/dist/primitives/reasoning/useScrollLock.d.ts.map +1 -0
- package/dist/primitives/reasoning/useScrollLock.js +50 -0
- package/dist/primitives/reasoning/useScrollLock.js.map +1 -0
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +11 -12
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/tests/setup.js +287 -125
- package/dist/tests/setup.js.map +1 -1
- package/dist/types/MessagePartComponentTypes.d.ts +6 -1
- package/dist/types/MessagePartComponentTypes.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.tsx +49 -2
- package/src/legacy-runtime/runtime-cores/remote-thread-list/adapter/cloud.tsx +10 -0
- package/src/legacy-runtime/runtime-cores/remote-thread-list/adapter/in-memory.tsx +5 -0
- package/src/legacy-runtime/runtime-cores/remote-thread-list/types.tsx +1 -0
- package/src/primitives/assistantModal/scope.tsx +3 -1
- package/src/primitives/index.ts +1 -0
- package/src/primitives/message/MessageParts.tsx +111 -30
- package/src/primitives/reasoning/index.ts +1 -0
- package/src/primitives/reasoning/useScrollLock.tsx +86 -0
- package/src/primitives/thread/useThreadViewportAutoScroll.tsx +14 -13
- package/src/types/MessagePartComponentTypes.tsx +7 -1
- package/src/types/index.ts +2 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ComponentType } from "react";
|
|
1
|
+
import type { ComponentType, PropsWithChildren } from "react";
|
|
2
2
|
import type { MessagePartStatus, FileMessagePart, ImageMessagePart, ReasoningMessagePart, SourceMessagePart, TextMessagePart, ToolCallMessagePart, Unstable_AudioMessagePart } from "./AssistantTypes";
|
|
3
3
|
import { MessagePartState } from "../legacy-runtime/runtime/MessagePartRuntime";
|
|
4
4
|
import { ToolResponse } from "assistant-stream";
|
|
@@ -10,6 +10,11 @@ export type TextMessagePartProps = MessagePartState & TextMessagePart;
|
|
|
10
10
|
export type TextMessagePartComponent = ComponentType<TextMessagePartProps>;
|
|
11
11
|
export type ReasoningMessagePartProps = MessagePartState & ReasoningMessagePart;
|
|
12
12
|
export type ReasoningMessagePartComponent = ComponentType<ReasoningMessagePartProps>;
|
|
13
|
+
export type ReasoningGroupProps = PropsWithChildren<{
|
|
14
|
+
startIndex: number;
|
|
15
|
+
endIndex: number;
|
|
16
|
+
}>;
|
|
17
|
+
export type ReasoningGroupComponent = ComponentType<ReasoningGroupProps>;
|
|
13
18
|
export type SourceMessagePartProps = MessagePartState & SourceMessagePart;
|
|
14
19
|
export type SourceMessagePartComponent = ComponentType<SourceMessagePartProps>;
|
|
15
20
|
export type ImageMessagePartProps = MessagePartState & ImageMessagePart;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessagePartComponentTypes.d.ts","sourceRoot":"","sources":["../../src/types/MessagePartComponentTypes.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"MessagePartComponentTypes.d.ts","sourceRoot":"","sources":["../../src/types/MessagePartComponentTypes.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAC9D,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,yBAAyB,EAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,iBAAiB,CAAC;CAC3B,CAAC;AACF,MAAM,MAAM,yBAAyB,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;AAE7E,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG,eAAe,CAAC;AACtE,MAAM,MAAM,wBAAwB,GAAG,aAAa,CAAC,oBAAoB,CAAC,CAAC;AAE3E,MAAM,MAAM,yBAAyB,GAAG,gBAAgB,GAAG,oBAAoB,CAAC;AAChF,MAAM,MAAM,6BAA6B,GACvC,aAAa,CAAC,yBAAyB,CAAC,CAAC;AAE3C,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,aAAa,CAAC,mBAAmB,CAAC,CAAC;AAEzE,MAAM,MAAM,sBAAsB,GAAG,gBAAgB,GAAG,iBAAiB,CAAC;AAC1E,MAAM,MAAM,0BAA0B,GAAG,aAAa,CAAC,sBAAsB,CAAC,CAAC;AAE/E,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AACxE,MAAM,MAAM,yBAAyB,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;AAE7E,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG,eAAe,CAAC;AACtE,MAAM,MAAM,wBAAwB,GAAG,aAAa,CAAC,oBAAoB,CAAC,CAAC;AAE3E,MAAM,MAAM,8BAA8B,GAAG,gBAAgB,GAC3D,yBAAyB,CAAC;AAC5B,MAAM,MAAM,kCAAkC,GAC5C,aAAa,CAAC,8BAA8B,CAAC,CAAC;AAEhD,MAAM,MAAM,wBAAwB,CAClC,KAAK,GAAG,GAAG,EACX,OAAO,GAAG,OAAO,IACf,gBAAgB,GAClB,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG;IACpC,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC,CAAC;AAEJ,MAAM,MAAM,4BAA4B,CACtC,KAAK,GAAG,GAAG,EACX,OAAO,GAAG,GAAG,IACX,aAAa,CAAC,wBAAwB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { Attachment, PendingAttachment, CompleteAttachment, AttachmentStatus, } from "./AttachmentTypes";
|
|
2
2
|
export type { AppendMessage, TextMessagePart, ReasoningMessagePart, SourceMessagePart, ImageMessagePart, FileMessagePart, Unstable_AudioMessagePart, ToolCallMessagePart, MessageStatus, MessagePartStatus, ToolCallMessagePartStatus, ThreadUserMessagePart, ThreadAssistantMessagePart, ThreadSystemMessage, ThreadAssistantMessage, ThreadUserMessage, ThreadMessage, } from "./AssistantTypes";
|
|
3
|
-
export type { EmptyMessagePartComponent, EmptyMessagePartProps, TextMessagePartComponent, TextMessagePartProps, ReasoningMessagePartComponent, ReasoningMessagePartProps, SourceMessagePartComponent, SourceMessagePartProps, ImageMessagePartComponent, ImageMessagePartProps, FileMessagePartComponent, FileMessagePartProps, Unstable_AudioMessagePartComponent, Unstable_AudioMessagePartProps, ToolCallMessagePartComponent, ToolCallMessagePartProps, } from "./MessagePartComponentTypes";
|
|
3
|
+
export type { EmptyMessagePartComponent, EmptyMessagePartProps, TextMessagePartComponent, TextMessagePartProps, ReasoningMessagePartComponent, ReasoningMessagePartProps, SourceMessagePartComponent, SourceMessagePartProps, ImageMessagePartComponent, ImageMessagePartProps, FileMessagePartComponent, FileMessagePartProps, Unstable_AudioMessagePartComponent, Unstable_AudioMessagePartProps, ToolCallMessagePartComponent, ToolCallMessagePartProps, ReasoningGroupProps, ReasoningGroupComponent, } from "./MessagePartComponentTypes";
|
|
4
4
|
export type { ThreadListItemStatus } from "../legacy-runtime/runtime/ThreadListItemRuntime";
|
|
5
5
|
export type { Unsubscribe } from "./Unsubscribe";
|
|
6
6
|
export type { AssistantEventScope, AssistantEventSelector, AssistantEvent, AssistantEventMap, AssistantEventCallback, } from "./EventTypes";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,aAAa,EACb,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,yBAAyB,EACzB,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,yBAAyB,EAGzB,qBAAqB,EACrB,0BAA0B,EAC1B,mBAAmB,EACnB,sBAAsB,EACtB,iBAAiB,EACjB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,yBAAyB,EACzB,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EACpB,6BAA6B,EAC7B,yBAAyB,EACzB,0BAA0B,EAC1B,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EACpB,kCAAkC,EAClC,8BAA8B,EAC9B,4BAA4B,EAC5B,wBAAwB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,aAAa,EACb,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,yBAAyB,EACzB,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,yBAAyB,EAGzB,qBAAqB,EACrB,0BAA0B,EAC1B,mBAAmB,EACnB,sBAAsB,EACtB,iBAAiB,EACjB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,yBAAyB,EACzB,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EACpB,6BAA6B,EAC7B,yBAAyB,EACzB,0BAA0B,EAC1B,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EACpB,kCAAkC,EAClC,8BAA8B,EAC9B,4BAA4B,EAC5B,wBAAwB,EACxB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,6BAA6B,CAAC;AAGrC,YAAY,EAAE,oBAAoB,EAAE,MAAM,iDAAiD,CAAC;AAE5F,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,YAAY,EACV,mBAAmB,EACnB,sBAAsB,EACtB,cAAc,EACd,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,cAAc,CAAC"}
|
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.38",
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"type": "module",
|
|
34
34
|
"exports": {
|
|
@@ -48,14 +48,14 @@
|
|
|
48
48
|
],
|
|
49
49
|
"sideEffects": false,
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"assistant-cloud": "^0.1.
|
|
51
|
+
"assistant-cloud": "^0.1.7",
|
|
52
52
|
"@assistant-ui/tap": "^0.1.5",
|
|
53
53
|
"@radix-ui/primitive": "^1.1.3",
|
|
54
54
|
"@radix-ui/react-compose-refs": "^1.1.2",
|
|
55
|
-
"@radix-ui/react-context": "^1.1.
|
|
55
|
+
"@radix-ui/react-context": "^1.1.3",
|
|
56
56
|
"@radix-ui/react-popover": "^1.1.15",
|
|
57
|
-
"@radix-ui/react-primitive": "^2.1.
|
|
58
|
-
"@radix-ui/react-slot": "^1.2.
|
|
57
|
+
"@radix-ui/react-primitive": "^2.1.4",
|
|
58
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
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",
|
|
@@ -84,11 +84,11 @@
|
|
|
84
84
|
"@stryker-mutator/core": "^9.3.0",
|
|
85
85
|
"@stryker-mutator/vitest-runner": "^9.3.0",
|
|
86
86
|
"@types/json-schema": "^7.0.15",
|
|
87
|
-
"@types/node": "^24.10.
|
|
87
|
+
"@types/node": "^24.10.1",
|
|
88
88
|
"eslint": "^9",
|
|
89
|
-
"eslint-config-next": "16.0.
|
|
89
|
+
"eslint-config-next": "16.0.3",
|
|
90
90
|
"tsx": "^4.20.6",
|
|
91
|
-
"vitest": "^4.0.
|
|
91
|
+
"vitest": "^4.0.8",
|
|
92
92
|
"@assistant-ui/x-buildutils": "0.0.1"
|
|
93
93
|
},
|
|
94
94
|
"publishConfig": {
|
|
@@ -308,9 +308,56 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
public async switchToThread(threadIdOrRemoteId: string): Promise<void> {
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
let data = this.getItemById(threadIdOrRemoteId);
|
|
312
|
+
|
|
313
|
+
if (!data) {
|
|
314
|
+
const remoteMetadata =
|
|
315
|
+
await this._options.adapter.fetch(threadIdOrRemoteId);
|
|
316
|
+
const state = this._state.value;
|
|
317
|
+
const mappingId = createThreadMappingId(remoteMetadata.remoteId);
|
|
318
|
+
|
|
319
|
+
const newThreadData = {
|
|
320
|
+
...state.threadData,
|
|
321
|
+
[mappingId]: {
|
|
322
|
+
id: mappingId,
|
|
323
|
+
initializeTask: Promise.resolve({
|
|
324
|
+
remoteId: remoteMetadata.remoteId,
|
|
325
|
+
externalId: remoteMetadata.externalId,
|
|
326
|
+
}),
|
|
327
|
+
remoteId: remoteMetadata.remoteId,
|
|
328
|
+
externalId: remoteMetadata.externalId,
|
|
329
|
+
status: remoteMetadata.status,
|
|
330
|
+
title: remoteMetadata.title,
|
|
331
|
+
} as RemoteThreadData,
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const newThreadIdMap = {
|
|
335
|
+
...state.threadIdMap,
|
|
336
|
+
[remoteMetadata.remoteId]: mappingId,
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const newThreadIds =
|
|
340
|
+
remoteMetadata.status === "regular"
|
|
341
|
+
? [...state.threadIds, remoteMetadata.remoteId]
|
|
342
|
+
: state.threadIds;
|
|
343
|
+
|
|
344
|
+
const newArchivedThreadIds =
|
|
345
|
+
remoteMetadata.status === "archived"
|
|
346
|
+
? [...state.archivedThreadIds, remoteMetadata.remoteId]
|
|
347
|
+
: state.archivedThreadIds;
|
|
313
348
|
|
|
349
|
+
this._state.update({
|
|
350
|
+
...state,
|
|
351
|
+
threadIds: newThreadIds,
|
|
352
|
+
archivedThreadIds: newArchivedThreadIds,
|
|
353
|
+
threadIdMap: newThreadIdMap,
|
|
354
|
+
threadData: newThreadData,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
data = this.getItemById(threadIdOrRemoteId);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (!data) throw new Error("Thread not found");
|
|
314
361
|
if (this._mainThreadId === data.id) return;
|
|
315
362
|
|
|
316
363
|
const task = this._hookManager.startThreadRuntime(data.id);
|
|
@@ -121,6 +121,16 @@ export const useCloudThreadListAdapter = (
|
|
|
121
121
|
});
|
|
122
122
|
},
|
|
123
123
|
|
|
124
|
+
fetch: async (threadId: string) => {
|
|
125
|
+
const thread = await cloud.threads.get(threadId);
|
|
126
|
+
return {
|
|
127
|
+
status: thread.is_archived ? "archived" : "regular",
|
|
128
|
+
remoteId: thread.id,
|
|
129
|
+
title: thread.title,
|
|
130
|
+
externalId: thread.external_id ?? undefined,
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
|
|
124
134
|
unstable_Provider,
|
|
125
135
|
};
|
|
126
136
|
};
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
RemoteThreadInitializeResponse,
|
|
4
4
|
RemoteThreadListAdapter,
|
|
5
5
|
RemoteThreadListResponse,
|
|
6
|
+
RemoteThreadMetadata,
|
|
6
7
|
} from "../types";
|
|
7
8
|
|
|
8
9
|
export class InMemoryThreadListAdapter implements RemoteThreadListAdapter {
|
|
@@ -35,4 +36,8 @@ export class InMemoryThreadListAdapter implements RemoteThreadListAdapter {
|
|
|
35
36
|
generateTitle(): Promise<AssistantStream> {
|
|
36
37
|
return Promise.resolve(new ReadableStream<AssistantStreamChunk>());
|
|
37
38
|
}
|
|
39
|
+
|
|
40
|
+
fetch(_threadId: string): Promise<RemoteThreadMetadata> {
|
|
41
|
+
return Promise.reject(new Error("Thread not found"));
|
|
42
|
+
}
|
|
38
43
|
}
|
|
@@ -31,6 +31,7 @@ export type RemoteThreadListAdapter = {
|
|
|
31
31
|
remoteId: string,
|
|
32
32
|
unstable_messages: readonly ThreadMessage[],
|
|
33
33
|
): Promise<AssistantStream>;
|
|
34
|
+
fetch(threadId: string): Promise<RemoteThreadMetadata>;
|
|
34
35
|
|
|
35
36
|
unstable_Provider?: ComponentType<PropsWithChildren>;
|
|
36
37
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
2
2
|
import type { Scope } from "@radix-ui/react-context";
|
|
3
3
|
|
|
4
|
-
export const usePopoverScope
|
|
4
|
+
export const usePopoverScope: ReturnType<
|
|
5
|
+
typeof PopoverPrimitive.createPopoverScope
|
|
6
|
+
> = PopoverPrimitive.createPopoverScope();
|
|
5
7
|
export type ScopedProps<P> = P & { __scopeAssistantModal?: Scope };
|
package/src/primitives/index.ts
CHANGED
|
@@ -16,3 +16,4 @@ export { useMessagePartSource } from "./messagePart/useMessagePartSource";
|
|
|
16
16
|
export { useMessagePartFile } from "./messagePart/useMessagePartFile";
|
|
17
17
|
export { useMessagePartImage } from "./messagePart/useMessagePartImage";
|
|
18
18
|
export { useThreadViewportAutoScroll } from "./thread/useThreadViewportAutoScroll";
|
|
19
|
+
export { useScrollLock } from "./reasoning";
|
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
ToolCallMessagePartProps,
|
|
26
26
|
FileMessagePartComponent,
|
|
27
27
|
ReasoningMessagePartComponent,
|
|
28
|
+
ReasoningGroupComponent,
|
|
28
29
|
} from "../../types/MessagePartComponentTypes";
|
|
29
30
|
import { MessagePartPrimitiveInProgress } from "../messagePart/MessagePartInProgress";
|
|
30
31
|
import { MessagePartStatus } from "../../types/AssistantTypes";
|
|
@@ -32,50 +33,75 @@ import { useShallow } from "zustand/shallow";
|
|
|
32
33
|
|
|
33
34
|
type MessagePartRange =
|
|
34
35
|
| { type: "single"; index: number }
|
|
35
|
-
| { type: "toolGroup"; startIndex: number; endIndex: number }
|
|
36
|
+
| { type: "toolGroup"; startIndex: number; endIndex: number }
|
|
37
|
+
| { type: "reasoningGroup"; startIndex: number; endIndex: number };
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
+
* Creates a group state manager for a specific part type.
|
|
41
|
+
* Returns functions to start, end, and finalize groups.
|
|
42
|
+
*/
|
|
43
|
+
const createGroupState = <T extends "toolGroup" | "reasoningGroup">(
|
|
44
|
+
groupType: T,
|
|
45
|
+
) => {
|
|
46
|
+
let start = -1;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
startGroup: (index: number) => {
|
|
50
|
+
if (start === -1) {
|
|
51
|
+
start = index;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
endGroup: (endIndex: number, ranges: MessagePartRange[]) => {
|
|
55
|
+
if (start !== -1) {
|
|
56
|
+
ranges.push({
|
|
57
|
+
type: groupType,
|
|
58
|
+
startIndex: start,
|
|
59
|
+
endIndex,
|
|
60
|
+
} as MessagePartRange);
|
|
61
|
+
start = -1;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
finalize: (endIndex: number, ranges: MessagePartRange[]) => {
|
|
65
|
+
if (start !== -1) {
|
|
66
|
+
ranges.push({
|
|
67
|
+
type: groupType,
|
|
68
|
+
startIndex: start,
|
|
69
|
+
endIndex,
|
|
70
|
+
} as MessagePartRange);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Groups consecutive tool-call and reasoning message parts into ranges.
|
|
78
|
+
* Always groups tool calls and reasoning parts, even if there's only one.
|
|
40
79
|
*/
|
|
41
80
|
const groupMessageParts = (
|
|
42
81
|
messageTypes: readonly string[],
|
|
43
82
|
): MessagePartRange[] => {
|
|
44
83
|
const ranges: MessagePartRange[] = [];
|
|
45
|
-
|
|
84
|
+
const toolGroup = createGroupState("toolGroup");
|
|
85
|
+
const reasoningGroup = createGroupState("reasoningGroup");
|
|
46
86
|
|
|
47
87
|
for (let i = 0; i < messageTypes.length; i++) {
|
|
48
88
|
const type = messageTypes[i];
|
|
49
89
|
|
|
50
90
|
if (type === "tool-call") {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
91
|
+
reasoningGroup.endGroup(i - 1, ranges);
|
|
92
|
+
toolGroup.startGroup(i);
|
|
93
|
+
} else if (type === "reasoning") {
|
|
94
|
+
toolGroup.endGroup(i - 1, ranges);
|
|
95
|
+
reasoningGroup.startGroup(i);
|
|
55
96
|
} else {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
ranges.push({
|
|
59
|
-
type: "toolGroup",
|
|
60
|
-
startIndex: currentToolGroupStart,
|
|
61
|
-
endIndex: i - 1,
|
|
62
|
-
});
|
|
63
|
-
currentToolGroupStart = -1;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Add non-tool-call part individually
|
|
97
|
+
toolGroup.endGroup(i - 1, ranges);
|
|
98
|
+
reasoningGroup.endGroup(i - 1, ranges);
|
|
67
99
|
ranges.push({ type: "single", index: i });
|
|
68
100
|
}
|
|
69
101
|
}
|
|
70
102
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
ranges.push({
|
|
74
|
-
type: "toolGroup",
|
|
75
|
-
startIndex: currentToolGroupStart,
|
|
76
|
-
endIndex: messageTypes.length - 1,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
103
|
+
toolGroup.finalize(messageTypes.length - 1, ranges);
|
|
104
|
+
reasoningGroup.finalize(messageTypes.length - 1, ranges);
|
|
79
105
|
|
|
80
106
|
return ranges;
|
|
81
107
|
};
|
|
@@ -184,6 +210,37 @@ export namespace MessagePrimitiveParts {
|
|
|
184
210
|
ToolGroup?: ComponentType<
|
|
185
211
|
PropsWithChildren<{ startIndex: number; endIndex: number }>
|
|
186
212
|
>;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Component for rendering grouped reasoning parts.
|
|
216
|
+
*
|
|
217
|
+
* When provided, this component will automatically wrap reasoning message parts
|
|
218
|
+
* (one or more consecutive) in a group container. Each reasoning part inside
|
|
219
|
+
* renders its own text independently - no text merging occurs.
|
|
220
|
+
*
|
|
221
|
+
* The component receives:
|
|
222
|
+
* - `startIndex`: The index of the first reasoning part in the group
|
|
223
|
+
* - `endIndex`: The index of the last reasoning part in the group
|
|
224
|
+
* - `children`: The rendered Reasoning components (one per part)
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```tsx
|
|
228
|
+
* // Collapsible reasoning group
|
|
229
|
+
* ReasoningGroup: ({ children }) => (
|
|
230
|
+
* <details className="reasoning-group">
|
|
231
|
+
* <summary>Reasoning</summary>
|
|
232
|
+
* <div className="reasoning-content">
|
|
233
|
+
* {children}
|
|
234
|
+
* </div>
|
|
235
|
+
* </details>
|
|
236
|
+
* )
|
|
237
|
+
* ```
|
|
238
|
+
*
|
|
239
|
+
* @param startIndex - Index of the first reasoning part in the group
|
|
240
|
+
* @param endIndex - Index of the last reasoning part in the group
|
|
241
|
+
* @param children - Rendered reasoning part components
|
|
242
|
+
*/
|
|
243
|
+
ReasoningGroup?: ReasoningGroupComponent;
|
|
187
244
|
}
|
|
188
245
|
| undefined;
|
|
189
246
|
};
|
|
@@ -220,6 +277,7 @@ const defaultComponents = {
|
|
|
220
277
|
File: () => null,
|
|
221
278
|
Unstable_Audio: () => null,
|
|
222
279
|
ToolGroup: ({ children }) => children,
|
|
280
|
+
ReasoningGroup: ({ children }) => children,
|
|
223
281
|
} satisfies MessagePrimitiveParts.Props["components"];
|
|
224
282
|
|
|
225
283
|
type MessagePartComponentProps = {
|
|
@@ -328,7 +386,8 @@ export const MessagePrimitivePartByIndex: FC<MessagePrimitivePartByIndex.Props>
|
|
|
328
386
|
prev.components?.File === next.components?.File &&
|
|
329
387
|
prev.components?.Unstable_Audio === next.components?.Unstable_Audio &&
|
|
330
388
|
prev.components?.tools === next.components?.tools &&
|
|
331
|
-
prev.components?.ToolGroup === next.components?.ToolGroup
|
|
389
|
+
prev.components?.ToolGroup === next.components?.ToolGroup &&
|
|
390
|
+
prev.components?.ReasoningGroup === next.components?.ReasoningGroup,
|
|
332
391
|
);
|
|
333
392
|
|
|
334
393
|
MessagePrimitivePartByIndex.displayName = "MessagePrimitive.PartByIndex";
|
|
@@ -416,12 +475,12 @@ export const MessagePrimitiveParts: FC<MessagePrimitiveParts.Props> = ({
|
|
|
416
475
|
components={components}
|
|
417
476
|
/>
|
|
418
477
|
);
|
|
419
|
-
} else {
|
|
478
|
+
} else if (range.type === "toolGroup") {
|
|
420
479
|
const ToolGroupComponent =
|
|
421
480
|
components!.ToolGroup ?? defaultComponents.ToolGroup;
|
|
422
481
|
return (
|
|
423
482
|
<ToolGroupComponent
|
|
424
|
-
key={range.startIndex}
|
|
483
|
+
key={`tool-${range.startIndex}`}
|
|
425
484
|
startIndex={range.startIndex}
|
|
426
485
|
endIndex={range.endIndex}
|
|
427
486
|
>
|
|
@@ -437,6 +496,28 @@ export const MessagePrimitiveParts: FC<MessagePrimitiveParts.Props> = ({
|
|
|
437
496
|
)}
|
|
438
497
|
</ToolGroupComponent>
|
|
439
498
|
);
|
|
499
|
+
} else {
|
|
500
|
+
// reasoningGroup
|
|
501
|
+
const ReasoningGroupComponent =
|
|
502
|
+
components!.ReasoningGroup ?? defaultComponents.ReasoningGroup;
|
|
503
|
+
return (
|
|
504
|
+
<ReasoningGroupComponent
|
|
505
|
+
key={`reasoning-${range.startIndex}`}
|
|
506
|
+
startIndex={range.startIndex}
|
|
507
|
+
endIndex={range.endIndex}
|
|
508
|
+
>
|
|
509
|
+
{Array.from(
|
|
510
|
+
{ length: range.endIndex - range.startIndex + 1 },
|
|
511
|
+
(_, i) => (
|
|
512
|
+
<MessagePrimitivePartByIndex
|
|
513
|
+
key={i}
|
|
514
|
+
index={range.startIndex + i}
|
|
515
|
+
components={components}
|
|
516
|
+
/>
|
|
517
|
+
),
|
|
518
|
+
)}
|
|
519
|
+
</ReasoningGroupComponent>
|
|
520
|
+
);
|
|
440
521
|
}
|
|
441
522
|
});
|
|
442
523
|
}, [messageRanges, components, contentLength]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useScrollLock } from "./useScrollLock";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { type RefObject, useCallback, useEffect, useRef } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Locks scroll position during collapsible/height animations and hides scrollbar.
|
|
7
|
+
*
|
|
8
|
+
* This utility prevents page jumps when content height changes during animations,
|
|
9
|
+
* providing a smooth user experience. It finds the nearest scrollable ancestor and
|
|
10
|
+
* temporarily locks its scroll position while the animation completes.
|
|
11
|
+
*
|
|
12
|
+
* - Prevents forced reflows: no layout reads, mutations scoped to scrollable parent only
|
|
13
|
+
* - Reactive: only intercepts scroll events when browser actually adjusts
|
|
14
|
+
* - Cleans up automatically after animation duration
|
|
15
|
+
*
|
|
16
|
+
* @param animatedElementRef - Ref to the animated element
|
|
17
|
+
* @param animationDuration - Lock duration in milliseconds
|
|
18
|
+
* @returns Function to activate the scroll lock
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* const collapsibleRef = useRef<HTMLDivElement>(null);
|
|
23
|
+
* const lockScroll = useScrollLock(collapsibleRef, 200);
|
|
24
|
+
*
|
|
25
|
+
* const handleCollapse = () => {
|
|
26
|
+
* lockScroll(); // Lock scroll before collapsing
|
|
27
|
+
* setIsOpen(false);
|
|
28
|
+
* };
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export const useScrollLock = <T extends HTMLElement = HTMLElement>(
|
|
32
|
+
animatedElementRef: RefObject<T | null>,
|
|
33
|
+
animationDuration: number,
|
|
34
|
+
) => {
|
|
35
|
+
const scrollContainerRef = useRef<HTMLElement | null>(null);
|
|
36
|
+
const cleanupRef = useRef<(() => void) | null>(null);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
return () => {
|
|
40
|
+
cleanupRef.current?.();
|
|
41
|
+
};
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
const lockScroll = useCallback(() => {
|
|
45
|
+
cleanupRef.current?.();
|
|
46
|
+
|
|
47
|
+
(function findScrollableAncestor() {
|
|
48
|
+
if (scrollContainerRef.current || !animatedElementRef.current) return;
|
|
49
|
+
|
|
50
|
+
let el: HTMLElement | null = animatedElementRef.current;
|
|
51
|
+
while (el) {
|
|
52
|
+
const { overflowY } = getComputedStyle(el);
|
|
53
|
+
if (overflowY === "scroll" || overflowY === "auto") {
|
|
54
|
+
scrollContainerRef.current = el;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
el = el.parentElement;
|
|
58
|
+
}
|
|
59
|
+
})();
|
|
60
|
+
|
|
61
|
+
const scrollContainer = scrollContainerRef.current;
|
|
62
|
+
if (!scrollContainer) return;
|
|
63
|
+
|
|
64
|
+
const scrollPosition = scrollContainer.scrollTop;
|
|
65
|
+
const scrollbarWidth = scrollContainer.style.scrollbarWidth;
|
|
66
|
+
|
|
67
|
+
scrollContainer.style.scrollbarWidth = "none";
|
|
68
|
+
|
|
69
|
+
const resetPosition = () => (scrollContainer.scrollTop = scrollPosition);
|
|
70
|
+
scrollContainer.addEventListener("scroll", resetPosition);
|
|
71
|
+
|
|
72
|
+
const timeoutId = setTimeout(() => {
|
|
73
|
+
scrollContainer.removeEventListener("scroll", resetPosition);
|
|
74
|
+
scrollContainer.style.scrollbarWidth = scrollbarWidth;
|
|
75
|
+
cleanupRef.current = null;
|
|
76
|
+
}, animationDuration);
|
|
77
|
+
|
|
78
|
+
cleanupRef.current = () => {
|
|
79
|
+
clearTimeout(timeoutId);
|
|
80
|
+
scrollContainer.removeEventListener("scroll", resetPosition);
|
|
81
|
+
scrollContainer.style.scrollbarWidth = scrollbarWidth;
|
|
82
|
+
};
|
|
83
|
+
}, [animationDuration, animatedElementRef]);
|
|
84
|
+
|
|
85
|
+
return lockScroll;
|
|
86
|
+
};
|
|
@@ -28,16 +28,13 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
|
28
28
|
// fix: delay the state change until the scroll is done
|
|
29
29
|
const isScrollingToBottomRef = useRef(false);
|
|
30
30
|
|
|
31
|
-
const scrollToBottom = useCallback(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!div || !autoScroll) return;
|
|
31
|
+
const scrollToBottom = useCallback((behavior: ScrollBehavior) => {
|
|
32
|
+
const div = divRef.current;
|
|
33
|
+
if (!div) return;
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
[autoScroll],
|
|
40
|
-
);
|
|
35
|
+
isScrollingToBottomRef.current = true;
|
|
36
|
+
div.scrollTo({ top: div.scrollHeight, behavior });
|
|
37
|
+
}, []);
|
|
41
38
|
|
|
42
39
|
const handleScroll = () => {
|
|
43
40
|
const div = divRef.current;
|
|
@@ -45,7 +42,8 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
|
45
42
|
|
|
46
43
|
const isAtBottom = threadViewportStore.getState().isAtBottom;
|
|
47
44
|
const newIsAtBottom =
|
|
48
|
-
div.scrollHeight - div.scrollTop
|
|
45
|
+
Math.abs(div.scrollHeight - div.scrollTop - div.clientHeight) < 1 ||
|
|
46
|
+
div.scrollHeight <= div.clientHeight;
|
|
49
47
|
|
|
50
48
|
if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
|
|
51
49
|
// ignore scroll down
|
|
@@ -66,8 +64,9 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
|
66
64
|
|
|
67
65
|
const resizeRef = useOnResizeContent(() => {
|
|
68
66
|
if (
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
autoScroll &&
|
|
68
|
+
(isScrollingToBottomRef.current ||
|
|
69
|
+
threadViewportStore.getState().isAtBottom)
|
|
71
70
|
) {
|
|
72
71
|
scrollToBottom("instant");
|
|
73
72
|
}
|
|
@@ -87,7 +86,9 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
|
87
86
|
});
|
|
88
87
|
|
|
89
88
|
// autoscroll on run start
|
|
90
|
-
useAssistantEvent("thread.run-start", () =>
|
|
89
|
+
useAssistantEvent("thread.run-start", () => {
|
|
90
|
+
if (autoScroll) scrollToBottom("auto");
|
|
91
|
+
});
|
|
91
92
|
|
|
92
93
|
const autoScrollRef = useComposedRefs<TElement>(resizeRef, scrollRef, divRef);
|
|
93
94
|
return autoScrollRef as RefCallback<TElement>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ComponentType } from "react";
|
|
1
|
+
import type { ComponentType, PropsWithChildren } from "react";
|
|
2
2
|
import type {
|
|
3
3
|
MessagePartStatus,
|
|
4
4
|
FileMessagePart,
|
|
@@ -24,6 +24,12 @@ export type ReasoningMessagePartProps = MessagePartState & ReasoningMessagePart;
|
|
|
24
24
|
export type ReasoningMessagePartComponent =
|
|
25
25
|
ComponentType<ReasoningMessagePartProps>;
|
|
26
26
|
|
|
27
|
+
export type ReasoningGroupProps = PropsWithChildren<{
|
|
28
|
+
startIndex: number;
|
|
29
|
+
endIndex: number;
|
|
30
|
+
}>;
|
|
31
|
+
export type ReasoningGroupComponent = ComponentType<ReasoningGroupProps>;
|
|
32
|
+
|
|
27
33
|
export type SourceMessagePartProps = MessagePartState & SourceMessagePart;
|
|
28
34
|
export type SourceMessagePartComponent = ComponentType<SourceMessagePartProps>;
|
|
29
35
|
|
package/src/types/index.ts
CHANGED