create-agents24-app 0.1.6 → 0.1.7
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.
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import { ArrowUp, ChevronDown, LoaderCircle, Mic, Square
|
|
1
|
+
import { ArrowUp, ChevronDown, LoaderCircle, Mic, Square } from "lucide-react"
|
|
2
2
|
import * as React from "react"
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
AgentChatContextStatus,
|
|
5
|
+
ChatAttachmentRows,
|
|
6
|
+
createAgentChatComposerFile,
|
|
7
|
+
revokeAgentChatComposerFiles,
|
|
8
|
+
useAudioRecorder,
|
|
9
|
+
type AgentChatComposerFile,
|
|
10
|
+
} from "@agents24/chat-react/ui"
|
|
4
11
|
import { canonicalInputMimeType, inputMimeTypes, validateAgentChatSubmission, type AgentChatInputContract } from "@agents24/chat-react/runtime"
|
|
5
12
|
import type { ContextWindow } from "@agents24/client/protocol"
|
|
6
13
|
|
|
@@ -29,7 +36,7 @@ type ChatComposerProps = {
|
|
|
29
36
|
onModelChange: (modelId: string) => void
|
|
30
37
|
onAgentChange: (agentId: string) => void
|
|
31
38
|
onStop: () => void
|
|
32
|
-
onSubmit: (text: string, files:
|
|
39
|
+
onSubmit: (text: string, files: AgentChatComposerFile[]) => Promise<void>
|
|
33
40
|
utilityRow?: "inline" | "below"
|
|
34
41
|
}
|
|
35
42
|
|
|
@@ -52,15 +59,24 @@ export function ChatComposer({
|
|
|
52
59
|
utilityRow = "inline",
|
|
53
60
|
}: ChatComposerProps) {
|
|
54
61
|
const [text, setText] = React.useState("")
|
|
55
|
-
const [files, setFiles] = React.useState<
|
|
62
|
+
const [files, setFiles] = React.useState<AgentChatComposerFile[]>([])
|
|
56
63
|
const [attachmentError, setAttachmentError] = React.useState<string | null>(null)
|
|
57
64
|
const inputRef = React.useRef<HTMLInputElement>(null)
|
|
58
65
|
const textareaRef = React.useRef<HTMLTextAreaElement>(null)
|
|
66
|
+
const filesRef = React.useRef<AgentChatComposerFile[]>([])
|
|
67
|
+
const inFlightFilesRef = React.useRef<AgentChatComposerFile[]>([])
|
|
59
68
|
const submittingRef = React.useRef(false)
|
|
60
69
|
const draftRevisionRef = React.useRef(0)
|
|
61
70
|
const attachmentMimeTypes = inputMimeTypes(inputContract)
|
|
62
71
|
const allowAttachments = attachmentMimeTypes.length > 0
|
|
63
72
|
const recordingAllowed = inputContract.modalities.audio.recording_enabled
|
|
73
|
+
React.useEffect(() => {
|
|
74
|
+
filesRef.current = files
|
|
75
|
+
}, [files])
|
|
76
|
+
React.useEffect(() => () => {
|
|
77
|
+
revokeAgentChatComposerFiles(filesRef.current)
|
|
78
|
+
revokeAgentChatComposerFiles(inFlightFilesRef.current)
|
|
79
|
+
}, [])
|
|
64
80
|
React.useEffect(() => {
|
|
65
81
|
if (disabled || isRunning) return
|
|
66
82
|
const frame = window.requestAnimationFrame(() => textareaRef.current?.focus({ preventScroll: true }))
|
|
@@ -68,14 +84,14 @@ export function ChatComposer({
|
|
|
68
84
|
}, [disabled, focusKey, isRunning])
|
|
69
85
|
const addRecording = React.useCallback((file: File) => {
|
|
70
86
|
draftRevisionRef.current += 1
|
|
71
|
-
setFiles((current) => [...current, file])
|
|
87
|
+
setFiles((current) => [...current, createAgentChatComposerFile(file)])
|
|
72
88
|
}, [])
|
|
73
89
|
const recorder = useAudioRecorder(addRecording)
|
|
74
90
|
const isExpanded = forceExpanded || text.includes("\n") || text.length > 62
|
|
75
91
|
const validationError = validateAgentChatSubmission(
|
|
76
92
|
inputContract,
|
|
77
93
|
text,
|
|
78
|
-
files.map((file) => ({ data: file, mediaType: file.
|
|
94
|
+
files.map((file) => ({ data: file.source, mediaType: file.mediaType })),
|
|
79
95
|
)
|
|
80
96
|
|
|
81
97
|
const selectedAgent = agents.find((item) => item.id === agentId)
|
|
@@ -143,16 +159,23 @@ export function ChatComposer({
|
|
|
143
159
|
const clearedRevision = draftRevisionRef.current + 1
|
|
144
160
|
draftRevisionRef.current = clearedRevision
|
|
145
161
|
submittingRef.current = true
|
|
162
|
+
inFlightFilesRef.current = selectedFiles
|
|
146
163
|
setText("")
|
|
147
164
|
setFiles([])
|
|
148
165
|
textareaRef.current?.focus()
|
|
149
166
|
try {
|
|
150
167
|
await onSubmit(value, selectedFiles)
|
|
168
|
+
revokeAgentChatComposerFiles(selectedFiles)
|
|
169
|
+
inFlightFilesRef.current = []
|
|
151
170
|
} catch {
|
|
152
171
|
if (draftRevisionRef.current === clearedRevision) {
|
|
153
172
|
draftRevisionRef.current += 1
|
|
154
173
|
setText(value)
|
|
155
174
|
setFiles(selectedFiles)
|
|
175
|
+
inFlightFilesRef.current = []
|
|
176
|
+
} else {
|
|
177
|
+
revokeAgentChatComposerFiles(selectedFiles)
|
|
178
|
+
inFlightFilesRef.current = []
|
|
156
179
|
}
|
|
157
180
|
} finally {
|
|
158
181
|
submittingRef.current = false
|
|
@@ -165,18 +188,19 @@ export function ChatComposer({
|
|
|
165
188
|
data-agents24-chat-composer=""
|
|
166
189
|
>
|
|
167
190
|
{allowAttachments && files.length > 0 && (
|
|
168
|
-
<div className="mb-2
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
191
|
+
<div className="mb-2 min-w-0">
|
|
192
|
+
<ChatAttachmentRows
|
|
193
|
+
attachments={files}
|
|
194
|
+
onRemove={(attachment) => {
|
|
195
|
+
if (!attachment.id) return
|
|
196
|
+
draftRevisionRef.current += 1
|
|
197
|
+
setFiles((current) => {
|
|
198
|
+
const removed = current.find((file) => file.id === attachment.id)
|
|
199
|
+
if (removed) revokeAgentChatComposerFiles([removed])
|
|
200
|
+
return current.filter((file) => file.id !== attachment.id)
|
|
201
|
+
})
|
|
202
|
+
}}
|
|
203
|
+
/>
|
|
180
204
|
</div>
|
|
181
205
|
)}
|
|
182
206
|
{attachmentError || recorder.error ? <p className="mb-2 text-xs text-destructive" role="alert">{attachmentError || recorder.error}</p> : null}
|
|
@@ -201,7 +225,7 @@ export function ChatComposer({
|
|
|
201
225
|
attachmentMimeTypes.includes(canonicalInputMimeType(file.type))
|
|
202
226
|
))
|
|
203
227
|
draftRevisionRef.current += 1
|
|
204
|
-
setFiles((current) => [...current, ...accepted])
|
|
228
|
+
setFiles((current) => [...current, ...accepted.map(createAgentChatComposerFile)])
|
|
205
229
|
setAttachmentError(
|
|
206
230
|
accepted.length === selected.length
|
|
207
231
|
? null
|
|
@@ -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,
|
|
4
|
+
import { AgentChatDefaultActions, ChatAttachmentRows, type ChatAttachmentContentResolver } from "@agents24/chat-react/ui"
|
|
5
5
|
import * as React from "react"
|
|
6
6
|
|
|
7
7
|
import { MessageParts } from "./message-parts"
|
|
@@ -20,6 +20,7 @@ type Props = {
|
|
|
20
20
|
input: { rating: "like" | "dislike" | null; reason?: FeedbackReason | null; comment?: string | null },
|
|
21
21
|
) => Promise<void>
|
|
22
22
|
Timeline?: React.ComponentType<Agents24ChatTimelineSlotProps>
|
|
23
|
+
resolveAttachmentContent?: ChatAttachmentContentResolver
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export function ChatMessage({
|
|
@@ -30,6 +31,7 @@ export function ChatMessage({
|
|
|
30
31
|
onHitlAction,
|
|
31
32
|
onRegenerate,
|
|
32
33
|
onSetFeedback,
|
|
34
|
+
resolveAttachmentContent,
|
|
33
35
|
Timeline,
|
|
34
36
|
}: Props) {
|
|
35
37
|
const isUser = message.role === "user"
|
|
@@ -37,14 +39,11 @@ export function ChatMessage({
|
|
|
37
39
|
<Message align={isUser ? "end" : "start"}>
|
|
38
40
|
<MessageContent>
|
|
39
41
|
{isUser && message.attachments?.length ? (
|
|
40
|
-
<
|
|
41
|
-
{message.attachments
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
/>
|
|
46
|
-
))}
|
|
47
|
-
</ChatAttachments>
|
|
42
|
+
<ChatAttachmentRows
|
|
43
|
+
attachments={message.attachments}
|
|
44
|
+
className="mb-2"
|
|
45
|
+
resolveContent={resolveAttachmentContent}
|
|
46
|
+
/>
|
|
48
47
|
) : null}
|
|
49
48
|
<Bubble variant={isUser ? "secondary" : "ghost"} align={isUser ? "end" : "start"} className={isUser ? "max-w-[min(75%,42rem)]" : "max-w-full overflow-visible"}>
|
|
50
49
|
<BubbleContent className={isUser ? "px-4 py-2.5" : "w-full overflow-visible"}>
|
|
@@ -100,7 +100,7 @@ export function ChatShell({
|
|
|
100
100
|
|| controller.runState === "reconnecting"
|
|
101
101
|
|| controller.runState === "paused"
|
|
102
102
|
|| controller.runState === "cancelling"
|
|
103
|
-
|
|
103
|
+
const isUploading = controller.runState === "uploading" || runtime.isUploading
|
|
104
104
|
const isCancelling = controller.runState === "cancelling"
|
|
105
105
|
const activeTitle = controller.threads.find((thread) => thread.id === controller.activeThreadId)?.title
|
|
106
106
|
const hasBlockingError = Boolean(runtime.fatalError && controller.messages.length === 0)
|
|
@@ -151,7 +151,7 @@ export function ChatShell({
|
|
|
151
151
|
<ChatComposer
|
|
152
152
|
inputContract={runtime.inputContract || LOADING_INPUT_CONTRACT}
|
|
153
153
|
contextWindow={runtime.contextWindow}
|
|
154
|
-
disabled={runtime.isBootstrapping || runtime.isChangingAgent}
|
|
154
|
+
disabled={runtime.isBootstrapping || runtime.isChangingAgent || isUploading}
|
|
155
155
|
focusKey={controller.activeThreadId}
|
|
156
156
|
forceExpanded
|
|
157
157
|
isCancelling={isCancelling}
|
|
@@ -166,10 +166,11 @@ export function ChatShell({
|
|
|
166
166
|
onSubmit={(text, files) => runtime.submit({
|
|
167
167
|
text,
|
|
168
168
|
files: files.map((file) => ({
|
|
169
|
-
data: file,
|
|
170
|
-
mediaType: file.
|
|
171
|
-
name: file.
|
|
169
|
+
data: file.source,
|
|
170
|
+
mediaType: file.mediaType,
|
|
171
|
+
name: file.filename,
|
|
172
172
|
})),
|
|
173
|
+
attachments: files,
|
|
173
174
|
})}
|
|
174
175
|
/>
|
|
175
176
|
</div>
|
|
@@ -222,6 +223,7 @@ export function ChatShell({
|
|
|
222
223
|
onHitlAction={runtime.onHitlAction}
|
|
223
224
|
onRegenerate={() => controller.regenerate(message)}
|
|
224
225
|
onSetFeedback={(input) => controller.setFeedback(message, input)}
|
|
226
|
+
resolveAttachmentContent={runtime.resolveAttachmentContent}
|
|
225
227
|
Timeline={components?.Timeline}
|
|
226
228
|
/>
|
|
227
229
|
</MessageScrollerItem>
|
|
@@ -236,7 +238,7 @@ export function ChatShell({
|
|
|
236
238
|
<ChatComposer
|
|
237
239
|
inputContract={runtime.inputContract || LOADING_INPUT_CONTRACT}
|
|
238
240
|
contextWindow={runtime.contextWindow}
|
|
239
|
-
disabled={runtime.isBootstrapping || runtime.isChangingAgent || hasBlockingError}
|
|
241
|
+
disabled={runtime.isBootstrapping || runtime.isChangingAgent || hasBlockingError || isUploading}
|
|
240
242
|
focusKey={controller.activeThreadId}
|
|
241
243
|
isCancelling={isCancelling}
|
|
242
244
|
isRunning={isRunning}
|
|
@@ -250,10 +252,11 @@ export function ChatShell({
|
|
|
250
252
|
onSubmit={(text, files) => runtime.submit({
|
|
251
253
|
text,
|
|
252
254
|
files: files.map((file) => ({
|
|
253
|
-
data: file,
|
|
254
|
-
mediaType: file.
|
|
255
|
-
name: file.
|
|
255
|
+
data: file.source,
|
|
256
|
+
mediaType: file.mediaType,
|
|
257
|
+
name: file.filename,
|
|
256
258
|
})),
|
|
259
|
+
attachments: files,
|
|
257
260
|
})}
|
|
258
261
|
utilityRow="below"
|
|
259
262
|
/>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"tailwindcss": "^4.1.16"
|
|
21
21
|
},
|
|
22
22
|
"source": "packages/agents24-chat-react/scaffold",
|
|
23
|
-
"sha256": "
|
|
23
|
+
"sha256": "05a8b9cb10207bbe4cdf23d40196c020d1c542f2b071edfc618bc8993fa26a00",
|
|
24
24
|
"files": [
|
|
25
25
|
"components.json",
|
|
26
26
|
"manifest.json",
|