@assistant-ui/react 0.11.41 → 0.11.43

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.
Files changed (144) hide show
  1. package/dist/client/AssistantClient.d.ts.map +1 -1
  2. package/dist/client/AssistantClient.js.map +1 -1
  3. package/dist/client/ModelContext.d.ts +1 -1
  4. package/dist/client/ModelContext.d.ts.map +1 -1
  5. package/dist/client/ModelContext.js.map +1 -1
  6. package/dist/client/ModelContextClient.d.ts +1 -1
  7. package/dist/client/ThreadMessageClient.d.ts +1 -0
  8. package/dist/client/ThreadMessageClient.d.ts.map +1 -1
  9. package/dist/client/ThreadMessageClient.js +3 -1
  10. package/dist/client/ThreadMessageClient.js.map +1 -1
  11. package/dist/client/types/Message.d.ts +2 -0
  12. package/dist/client/types/Message.d.ts.map +1 -1
  13. package/dist/client/types/ModelContext.d.ts +1 -1
  14. package/dist/client/types/ModelContext.d.ts.map +1 -1
  15. package/dist/client/types/Tools.d.ts +1 -2
  16. package/dist/client/types/Tools.d.ts.map +1 -1
  17. package/dist/context/providers/ThreadViewportProvider.d.ts +5 -1
  18. package/dist/context/providers/ThreadViewportProvider.d.ts.map +1 -1
  19. package/dist/context/providers/ThreadViewportProvider.js +17 -6
  20. package/dist/context/providers/ThreadViewportProvider.js.map +1 -1
  21. package/dist/context/react/AssistantApiContext.d.ts +1 -1
  22. package/dist/context/react/AssistantApiContext.d.ts.map +1 -1
  23. package/dist/context/react/AssistantApiContext.js +1 -2
  24. package/dist/context/react/AssistantApiContext.js.map +1 -1
  25. package/dist/context/stores/ThreadViewport.d.ts +33 -3
  26. package/dist/context/stores/ThreadViewport.d.ts.map +1 -1
  27. package/dist/context/stores/ThreadViewport.js +67 -5
  28. package/dist/context/stores/ThreadViewport.js.map +1 -1
  29. package/dist/devtools/DevToolsHooks.d.ts +1 -1
  30. package/dist/devtools/DevToolsHooks.d.ts.map +1 -1
  31. package/dist/devtools/DevToolsHooks.js.map +1 -1
  32. package/dist/legacy-runtime/AssistantRuntimeProvider.d.ts.map +1 -1
  33. package/dist/legacy-runtime/AssistantRuntimeProvider.js +2 -1
  34. package/dist/legacy-runtime/AssistantRuntimeProvider.js.map +1 -1
  35. package/dist/legacy-runtime/client/ComposerRuntimeClient.d.ts +3 -3
  36. package/dist/legacy-runtime/client/ComposerRuntimeClient.d.ts.map +1 -1
  37. package/dist/legacy-runtime/client/ComposerRuntimeClient.js.map +1 -1
  38. package/dist/legacy-runtime/client/EventManagerRuntimeClient.d.ts +1 -1
  39. package/dist/legacy-runtime/client/ThreadRuntimeClient.js.map +1 -1
  40. package/dist/legacy-runtime/runtime/MessageRuntime.d.ts +3 -0
  41. package/dist/legacy-runtime/runtime/MessageRuntime.d.ts.map +1 -1
  42. package/dist/legacy-runtime/runtime/MessageRuntime.js.map +1 -1
  43. package/dist/legacy-runtime/runtime/RuntimeBindings.d.ts +2 -0
  44. package/dist/legacy-runtime/runtime/RuntimeBindings.d.ts.map +1 -1
  45. package/dist/legacy-runtime/runtime/ThreadRuntime.d.ts +1 -0
  46. package/dist/legacy-runtime/runtime/ThreadRuntime.d.ts.map +1 -1
  47. package/dist/legacy-runtime/runtime/ThreadRuntime.js +6 -3
  48. package/dist/legacy-runtime/runtime/ThreadRuntime.js.map +1 -1
  49. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js +5 -5
  50. package/dist/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.js.map +1 -1
  51. package/dist/legacy-runtime/runtime-cores/core/BaseThreadRuntimeCore.d.ts +1 -0
  52. package/dist/legacy-runtime/runtime-cores/core/BaseThreadRuntimeCore.d.ts.map +1 -1
  53. package/dist/legacy-runtime/runtime-cores/core/ThreadRuntimeCore.d.ts +1 -0
  54. package/dist/legacy-runtime/runtime-cores/core/ThreadRuntimeCore.d.ts.map +1 -1
  55. package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListHookInstanceManager.d.ts +2 -0
  56. package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  57. package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.d.ts +2 -0
  58. package/dist/legacy-runtime/runtime-cores/remote-thread-list/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  59. package/dist/legacy-runtime/runtime-cores/utils/MessageRepository.d.ts +1 -0
  60. package/dist/legacy-runtime/runtime-cores/utils/MessageRepository.d.ts.map +1 -1
  61. package/dist/legacy-runtime/runtime-cores/utils/MessageRepository.js +2 -1
  62. package/dist/legacy-runtime/runtime-cores/utils/MessageRepository.js.map +1 -1
  63. package/dist/primitives/composer/ComposerAttachmentDropzone.d.ts +2 -2
  64. package/dist/primitives/composer/ComposerAttachmentDropzone.d.ts.map +1 -1
  65. package/dist/primitives/composer/ComposerAttachmentDropzone.js +31 -11
  66. package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
  67. package/dist/primitives/composer/index.d.ts +1 -0
  68. package/dist/primitives/composer/index.d.ts.map +1 -1
  69. package/dist/primitives/composer/index.js +2 -0
  70. package/dist/primitives/composer/index.js.map +1 -1
  71. package/dist/primitives/message/MessageRoot.d.ts +3 -0
  72. package/dist/primitives/message/MessageRoot.d.ts.map +1 -1
  73. package/dist/primitives/message/MessageRoot.js +24 -2
  74. package/dist/primitives/message/MessageRoot.js.map +1 -1
  75. package/dist/primitives/thread/ThreadScrollToBottom.d.ts +7 -2
  76. package/dist/primitives/thread/ThreadScrollToBottom.d.ts.map +1 -1
  77. package/dist/primitives/thread/ThreadScrollToBottom.js +7 -4
  78. package/dist/primitives/thread/ThreadScrollToBottom.js.map +1 -1
  79. package/dist/primitives/thread/ThreadViewport.d.ts +17 -3
  80. package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
  81. package/dist/primitives/thread/ThreadViewport.js +19 -5
  82. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  83. package/dist/primitives/thread/ThreadViewportFooter.d.ts +31 -0
  84. package/dist/primitives/thread/ThreadViewportFooter.d.ts.map +1 -0
  85. package/dist/primitives/thread/ThreadViewportFooter.js +27 -0
  86. package/dist/primitives/thread/ThreadViewportFooter.js.map +1 -0
  87. package/dist/primitives/thread/ThreadViewportSlack.d.ts +20 -0
  88. package/dist/primitives/thread/ThreadViewportSlack.d.ts.map +1 -0
  89. package/dist/primitives/thread/ThreadViewportSlack.js +77 -0
  90. package/dist/primitives/thread/ThreadViewportSlack.js.map +1 -0
  91. package/dist/primitives/thread/index.d.ts +3 -0
  92. package/dist/primitives/thread/index.d.ts.map +1 -1
  93. package/dist/primitives/thread/index.js +7 -1
  94. package/dist/primitives/thread/index.js.map +1 -1
  95. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts +6 -0
  96. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  97. package/dist/primitives/thread/useThreadViewportAutoScroll.js +17 -8
  98. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  99. package/dist/utils/hooks/useOnScrollToBottom.d.ts +3 -1
  100. package/dist/utils/hooks/useOnScrollToBottom.d.ts.map +1 -1
  101. package/dist/utils/hooks/useOnScrollToBottom.js.map +1 -1
  102. package/dist/utils/hooks/useSizeHandle.d.ts +11 -0
  103. package/dist/utils/hooks/useSizeHandle.d.ts.map +1 -0
  104. package/dist/utils/hooks/useSizeHandle.js +30 -0
  105. package/dist/utils/hooks/useSizeHandle.js.map +1 -0
  106. package/dist/utils/tap-store/derived-scopes.d.ts +2 -1
  107. package/dist/utils/tap-store/derived-scopes.d.ts.map +1 -1
  108. package/dist/utils/tap-store/derived-scopes.js.map +1 -1
  109. package/dist/utils/tap-store/store.d.ts +2 -1
  110. package/dist/utils/tap-store/store.d.ts.map +1 -1
  111. package/dist/utils/tap-store/store.js.map +1 -1
  112. package/package.json +3 -3
  113. package/src/client/AssistantClient.ts +1 -1
  114. package/src/client/ModelContext.ts +1 -1
  115. package/src/client/ThreadMessageClient.tsx +4 -1
  116. package/src/client/types/Message.ts +3 -0
  117. package/src/client/types/ModelContext.ts +1 -1
  118. package/src/client/types/Tools.ts +1 -2
  119. package/src/context/providers/ThreadViewportProvider.tsx +27 -5
  120. package/src/context/react/AssistantApiContext.tsx +2 -5
  121. package/src/context/stores/ThreadViewport.tsx +125 -7
  122. package/src/devtools/DevToolsHooks.ts +1 -1
  123. package/src/legacy-runtime/AssistantRuntimeProvider.tsx +6 -1
  124. package/src/legacy-runtime/client/ComposerRuntimeClient.ts +3 -3
  125. package/src/legacy-runtime/client/ThreadRuntimeClient.ts +2 -2
  126. package/src/legacy-runtime/runtime/MessageRuntime.ts +2 -0
  127. package/src/legacy-runtime/runtime/RuntimeBindings.ts +2 -0
  128. package/src/legacy-runtime/runtime/ThreadRuntime.ts +6 -3
  129. package/src/legacy-runtime/runtime-cores/assistant-transport/useToolInvocations.ts +5 -5
  130. package/src/legacy-runtime/runtime-cores/core/ThreadRuntimeCore.tsx +1 -0
  131. package/src/legacy-runtime/runtime-cores/utils/MessageRepository.tsx +1 -0
  132. package/src/primitives/composer/ComposerAttachmentDropzone.tsx +35 -12
  133. package/src/primitives/composer/index.ts +1 -0
  134. package/src/primitives/message/MessageRoot.tsx +45 -2
  135. package/src/primitives/thread/ThreadScrollToBottom.tsx +12 -3
  136. package/src/primitives/thread/ThreadViewport.tsx +35 -9
  137. package/src/primitives/thread/ThreadViewportFooter.tsx +57 -0
  138. package/src/primitives/thread/ThreadViewportSlack.tsx +109 -0
  139. package/src/primitives/thread/index.ts +3 -0
  140. package/src/primitives/thread/useThreadViewportAutoScroll.tsx +24 -12
  141. package/src/utils/hooks/useOnScrollToBottom.tsx +3 -1
  142. package/src/utils/hooks/useSizeHandle.ts +43 -0
  143. package/src/utils/tap-store/derived-scopes.ts +2 -1
  144. package/src/utils/tap-store/store.ts +1 -1
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  tapMemo,
3
3
  resource,
4
- Unsubscribe,
5
4
  tapInlineResource,
6
5
  ResourceElement,
7
6
  tapResource,
@@ -29,6 +28,7 @@ import { withModelContextProvider } from "./ModelContext";
29
28
  import { ToolsApi, ToolsState } from "./types/Tools";
30
29
  import { ModelContextApi, ModelContextState } from "./types/ModelContext";
31
30
  import { ModelContext as ModelContextResource } from "./ModelContextClient";
31
+ import { Unsubscribe } from "../types";
32
32
 
33
33
  type AssistantClientState = {
34
34
  readonly threads: ThreadListClientState;
@@ -2,9 +2,9 @@ import {
2
2
  createContext,
3
3
  tapContext,
4
4
  withContextProvider,
5
- Unsubscribe,
6
5
  } from "@assistant-ui/tap";
7
6
  import { ModelContextProvider } from "../model-context/ModelContextTypes";
7
+ import { Unsubscribe } from "../types";
8
8
 
9
9
  export type ModelContextRegistrar = ModelContextProvider & {
10
10
  register: (provider: ModelContextProvider) => Unsubscribe;
@@ -63,6 +63,7 @@ const ThreadMessageAttachmentClient = resource(
63
63
  );
64
64
  export type ThreadMessageClientProps = {
65
65
  message: ThreadMessage;
66
+ index: number;
66
67
  isLast?: boolean;
67
68
  branchNumber?: number;
68
69
  branchCount?: number;
@@ -70,6 +71,7 @@ export type ThreadMessageClientProps = {
70
71
  export const ThreadMessageClient = resource(
71
72
  ({
72
73
  message,
74
+ index,
73
75
  isLast = true,
74
76
  branchNumber = 1,
75
77
  branchCount = 1,
@@ -102,6 +104,7 @@ export const ThreadMessageClient = resource(
102
104
  parts: parts.state,
103
105
  composer: composerState.state,
104
106
  parentId: null,
107
+ index,
105
108
  isLast,
106
109
  branchNumber,
107
110
  branchCount,
@@ -110,7 +113,7 @@ export const ThreadMessageClient = resource(
110
113
  isCopied: isCopiedState,
111
114
  isHovering: isHoveringState,
112
115
  };
113
- }, [message, isCopiedState, isHoveringState, isLast]);
116
+ }, [message, index, isCopiedState, isHoveringState, isLast]);
114
117
 
115
118
  return tapApi<MessageClientApi>({
116
119
  getState: () => state,
@@ -30,6 +30,9 @@ export type MessageClientState = ThreadMessage & {
30
30
 
31
31
  readonly isCopied: boolean;
32
32
  readonly isHovering: boolean;
33
+
34
+ /** The position of this message in the thread (0 for first message) */
35
+ readonly index: number;
33
36
  };
34
37
 
35
38
  export type MessageClientApi = {
@@ -1,4 +1,4 @@
1
- import type { Unsubscribe } from "@assistant-ui/tap";
1
+ import type { Unsubscribe } from "../../types";
2
2
  import type { ModelContextProvider } from "../../model-context/ModelContextTypes";
3
3
 
4
4
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
@@ -1,5 +1,4 @@
1
- import { Unsubscribe } from "@assistant-ui/tap";
2
- import { ToolCallMessagePartComponent } from "../../types";
1
+ import { ToolCallMessagePartComponent, Unsubscribe } from "../../types";
3
2
 
4
3
  export type ToolsState = {
5
4
  tools: Record<string, ToolCallMessagePartComponent[]>;
@@ -2,7 +2,10 @@
2
2
 
3
3
  import type { FC, PropsWithChildren } from "react";
4
4
  import { useEffect, useState } from "react";
5
- import { makeThreadViewportStore } from "../stores/ThreadViewport";
5
+ import {
6
+ makeThreadViewportStore,
7
+ type ThreadViewportStoreOptions,
8
+ } from "../stores/ThreadViewport";
6
9
  import {
7
10
  ThreadViewportContext,
8
11
  ThreadViewportContextValue,
@@ -10,10 +13,15 @@ import {
10
13
  } from "../react/ThreadViewportContext";
11
14
  import { writableStore } from "../ReadonlyStore";
12
15
 
13
- const useThreadViewportStoreValue = () => {
16
+ export type ThreadViewportProviderProps = PropsWithChildren<{
17
+ options?: ThreadViewportStoreOptions;
18
+ }>;
19
+
20
+ const useThreadViewportStoreValue = (options: ThreadViewportStoreOptions) => {
14
21
  const outerViewport = useThreadViewportStore({ optional: true });
15
- const [store] = useState(() => makeThreadViewportStore());
22
+ const [store] = useState(() => makeThreadViewportStore(options));
16
23
 
24
+ // Forward scrollToBottom from outer viewport to inner viewport
17
25
  useEffect(() => {
18
26
  return outerViewport?.getState().onScrollToBottom(() => {
19
27
  store.getState().scrollToBottom();
@@ -29,11 +37,25 @@ const useThreadViewportStoreValue = () => {
29
37
  });
30
38
  }, [store, outerViewport]);
31
39
 
40
+ // Sync options to store when they change
41
+ useEffect(() => {
42
+ const nextState = {
43
+ turnAnchor: options.turnAnchor ?? "bottom",
44
+ };
45
+
46
+ const currentState = store.getState();
47
+ if (currentState.turnAnchor !== nextState.turnAnchor) {
48
+ writableStore(store).setState(nextState);
49
+ }
50
+ }, [store, options.turnAnchor]);
51
+
32
52
  return store;
33
53
  };
34
54
 
35
- export const ThreadViewportProvider: FC<PropsWithChildren> = ({ children }) => {
36
- const useThreadViewport = useThreadViewportStoreValue();
55
+ export const ThreadPrimitiveViewportProvider: FC<
56
+ ThreadViewportProviderProps
57
+ > = ({ children, options = {} }) => {
58
+ const useThreadViewport = useThreadViewportStoreValue(options);
37
59
 
38
60
  const [context] = useState<ThreadViewportContextValue>(() => {
39
61
  return {
@@ -31,7 +31,7 @@ import {
31
31
  AttachmentClientApi,
32
32
  AttachmentClientState,
33
33
  } from "../../client/types/Attachment";
34
- import { Unsubscribe } from "@assistant-ui/tap";
34
+ import { Unsubscribe } from "../../types";
35
35
  import {
36
36
  AssistantEvent,
37
37
  AssistantEventCallback,
@@ -42,7 +42,6 @@ import {
42
42
  ThreadListClientApi,
43
43
  ThreadListClientState,
44
44
  } from "../../client/types/ThreadList";
45
- import { ThreadViewportProvider } from "../providers/ThreadViewportProvider";
46
45
  import { DevToolsProviderApi } from "../../devtools/DevToolsHooks";
47
46
  import {
48
47
  AssistantClientProps,
@@ -354,9 +353,7 @@ export const AssistantProvider: FC<
354
353
 
355
354
  return (
356
355
  <AssistantApiContext.Provider value={api}>
357
- {/* TODO temporarily allow accessing viewport state from outside the viewport */}
358
- {/* TODO figure out if this behavior should be deprecated, since it is quite hacky */}
359
- <ThreadViewportProvider>{children}</ThreadViewportProvider>
356
+ {children}
360
357
  </AssistantApiContext.Provider>
361
358
  );
362
359
  };
@@ -3,20 +3,124 @@
3
3
  import { create } from "zustand";
4
4
  import type { Unsubscribe } from "../../types/Unsubscribe";
5
5
 
6
+ export type SizeHandle = {
7
+ /** Update the height */
8
+ setHeight: (height: number) => void;
9
+ /** Unregister this handle */
10
+ unregister: Unsubscribe;
11
+ };
12
+
13
+ type SizeRegistry = {
14
+ register: () => SizeHandle;
15
+ };
16
+
17
+ const createSizeRegistry = (
18
+ onChange: (total: number) => void,
19
+ ): SizeRegistry => {
20
+ const entries = new Map<symbol, number>();
21
+
22
+ const recalculate = () => {
23
+ let total = 0;
24
+ for (const height of entries.values()) {
25
+ total += height;
26
+ }
27
+ onChange(total);
28
+ };
29
+
30
+ return {
31
+ register: () => {
32
+ const id = Symbol();
33
+ entries.set(id, 0);
34
+
35
+ return {
36
+ setHeight: (height: number) => {
37
+ if (entries.get(id) !== height) {
38
+ entries.set(id, height);
39
+ recalculate();
40
+ }
41
+ },
42
+ unregister: () => {
43
+ entries.delete(id);
44
+ recalculate();
45
+ },
46
+ };
47
+ },
48
+ };
49
+ };
50
+
6
51
  export type ThreadViewportState = {
7
52
  readonly isAtBottom: boolean;
8
- readonly scrollToBottom: () => void;
9
- readonly onScrollToBottom: (callback: () => void) => Unsubscribe;
53
+ readonly scrollToBottom: (config?: {
54
+ behavior?: ScrollBehavior | undefined;
55
+ }) => void;
56
+ readonly onScrollToBottom: (
57
+ callback: ({ behavior }: { behavior: ScrollBehavior }) => void,
58
+ ) => Unsubscribe;
59
+
60
+ /** Controls scroll anchoring: "top" anchors user messages at top, "bottom" is classic behavior */
61
+ readonly turnAnchor: "top" | "bottom";
62
+
63
+ /** Raw height values from registered elements */
64
+ readonly height: {
65
+ /** Total viewport height */
66
+ readonly viewport: number;
67
+ /** Total content inset height (footer, anchor message, etc.) */
68
+ readonly inset: number;
69
+ /** Height of the anchor user message (full height) */
70
+ readonly userMessage: number;
71
+ };
72
+
73
+ /** Register a viewport and get a handle to update its height */
74
+ readonly registerViewport: () => SizeHandle;
75
+
76
+ /** Register a content inset (footer, anchor message, etc.) and get a handle to update its height */
77
+ readonly registerContentInset: () => SizeHandle;
78
+
79
+ /** Register the anchor user message height */
80
+ readonly registerUserMessageHeight: () => SizeHandle;
81
+ };
82
+
83
+ export type ThreadViewportStoreOptions = {
84
+ turnAnchor?: "top" | "bottom" | undefined;
10
85
  };
11
86
 
12
- export const makeThreadViewportStore = () => {
13
- const scrollToBottomListeners = new Set<() => void>();
87
+ export const makeThreadViewportStore = (
88
+ options: ThreadViewportStoreOptions = {},
89
+ ) => {
90
+ const scrollToBottomListeners = new Set<
91
+ (config: { behavior: ScrollBehavior }) => void
92
+ >();
14
93
 
15
- return create<ThreadViewportState>(() => ({
94
+ const viewportRegistry = createSizeRegistry((total) => {
95
+ store.setState({
96
+ height: {
97
+ ...store.getState().height,
98
+ viewport: total,
99
+ },
100
+ });
101
+ });
102
+ const insetRegistry = createSizeRegistry((total) => {
103
+ store.setState({
104
+ height: {
105
+ ...store.getState().height,
106
+ inset: total,
107
+ },
108
+ });
109
+ });
110
+ const userMessageRegistry = createSizeRegistry((total) => {
111
+ store.setState({
112
+ height: {
113
+ ...store.getState().height,
114
+ userMessage: total,
115
+ },
116
+ });
117
+ });
118
+
119
+ const store = create<ThreadViewportState>(() => ({
16
120
  isAtBottom: true,
17
- scrollToBottom: () => {
121
+ scrollToBottom: ({ behavior = "auto" } = {}) => {
18
122
  for (const listener of scrollToBottomListeners) {
19
- listener();
123
+ listener({ behavior });
20
124
  }
21
125
  },
22
126
  onScrollToBottom: (callback) => {
@@ -25,5 +129,19 @@ export const makeThreadViewportStore = () => {
25
129
  scrollToBottomListeners.delete(callback);
26
130
  };
27
131
  },
132
+
133
+ turnAnchor: options.turnAnchor ?? "bottom",
134
+
135
+ height: {
136
+ viewport: 0,
137
+ inset: 0,
138
+ userMessage: 0,
139
+ },
140
+
141
+ registerViewport: viewportRegistry.register,
142
+ registerContentInset: insetRegistry.register,
143
+ registerUserMessageHeight: userMessageRegistry.register,
28
144
  }));
145
+
146
+ return store;
29
147
  };
@@ -1,5 +1,5 @@
1
1
  import { AssistantApi } from "../context/react/AssistantApiContext";
2
- import { Unsubscribe } from "@assistant-ui/tap";
2
+ import { Unsubscribe } from "../types";
3
3
 
4
4
  export interface EventLog {
5
5
  time: Date;
@@ -8,6 +8,7 @@ import {
8
8
  import { AssistantRuntime } from "./runtime/AssistantRuntime";
9
9
  import { AssistantRuntimeCore } from "./runtime-cores/core/AssistantRuntimeCore";
10
10
  import { RuntimeAdapter } from "./RuntimeAdapter";
11
+ import { ThreadPrimitiveViewportProvider } from "../context/providers/ThreadViewportProvider";
11
12
 
12
13
  export namespace AssistantProvider {
13
14
  export type Props = PropsWithChildren<{
@@ -36,7 +37,11 @@ export const AssistantRuntimeProviderImpl: FC<AssistantProvider.Props> = ({
36
37
  <AssistantProvider api={api}>
37
38
  {RenderComponent && <RenderComponent />}
38
39
 
39
- {children}
40
+ {/* TODO temporarily allow accessing viewport state from outside the viewport */}
41
+ {/* TODO figure out if this behavior should be deprecated, since it is quite hacky */}
42
+ <ThreadPrimitiveViewportProvider>
43
+ {children}
44
+ </ThreadPrimitiveViewportProvider>
40
45
  </AssistantProvider>
41
46
  );
42
47
  };
@@ -2,8 +2,8 @@ import {
2
2
  resource,
3
3
  tapMemo,
4
4
  tapEffect,
5
- RefObject,
6
5
  tapInlineResource,
6
+ type tapRef,
7
7
  } from "@assistant-ui/tap";
8
8
  import {
9
9
  ComposerRuntime,
@@ -43,8 +43,8 @@ export const ComposerClient = resource(
43
43
  messageIdRef,
44
44
  runtime,
45
45
  }: {
46
- threadIdRef: RefObject<string>;
47
- messageIdRef?: RefObject<string>;
46
+ threadIdRef: tapRef.RefObject<string>;
47
+ messageIdRef?: tapRef.RefObject<string>;
48
48
  runtime: ComposerRuntime;
49
49
  }) => {
50
50
  const runtimeState = tapSubscribable(runtime);
@@ -5,7 +5,7 @@ import {
5
5
  tapInlineResource,
6
6
  tapMemo,
7
7
  tapEffect,
8
- RefObject,
8
+ type tapRef,
9
9
  } from "@assistant-ui/tap";
10
10
  import { ComposerClient } from "./ComposerRuntimeClient";
11
11
  import { MessageClient } from "./MessageRuntimeClient";
@@ -24,7 +24,7 @@ const MessageClientById = resource(
24
24
  }: {
25
25
  runtime: ThreadRuntime;
26
26
  id: string;
27
- threadIdRef: RefObject<string>;
27
+ threadIdRef: tapRef.RefObject<string>;
28
28
  }) => {
29
29
  const messageRuntime = tapMemo(
30
30
  () => runtime.getMessageById(id),
@@ -80,6 +80,8 @@ const getMessagePartState = (
80
80
 
81
81
  export type MessageState = ThreadMessage & {
82
82
  readonly parentId: string | null;
83
+ /** The position of this message in the thread (0 for first message) */
84
+ readonly index: number;
83
85
  readonly isLast: boolean;
84
86
 
85
87
  readonly branchNumber: number;
@@ -45,6 +45,8 @@ export type ThreadListRuntimeCoreBinding = SubscribableWithState<
45
45
  export type MessageStateBinding = SubscribableWithState<
46
46
  ThreadMessage & {
47
47
  readonly parentId: string | null;
48
+ /** The position of this message in the thread (0 for first message) */
49
+ readonly index: number;
48
50
  readonly isLast: boolean;
49
51
  readonly branchNumber: number;
50
52
  readonly branchCount: number;
@@ -447,6 +447,7 @@ export class ThreadRuntimeImpl implements ThreadRuntime {
447
447
  return {
448
448
  message,
449
449
  parentId: messages[idx - 1]?.id ?? null,
450
+ index: idx,
450
451
  };
451
452
  },
452
453
  );
@@ -468,19 +469,20 @@ export class ThreadRuntimeImpl implements ThreadRuntime {
468
469
  private _getMessageRuntime(
469
470
  path: MessageRuntimePath,
470
471
  callback: () =>
471
- | { parentId: string | null; message: ThreadMessage }
472
+ | { parentId: string | null; message: ThreadMessage; index: number }
472
473
  | undefined,
473
474
  ) {
474
475
  return new MessageRuntimeImpl(
475
476
  new ShallowMemoizeSubject({
476
477
  path,
477
478
  getState: () => {
478
- const { message, parentId } = callback() ?? {};
479
+ const { message, parentId, index } = callback() ?? {};
479
480
 
480
481
  const { messages, speech: speechState } =
481
482
  this._threadBinding.getState();
482
483
 
483
- if (!message || parentId === undefined) return SKIP_UPDATE;
484
+ if (!message || parentId === undefined || index === undefined)
485
+ return SKIP_UPDATE;
484
486
 
485
487
  const thread = this._threadBinding.getState();
486
488
 
@@ -491,6 +493,7 @@ export class ThreadRuntimeImpl implements ThreadRuntime {
491
493
  ...message,
492
494
  ...{ [symbolInnerMessage]: (message as any)[symbolInnerMessage] },
493
495
 
496
+ index,
494
497
  isLast: messages.at(-1)?.id === message.id,
495
498
  parentId,
496
499
 
@@ -131,7 +131,7 @@ export function useToolInvocations({
131
131
  });
132
132
 
133
133
  const ignoredToolIds = useRef<Set<string>>(new Set());
134
- const isInititialState = useRef(true);
134
+ const isInitialState = useRef(true);
135
135
 
136
136
  useEffect(() => {
137
137
  const processMessages = (
@@ -140,7 +140,7 @@ export function useToolInvocations({
140
140
  messages.forEach((message) => {
141
141
  message.content.forEach((content) => {
142
142
  if (content.type === "tool-call") {
143
- if (isInititialState.current) {
143
+ if (isInitialState.current) {
144
144
  ignoredToolIds.current.add(content.toolCallId);
145
145
  } else {
146
146
  if (ignoredToolIds.current.has(content.toolCallId)) {
@@ -225,8 +225,8 @@ export function useToolInvocations({
225
225
 
226
226
  processMessages(state.messages);
227
227
 
228
- if (isInititialState.current) {
229
- isInititialState.current = false;
228
+ if (isInitialState.current) {
229
+ isInitialState.current = false;
230
230
  }
231
231
  }, [state, controller, onResult]);
232
232
 
@@ -244,7 +244,7 @@ export function useToolInvocations({
244
244
  return {
245
245
  reset: () => {
246
246
  abort();
247
- isInititialState.current = true;
247
+ isInitialState.current = true;
248
248
  },
249
249
  abort,
250
250
  resume: (toolCallId: string, payload: unknown) => {
@@ -79,6 +79,7 @@ export type ThreadRuntimeCore = Readonly<{
79
79
  | {
80
80
  parentId: string | null;
81
81
  message: ThreadMessage;
82
+ index: number;
82
83
  }
83
84
  | undefined;
84
85
 
@@ -339,6 +339,7 @@ export class MessageRepository {
339
339
  return {
340
340
  parentId: message.prev?.current.id ?? null,
341
341
  message: message.current,
342
+ index: message.level,
342
343
  };
343
344
  }
344
345
 
@@ -1,10 +1,12 @@
1
+ "use client";
2
+
1
3
  import { forwardRef, useCallback, useState } from "react";
2
4
 
3
5
  import { Slot } from "@radix-ui/react-slot";
4
6
  import React from "react";
5
7
  import { useAssistantApi } from "../../context";
6
8
 
7
- export namespace ComposerAttachmentDropzonePrimitive {
9
+ export namespace ComposerPrimitiveAttachmentDropzone {
8
10
  export type Element = HTMLDivElement;
9
11
  export type Props = React.HTMLAttributes<HTMLDivElement> & {
10
12
  asChild?: boolean | undefined;
@@ -12,19 +14,40 @@ export namespace ComposerAttachmentDropzonePrimitive {
12
14
  };
13
15
  }
14
16
 
15
- export const ComposerAttachmentDropzone = forwardRef<
17
+ export const ComposerPrimitiveAttachmentDropzone = forwardRef<
16
18
  HTMLDivElement,
17
- ComposerAttachmentDropzonePrimitive.Props
19
+ ComposerPrimitiveAttachmentDropzone.Props
18
20
  >(({ disabled, asChild = false, children, ...rest }, ref) => {
19
21
  const [isDragging, setIsDragging] = useState(false);
20
22
  const api = useAssistantApi();
21
23
 
22
- const handleDrag = useCallback(
24
+ const handleDragEnterCapture = useCallback(
25
+ (e: React.DragEvent) => {
26
+ if (disabled) return;
27
+ e.preventDefault();
28
+ setIsDragging(true);
29
+ },
30
+ [disabled],
31
+ );
32
+
33
+ const handleDragOverCapture = useCallback(
23
34
  (e: React.DragEvent) => {
24
35
  if (disabled) return;
25
36
  e.preventDefault();
26
- e.stopPropagation();
27
- setIsDragging(e.type === "dragenter" || e.type === "dragover");
37
+ if (!isDragging) setIsDragging(true);
38
+ },
39
+ [disabled, isDragging],
40
+ );
41
+
42
+ const handleDragLeaveCapture = useCallback(
43
+ (e: React.DragEvent) => {
44
+ if (disabled) return;
45
+ e.preventDefault();
46
+ const next = e.relatedTarget as Node | null;
47
+ if (next && e.currentTarget.contains(next)) {
48
+ return;
49
+ }
50
+ setIsDragging(false);
28
51
  },
29
52
  [disabled],
30
53
  );
@@ -33,7 +56,6 @@ export const ComposerAttachmentDropzone = forwardRef<
33
56
  async (e: React.DragEvent) => {
34
57
  if (disabled) return;
35
58
  e.preventDefault();
36
- e.stopPropagation();
37
59
  setIsDragging(false);
38
60
  for (const file of e.dataTransfer.files) {
39
61
  try {
@@ -47,10 +69,10 @@ export const ComposerAttachmentDropzone = forwardRef<
47
69
  );
48
70
 
49
71
  const dragProps = {
50
- onDragEnter: handleDrag,
51
- onDragOver: handleDrag,
52
- onDragLeave: handleDrag,
53
- onDrop: handleDrop,
72
+ onDragEnterCapture: handleDragEnterCapture,
73
+ onDragOverCapture: handleDragOverCapture,
74
+ onDragLeaveCapture: handleDragLeaveCapture,
75
+ onDropCapture: handleDrop,
54
76
  };
55
77
 
56
78
  const Comp = asChild ? Slot : "div";
@@ -67,4 +89,5 @@ export const ComposerAttachmentDropzone = forwardRef<
67
89
  );
68
90
  });
69
91
 
70
- ComposerAttachmentDropzone.displayName = "ComposerPrimitive.AttachmentDropzone";
92
+ ComposerPrimitiveAttachmentDropzone.displayName =
93
+ "ComposerPrimitive.AttachmentDropzone";
@@ -5,4 +5,5 @@ export { ComposerPrimitiveCancel as Cancel } from "./ComposerCancel";
5
5
  export { ComposerPrimitiveAddAttachment as AddAttachment } from "./ComposerAddAttachment";
6
6
  export { ComposerPrimitiveAttachments as Attachments } from "./ComposerAttachments";
7
7
  export { ComposerPrimitiveAttachmentByIndex as AttachmentByIndex } from "./ComposerAttachments";
8
+ export { ComposerPrimitiveAttachmentDropzone as AttachmentDropzone } from "./ComposerAttachmentDropzone";
8
9
  export { ComposerPrimitiveIf as If } from "./ComposerIf";