@agents24/chat-react 0.5.15 → 0.5.17

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 (36) hide show
  1. package/compatibility.json +8 -8
  2. package/dist/index.cjs +8 -0
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.js +7 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/latest-thread-scroller.d.ts +5 -0
  7. package/dist/runtime/index.cjs +16 -2
  8. package/dist/runtime/index.cjs.map +1 -1
  9. package/dist/runtime/index.js +16 -2
  10. package/dist/runtime/index.js.map +1 -1
  11. package/dist/runtime/use-agent-chat-runtime.d.ts +1 -0
  12. package/dist/scaffold/components/chat/chat-composer.d.ts +6 -1
  13. package/dist/scaffold/components/chat/chat-message.d.ts +2 -1
  14. package/dist/scaffold/index.cjs +185 -286
  15. package/dist/scaffold/index.cjs.map +1 -1
  16. package/dist/scaffold/index.js +190 -291
  17. package/dist/scaffold/index.js.map +1 -1
  18. package/dist/styles.css +34 -1
  19. package/dist/types.d.ts +1 -1
  20. package/dist/ui/agent-chat-actions.d.ts +4 -1
  21. package/dist/ui/agent-chat-composer.d.ts +9 -1
  22. package/dist/ui/agent-chat-queue.d.ts +12 -0
  23. package/dist/ui/composer-attachments.d.ts +5 -1
  24. package/dist/ui/composer-interactions.d.ts +17 -0
  25. package/dist/ui/index.cjs +531 -331
  26. package/dist/ui/index.cjs.map +1 -1
  27. package/dist/ui/index.d.ts +2 -0
  28. package/dist/ui/index.js +516 -322
  29. package/dist/ui/index.js.map +1 -1
  30. package/dist/ui/tool-sessions.d.ts +4 -1
  31. package/package.json +4 -4
  32. package/scaffold/src/components/chat/chat-composer.tsx +25 -26
  33. package/scaffold/src/components/chat/chat-message.tsx +7 -2
  34. package/scaffold/src/components/chat/chat-shell.tsx +52 -69
  35. package/dist/scaffold/components/ui/message-scroller.d.ts +0 -10
  36. package/scaffold/src/components/ui/message-scroller.tsx +0 -129
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import type { ChatModelTurnPart, ChatNodeActivityPart, ChatPart, ChatToolChild, ChatToolPart } from "../types";
2
+ import type { ChatComputerResourceTarget, ChatModelTurnPart, ChatNodeActivityPart, ChatPart, ChatToolChild, ChatToolPart } from "../types";
3
3
  export type AgentChatToolViewMode = "closed" | "expanded";
4
4
  export type AgentChatToolTimelineSegment = {
5
5
  kind: "part";
@@ -20,10 +20,13 @@ export type AgentChatToolSlotContext = {
20
20
  isActive: boolean;
21
21
  label: string;
22
22
  part: ChatToolPart;
23
+ resourceTargets: readonly ChatComputerResourceTarget[];
24
+ openResource: (target: ChatComputerResourceTarget) => void;
23
25
  };
24
26
  export type AgentChatToolSlots = {
25
27
  onExpandedChange?: (open: boolean, part?: ChatToolPart) => void;
26
28
  onToolSelect?: (part: ChatToolPart) => void;
29
+ onResourceAction?: (target: ChatComputerResourceTarget, part: ChatToolPart) => void;
27
30
  renderChildTool?: (child: ChatToolChild, part: ChatToolPart) => React.ReactNode;
28
31
  renderToolAccessory?: (context: AgentChatToolSlotContext) => React.ReactNode;
29
32
  renderToolDetails?: (part: ChatToolPart) => React.ReactNode;
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@agents24/chat-react",
3
- "version": "0.5.15",
3
+ "version": "0.5.17",
4
4
  "description": "Accessible web UI and complete default layout for Agents24 chat applications.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/daniel5426/talmudpedia.git",
8
+ "url": "git+https://github.com/agents24/talmudpedia.git",
9
9
  "directory": "packages/agents24-chat-react"
10
10
  },
11
11
  "private": false,
@@ -59,8 +59,8 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@untitledui/file-icons": "0.0.9",
62
- "@agents24/client": "0.2.6",
63
- "@agents24/react": "0.2.6",
62
+ "@agents24/client": "0.2.8",
63
+ "@agents24/react": "0.2.8",
64
64
  "@fontsource-variable/inter": "^5.3.0",
65
65
  "@fontsource-variable/jetbrains-mono": "^5.3.0",
66
66
  "@fontsource-variable/merriweather": "^5.3.0",
@@ -3,9 +3,11 @@ import * as React from "react"
3
3
  import {
4
4
  AgentChatContextStatus,
5
5
  ChatAttachmentRows,
6
+ chatComposerAction,
6
7
  createAgentChatComposerFile,
7
8
  revokeAgentChatComposerFiles,
8
9
  useAudioRecorder,
10
+ useChatComposerFocus,
9
11
  type AgentChatComposerFile,
10
12
  } from "@agents24/chat-react/ui"
11
13
  import { canonicalInputMimeType, inputMimeTypes, validateAgentChatSubmission, type AgentChatInputContract } from "@agents24/chat-react/runtime"
@@ -37,6 +39,7 @@ type ChatComposerProps = {
37
39
  onAgentChange: (agentId: string) => void
38
40
  onStop: () => void
39
41
  onSubmit: (text: string, files: AgentChatComposerFile[]) => Promise<void>
42
+ restoredDraft?: { revision: number; text: string; files: AgentChatComposerFile[] } | null
40
43
  utilityRow?: "inline" | "below"
41
44
  }
42
45
 
@@ -56,6 +59,7 @@ export function ChatComposer({
56
59
  onAgentChange,
57
60
  onStop,
58
61
  onSubmit,
62
+ restoredDraft,
59
63
  utilityRow = "inline",
60
64
  }: ChatComposerProps) {
61
65
  const [text, setText] = React.useState("")
@@ -77,11 +81,13 @@ export function ChatComposer({
77
81
  revokeAgentChatComposerFiles(filesRef.current)
78
82
  revokeAgentChatComposerFiles(inFlightFilesRef.current)
79
83
  }, [])
80
- React.useEffect(() => {
81
- if (disabled || isRunning) return
82
- const frame = window.requestAnimationFrame(() => textareaRef.current?.focus({ preventScroll: true }))
83
- return () => window.cancelAnimationFrame(frame)
84
- }, [disabled, focusKey, isRunning])
84
+ const focusComposer = useChatComposerFocus({ textareaRef, disabled: Boolean(disabled), focusKey, restoredDraft,
85
+ onRestore: (draft) => {
86
+ draftRevisionRef.current += 1
87
+ revokeAgentChatComposerFiles(filesRef.current)
88
+ setText(draft.text)
89
+ setFiles(draft.files)
90
+ } })
85
91
  const addRecording = React.useCallback((file: File) => {
86
92
  draftRevisionRef.current += 1
87
93
  setFiles((current) => [...current, createAgentChatComposerFile(file)])
@@ -91,8 +97,9 @@ export function ChatComposer({
91
97
  const validationError = validateAgentChatSubmission(
92
98
  inputContract,
93
99
  text,
94
- files.map((file) => ({ data: file.source, mediaType: file.mediaType })),
100
+ files.map((file) => ({ data: file.source || new Uint8Array(), mediaType: file.mediaType })),
95
101
  )
102
+ const action = chatComposerAction({ hasDraft: Boolean(text.trim() || files.length), isRunning, isCancelling })
96
103
 
97
104
  const selectedAgent = agents.find((item) => item.id === agentId)
98
105
  const selectedModel = models.find((item) => item.id === modelId)
@@ -153,7 +160,7 @@ export function ChatComposer({
153
160
  const showUtilityRow = utilityRow === "below"
154
161
 
155
162
  const submit = async () => {
156
- if (disabled || isRunning || submittingRef.current || validationError) return
163
+ if (disabled || submittingRef.current || validationError) return
157
164
  const value = text
158
165
  const selectedFiles = files
159
166
  const clearedRevision = draftRevisionRef.current + 1
@@ -162,7 +169,7 @@ export function ChatComposer({
162
169
  inFlightFilesRef.current = selectedFiles
163
170
  setText("")
164
171
  setFiles([])
165
- textareaRef.current?.focus()
172
+ focusComposer()
166
173
  try {
167
174
  await onSubmit(value, selectedFiles)
168
175
  revokeAgentChatComposerFiles(selectedFiles)
@@ -184,7 +191,7 @@ export function ChatComposer({
184
191
 
185
192
  return (
186
193
  <div
187
- className="mx-auto w-full max-w-3xl px-4 pb-[max(1rem,env(safe-area-inset-bottom))] sm:px-6"
194
+ className="relative z-10 w-full pb-[max(1rem,env(safe-area-inset-bottom))]"
188
195
  data-agents24-chat-composer=""
189
196
  >
190
197
  {allowAttachments && files.length > 0 && (
@@ -235,7 +242,7 @@ export function ChatComposer({
235
242
  }}
236
243
  />
237
244
  <div className="agents24-composer-attach flex min-w-0 items-center">
238
- <Button className="bg-muted/50 text-muted-foreground hover:bg-muted hover:text-foreground" variant="ghost" size="icon" aria-label="Attach files" disabled={disabled || isRunning} onClick={() => inputRef.current?.click()}>
245
+ <Button className="bg-muted/50 text-muted-foreground hover:bg-muted hover:text-foreground" variant="ghost" size="icon" aria-label="Attach files" disabled={disabled} onClick={() => inputRef.current?.click()}>
239
246
  <span aria-hidden>+</span>
240
247
  </Button>
241
248
  </div>
@@ -277,31 +284,23 @@ export function ChatComposer({
277
284
  {recorder.isRecording ? <Square className="fill-current" /> : <Mic />}
278
285
  </Button>
279
286
  ) : null}
280
- {isRunning ? (
281
- <Button
282
- size="icon"
283
- aria-label={isCancelling ? "Cancelling response" : "Stop response"}
284
- disabled={disabled || isCancelling}
285
- onClick={onStop}
286
- >
287
- {isCancelling
288
- ? <LoaderCircle className="animate-spin" />
289
- : <Square className="fill-current" />}
287
+ {action === "stop" ? (
288
+ <Button size="icon" aria-label={isCancelling ? "Cancelling response" : "Stop response"}
289
+ disabled={disabled || isCancelling} onClick={onStop}>
290
+ {isCancelling ? <LoaderCircle className="animate-spin" /> : <Square className="fill-current" />}
290
291
  </Button>
291
- ) : (
292
- validationError ? (
292
+ ) : validationError ? (
293
293
  <Tooltip>
294
294
  <TooltipTrigger asChild>
295
295
  <span tabIndex={0} aria-label={validationError}>
296
- <Button size="icon" aria-label="Send message" disabled onClick={() => void submit()}><ArrowUp /></Button>
296
+ <Button size="icon" aria-label={isRunning ? "Queue message" : "Send message"} disabled onClick={() => void submit()}><ArrowUp /></Button>
297
297
  </span>
298
298
  </TooltipTrigger>
299
299
  <TooltipContent>{validationError}</TooltipContent>
300
300
  </Tooltip>
301
301
  ) : (
302
- <Button size="icon" aria-label="Send message" disabled={disabled} onClick={() => void submit()}><ArrowUp /></Button>
303
- )
304
- )}
302
+ <Button size="icon" aria-label={isRunning ? "Queue message" : "Send message"} disabled={disabled} onClick={() => void submit()}><ArrowUp /></Button>
303
+ )}
305
304
  </div>
306
305
  </div>
307
306
  {showUtilityRow ? (
@@ -1,7 +1,7 @@
1
1
  import type { ChatHitlPart, ChatMessage as ChatMessageModel } from "@agents24/react"
2
2
  import type { FeedbackReason, HitlResponse } from "@agents24/client"
3
3
  import type { AgentChatRuntimeHitlActionState } from "@agents24/chat-react/runtime"
4
- import { AgentChatDefaultActions, AgentChatUserMessageActionRow, ChatAttachmentRows, type ChatAttachmentContentResolver } from "@agents24/chat-react/ui"
4
+ import { AgentChatDefaultActions, AgentChatUserMessageActionRow, ChatAttachmentRows, useCompletionActionEntrance, type ChatAttachmentContentResolver } from "@agents24/chat-react/ui"
5
5
  import * as React from "react"
6
6
 
7
7
  import { MessageParts } from "./message-parts"
@@ -10,6 +10,7 @@ import { Message, MessageContent, MessageFooter } from "../ui/message"
10
10
  import type { Agents24ChatTimelineSlotProps } from "../../app"
11
11
 
12
12
  type Props = {
13
+ animateCompletion: boolean
13
14
  message: ChatMessageModel
14
15
  getHitlActionState: (part: ChatHitlPart) => AgentChatRuntimeHitlActionState | undefined
15
16
  isLoading: boolean
@@ -24,6 +25,7 @@ type Props = {
24
25
  }
25
26
 
26
27
  export function ChatMessage({
28
+ animateCompletion,
27
29
  allowFeedback,
28
30
  getHitlActionState,
29
31
  isLoading,
@@ -35,6 +37,8 @@ export function ChatMessage({
35
37
  Timeline,
36
38
  }: Props) {
37
39
  const isUser = message.role === "user"
40
+ const showCompletionActions = !isUser && message.isFinal !== false && !isLoading
41
+ const animateCompletionActions = useCompletionActionEntrance(showCompletionActions, message.runId, animateCompletion)
38
42
  return (
39
43
  <Message align={isUser ? "end" : "start"}>
40
44
  <MessageContent className={isUser ? "gap-0" : undefined}>
@@ -66,9 +70,10 @@ export function ChatMessage({
66
70
  />
67
71
  </AgentChatUserMessageActionRow>
68
72
  )}
69
- {!isUser && message.isFinal !== false && (
73
+ {showCompletionActions && (
70
74
  <MessageFooter className="gap-1 opacity-70 transition-opacity group-hover/message:opacity-100">
71
75
  <AgentChatDefaultActions
76
+ animateCompletion={animateCompletionActions}
72
77
  handlers={{
73
78
  feedback: message.feedback,
74
79
  onCopy: (content) => { void navigator.clipboard.writeText(content).catch(() => undefined) },
@@ -3,6 +3,8 @@ import {
3
3
  type AgentChatRuntime,
4
4
  } from "@agents24/chat-react/runtime"
5
5
  import type { Agents24ChatScaffoldComponents } from "../../app"
6
+ import type { ThreadMessage } from "@agents24/client"
7
+ import { AgentChatQueue, queuedMessageComposerFiles, useLiveCompletionRuns, type AgentChatComposerFile } from "@agents24/chat-react/ui"
6
8
  import { CircleAlert, LoaderCircle, SquarePen, X } from "lucide-react"
7
9
  import * as React from "react"
8
10
  import type { Agents24ChatIdentity } from "../../identity"
@@ -15,13 +17,9 @@ import { TurnOutline } from "./turn-outline"
15
17
  import { isScaffoldTimelineLoading } from "./timeline-loading"
16
18
  import { Button } from "../ui/button"
17
19
  import {
18
- MessageScroller,
19
- MessageScrollerButton,
20
- MessageScrollerContent,
21
- MessageScrollerItem,
22
- MessageScrollerProvider,
23
- MessageScrollerViewport,
24
- } from "../ui/message-scroller"
20
+ LatestThreadScroller,
21
+ latestThreadAnchorId,
22
+ } from "@agents24/chat-react"
25
23
  import { SidebarInset, SidebarProvider, SidebarTrigger, useSidebar } from "../ui/sidebar"
26
24
 
27
25
  const LOADING_INPUT_CONTRACT = {
@@ -95,20 +93,30 @@ export function ChatShell({
95
93
  sidebar?: React.ReactNode
96
94
  }) {
97
95
  const { controller } = runtime
96
+ const isUploading = controller.runState === "uploading"
98
97
  const isRunning =
99
98
  controller.runState === "streaming"
100
99
  || controller.runState === "reconnecting"
101
100
  || controller.runState === "paused"
102
101
  || controller.runState === "cancelling"
103
- const isUploading = controller.runState === "uploading" || runtime.isUploading
104
102
  const isCancelling = controller.runState === "cancelling"
103
+ const wasLiveRun = useLiveCompletionRuns(controller.activeRunId, isRunning)
105
104
  const activeTitle = controller.threads.find((thread) => thread.id === controller.activeThreadId)?.title
106
105
  const hasBlockingError = Boolean(runtime.fatalError && controller.messages.length === 0)
107
106
  const isEmptyState = controller.activeThreadId === null && controller.messages.length === 0 && !hasBlockingError
107
+ const latestAnchorId = latestThreadAnchorId(controller.messages)
108
108
  const composerOverlayRef = React.useRef<HTMLDivElement>(null)
109
109
  const [composerOverlayHeight, setComposerOverlayHeight] = React.useState(192)
110
+ const [restoredDraft, setRestoredDraft] = React.useState<{ revision: number; text: string; files: AgentChatComposerFile[] } | null>(null)
111
+ const draftRevisionRef = React.useRef(0)
112
+ const retractForEdit = React.useCallback(async (message: ThreadMessage) => {
113
+ const draft = await controller.retractPendingMessage(message)
114
+ setRestoredDraft({ revision: ++draftRevisionRef.current, text: draft.text,
115
+ files: queuedMessageComposerFiles(draft),
116
+ })
117
+ }, [controller])
110
118
 
111
- React.useEffect(() => {
119
+ React.useLayoutEffect(() => {
112
120
  const overlay = composerOverlayRef.current
113
121
  if (!overlay || isEmptyState) return
114
122
 
@@ -159,47 +167,11 @@ export function ChatShell({
159
167
  data-agents24-chat-transcript=""
160
168
  >
161
169
  <OperationErrorNotice runtime={runtime} />
162
- {isEmptyState ? (
163
- <div className="absolute inset-0 flex min-h-0 flex-col justify-center pb-20">
164
- <div className="mx-auto mb-8 w-full max-w-3xl px-4 text-center sm:px-6">
165
- <h2 className="text-3xl font-semibold tracking-tight sm:text-4xl">Ready when you are.</h2>
166
- </div>
167
- <ChatComposer
168
- inputContract={runtime.inputContract || LOADING_INPUT_CONTRACT}
169
- contextWindow={runtime.contextWindow}
170
- disabled={runtime.isBootstrapping || runtime.isChangingAgent || isUploading}
171
- focusKey={controller.activeThreadId}
172
- forceExpanded
173
- isCancelling={isCancelling}
174
- isRunning={isRunning}
175
- agentId={runtime.agentId}
176
- agents={runtime.agents}
177
- onAgentChange={(agentId) => { void runtime.changeAgent(agentId) }}
178
- onStop={() => { void controller.cancelRun().catch(() => undefined) }}
179
- modelId={runtime.modelId}
180
- models={runtime.models}
181
- onModelChange={runtime.changeModel}
182
- onSubmit={(text, files) => runtime.submit({
183
- text,
184
- files: files.map((file) => ({
185
- data: file.source,
186
- mediaType: file.mediaType,
187
- name: file.filename,
188
- })),
189
- attachments: files,
190
- })}
191
- />
192
- </div>
193
- ) : (
194
- <>
195
- <MessageScrollerProvider
196
- autoScroll
197
- defaultScrollPosition="last-anchor"
198
- scrollPreviousItemPeek={64}
199
- >
200
- <MessageScroller className="relative min-h-0 flex-1">
201
- <MessageScrollerViewport preserveScrollOnPrepend>
202
- <MessageScrollerContent
170
+ <LatestThreadScroller.Provider autoScroll defaultScrollPosition="last-anchor" scrollPreviousItemPeek={64}>
171
+ {!isEmptyState && (
172
+ <LatestThreadScroller.Root className="relative min-h-0 flex-1">
173
+ <LatestThreadScroller.Viewport preserveScrollOnPrepend>
174
+ <LatestThreadScroller.Content
203
175
  className="mx-auto w-full max-w-3xl gap-3 px-4 pt-8 pb-0 sm:px-6 sm:pt-12"
204
176
  style={{ paddingBottom: composerOverlayHeight + 72 }}
205
177
  >
@@ -221,13 +193,14 @@ export function ChatShell({
221
193
  </div>
222
194
  )}
223
195
  {controller.messages.map((message) => (
224
- <MessageScrollerItem
196
+ <LatestThreadScroller.Item
225
197
  key={message.id}
226
198
  messageId={message.id}
227
- scrollAnchor={message.role === "user"}
199
+ scrollAnchor={isRunning && message.id === latestAnchorId}
228
200
  className="overflow-visible [content-visibility:visible]"
229
201
  >
230
202
  <ChatMessage
203
+ animateCompletion={wasLiveRun(message.runId)}
231
204
  allowFeedback={runtime.allowFeedback}
232
205
  getHitlActionState={runtime.getHitlActionState}
233
206
  isLoading={isScaffoldTimelineLoading({
@@ -243,29 +216,40 @@ export function ChatShell({
243
216
  resolveAttachmentContent={runtime.resolveAttachmentContent}
244
217
  Timeline={components?.Timeline}
245
218
  />
246
- </MessageScrollerItem>
219
+ </LatestThreadScroller.Item>
247
220
  ))}
248
- </MessageScrollerContent>
249
- </MessageScrollerViewport>
221
+ </LatestThreadScroller.Content>
222
+ </LatestThreadScroller.Viewport>
250
223
  <TurnOutline messages={controller.messages} />
251
- </MessageScroller>
224
+ </LatestThreadScroller.Root>
225
+ )}
252
226
 
253
227
  <div
254
228
  ref={composerOverlayRef}
255
- className="pointer-events-none absolute inset-x-0 bottom-0 z-30 overflow-visible pt-10"
256
- style={{
229
+ className={isEmptyState
230
+ ? "pointer-events-none absolute inset-0 z-30 flex flex-col justify-center overflow-visible pb-20"
231
+ : "pointer-events-none absolute inset-x-0 bottom-0 z-30 overflow-visible pt-10"}
232
+ style={isEmptyState ? undefined : {
257
233
  background: "linear-gradient(to bottom, transparent 0%, color-mix(in oklch, var(--background) 85%, transparent) 45%, var(--background) 65%, var(--background) 100%)",
258
234
  }}
259
235
  >
260
- <div className="pointer-events-auto absolute inset-x-0 top-0 flex -translate-y-full justify-center pb-3">
261
- <MessageScrollerButton className="!relative !inset-auto !translate-x-0 rtl:!translate-x-0" />
262
- </div>
263
- <div className="pointer-events-auto">
236
+ {isEmptyState ? <div className="mx-auto mb-8 w-full max-w-3xl px-4 text-center sm:px-6">
237
+ <h2 className="text-3xl font-semibold tracking-tight sm:text-4xl">Ready when you are.</h2>
238
+ </div> : <div className="pointer-events-auto absolute inset-x-0 top-0 flex -translate-y-full justify-center pb-3">
239
+ <LatestThreadScroller.Button className="!relative !inset-auto !translate-x-0 rtl:!translate-x-0" />
240
+ </div>}
241
+ <div className="pointer-events-auto mx-auto w-full max-w-3xl px-4 sm:px-6">
242
+ <AgentChatQueue pending={controller.pendingMessages} queue={controller.messageQueue}
243
+ canSteer={controller.runState === "streaming"}
244
+ onSteer={controller.steerPendingMessage} onRemove={controller.removePendingMessage}
245
+ onEdit={retractForEdit} onResume={controller.resumeQueue} />
264
246
  <ChatComposer
247
+ restoredDraft={restoredDraft}
265
248
  inputContract={runtime.inputContract || LOADING_INPUT_CONTRACT}
266
249
  contextWindow={runtime.contextWindow}
267
- disabled={runtime.isBootstrapping || runtime.isChangingAgent || hasBlockingError || isUploading}
250
+ disabled={runtime.isBootstrapping || runtime.isChangingAgent || isUploading || hasBlockingError}
268
251
  focusKey={controller.activeThreadId}
252
+ forceExpanded={isEmptyState}
269
253
  isCancelling={isCancelling}
270
254
  isRunning={isRunning}
271
255
  agentId={runtime.agentId}
@@ -277,20 +261,19 @@ export function ChatShell({
277
261
  onModelChange={runtime.changeModel}
278
262
  onSubmit={(text, files) => runtime.submit({
279
263
  text,
280
- files: files.map((file) => ({
281
- data: file.source,
264
+ files: files.filter((file) => file.source).map((file) => ({
265
+ data: file.source!,
282
266
  mediaType: file.mediaType,
283
267
  name: file.filename,
284
268
  })),
269
+ attachmentIds: files.flatMap((file) => file.persistedAttachmentId ? [file.persistedAttachmentId] : []),
285
270
  attachments: files,
286
271
  })}
287
- utilityRow="below"
272
+ utilityRow={isEmptyState ? "inline" : "below"}
288
273
  />
289
274
  </div>
290
275
  </div>
291
- </MessageScrollerProvider>
292
- </>
293
- )}
276
+ </LatestThreadScroller.Provider>
294
277
  </section>
295
278
  </SidebarInset>
296
279
  </SidebarProvider>
@@ -1,10 +0,0 @@
1
- import * as React from "react";
2
- import { MessageScroller as MessageScrollerPrimitive, useMessageScroller, useMessageScrollerScrollable, useMessageScrollerVisibility } from "@shadcn/react/message-scroller";
3
- import { Button } from "./button";
4
- declare function MessageScrollerProvider(props: React.ComponentProps<typeof MessageScrollerPrimitive.Provider>): import("react/jsx-runtime").JSX.Element;
5
- declare function MessageScroller({ className, ...props }: React.ComponentProps<typeof MessageScrollerPrimitive.Root>): import("react/jsx-runtime").JSX.Element;
6
- declare function MessageScrollerViewport({ className, ...props }: React.ComponentProps<typeof MessageScrollerPrimitive.Viewport>): import("react/jsx-runtime").JSX.Element;
7
- declare function MessageScrollerContent({ className, ...props }: React.ComponentProps<typeof MessageScrollerPrimitive.Content>): import("react/jsx-runtime").JSX.Element;
8
- declare function MessageScrollerItem({ className, scrollAnchor, ...props }: React.ComponentProps<typeof MessageScrollerPrimitive.Item>): import("react/jsx-runtime").JSX.Element;
9
- declare function MessageScrollerButton({ direction, className, children, render, variant, size, ...props }: React.ComponentProps<typeof MessageScrollerPrimitive.Button> & Pick<React.ComponentProps<typeof Button>, "variant" | "size">): import("react/jsx-runtime").JSX.Element;
10
- export { MessageScrollerProvider, MessageScroller, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem, MessageScrollerButton, useMessageScroller, useMessageScrollerScrollable, useMessageScrollerVisibility, };
@@ -1,129 +0,0 @@
1
- import * as React from "react"
2
- import {
3
- MessageScroller as MessageScrollerPrimitive,
4
- useMessageScroller,
5
- useMessageScrollerScrollable,
6
- useMessageScrollerVisibility,
7
- } from "@shadcn/react/message-scroller"
8
-
9
- import { cn } from "../../lib/utils"
10
- import { Button } from "./button"
11
- import { ArrowDownIcon } from "lucide-react"
12
-
13
- function MessageScrollerProvider(
14
- props: React.ComponentProps<typeof MessageScrollerPrimitive.Provider>
15
- ) {
16
- return <MessageScrollerPrimitive.Provider {...props} />
17
- }
18
-
19
- function MessageScroller({
20
- className,
21
- ...props
22
- }: React.ComponentProps<typeof MessageScrollerPrimitive.Root>) {
23
- return (
24
- <MessageScrollerPrimitive.Root
25
- data-slot="message-scroller"
26
- className={cn(
27
- "group/message-scroller relative flex size-full min-h-0 flex-col overflow-hidden",
28
- className
29
- )}
30
- {...props}
31
- />
32
- )
33
- }
34
-
35
- function MessageScrollerViewport({
36
- className,
37
- ...props
38
- }: React.ComponentProps<typeof MessageScrollerPrimitive.Viewport>) {
39
- return (
40
- <MessageScrollerPrimitive.Viewport
41
- data-slot="message-scroller-viewport"
42
- className={cn(
43
- "size-full min-h-0 min-w-0 scroll-fade-b overflow-y-auto overscroll-contain contain-content [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
44
- className
45
- )}
46
- {...props}
47
- />
48
- )
49
- }
50
-
51
- function MessageScrollerContent({
52
- className,
53
- ...props
54
- }: React.ComponentProps<typeof MessageScrollerPrimitive.Content>) {
55
- return (
56
- <MessageScrollerPrimitive.Content
57
- data-slot="message-scroller-content"
58
- className={cn("flex h-max min-h-full flex-col gap-3", className)}
59
- {...props}
60
- />
61
- )
62
- }
63
-
64
- function MessageScrollerItem({
65
- className,
66
- scrollAnchor = false,
67
- ...props
68
- }: React.ComponentProps<typeof MessageScrollerPrimitive.Item>) {
69
- return (
70
- <MessageScrollerPrimitive.Item
71
- data-slot="message-scroller-item"
72
- scrollAnchor={scrollAnchor}
73
- className={cn(
74
- "min-w-0 shrink-0 [contain-intrinsic-size:auto_10rem] [content-visibility:auto]",
75
- className
76
- )}
77
- {...props}
78
- />
79
- )
80
- }
81
-
82
- function MessageScrollerButton({
83
- direction = "end",
84
- className,
85
- children,
86
- render,
87
- variant = "secondary",
88
- size = "icon-sm",
89
- ...props
90
- }: React.ComponentProps<typeof MessageScrollerPrimitive.Button> &
91
- Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
92
- return (
93
- <MessageScrollerPrimitive.Button
94
- data-slot="message-scroller-button"
95
- data-direction={direction}
96
- data-variant={variant}
97
- data-size={size}
98
- direction={direction}
99
- className={cn(
100
- "absolute inset-s-1/2 -translate-x-1/2 border-border bg-background text-foreground transition-[translate,scale,opacity] duration-200 hover:bg-muted hover:text-foreground data-[active=false]:pointer-events-none data-[active=false]:scale-95 data-[active=false]:opacity-0 data-[active=false]:duration-400 data-[active=false]:ease-[cubic-bezier(0.7,0,0.84,0)] data-[active=true]:translate-y-0 data-[active=true]:scale-100 data-[active=true]:opacity-100 data-[active=true]:ease-[cubic-bezier(0.23,1,0.32,1)] data-[direction=end]:bottom-4 data-[direction=end]:data-[active=false]:translate-y-full data-[direction=start]:top-4 data-[direction=start]:data-[active=false]:-translate-y-full rtl:translate-x-1/2 data-[direction=start]:[&_svg]:rotate-180",
101
- className
102
- )}
103
- render={render ?? <Button variant={variant} size={size} />}
104
- {...props}
105
- >
106
- {children ?? (
107
- <>
108
- <ArrowDownIcon
109
- />
110
- <span className="sr-only">
111
- {direction === "end" ? "Scroll to end" : "Scroll to start"}
112
- </span>
113
- </>
114
- )}
115
- </MessageScrollerPrimitive.Button>
116
- )
117
- }
118
-
119
- export {
120
- MessageScrollerProvider,
121
- MessageScroller,
122
- MessageScrollerViewport,
123
- MessageScrollerContent,
124
- MessageScrollerItem,
125
- MessageScrollerButton,
126
- useMessageScroller,
127
- useMessageScrollerScrollable,
128
- useMessageScrollerVisibility,
129
- }