create-agents24-app 0.1.0 → 0.1.2

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 (75) hide show
  1. package/README.md +40 -2
  2. package/dist/{chunk-OCIF3LSI.js → chunk-PXJTBSPX.js} +99 -23
  3. package/dist/chunk-PXJTBSPX.js.map +1 -0
  4. package/dist/cli.js +39 -61
  5. package/dist/cli.js.map +1 -1
  6. package/dist/index.d.ts +6 -7
  7. package/dist/index.js +9 -1
  8. package/dist/scaffold/components.json +25 -0
  9. package/dist/scaffold/manifest.json +28 -0
  10. package/dist/scaffold/src/app.tsx +71 -0
  11. package/dist/scaffold/src/chat.css +54 -0
  12. package/dist/scaffold/src/components/chat/chat-composer.tsx +237 -0
  13. package/dist/scaffold/src/components/chat/chat-message.tsx +76 -0
  14. package/dist/scaffold/src/components/chat/chat-shell.tsx +257 -0
  15. package/dist/scaffold/src/components/chat/collapsible-user-message.tsx +37 -0
  16. package/dist/scaffold/src/components/chat/external-link-dialog.tsx +48 -0
  17. package/dist/scaffold/src/components/chat/identity-control.tsx +69 -0
  18. package/dist/scaffold/src/components/chat/message-parts.tsx +83 -0
  19. package/dist/scaffold/src/components/chat/theme-menu.tsx +79 -0
  20. package/dist/scaffold/src/components/chat/thread-sidebar.tsx +114 -0
  21. package/dist/scaffold/src/components/chat/timeline-loading.ts +17 -0
  22. package/dist/scaffold/src/components/chat/turn-outline.tsx +73 -0
  23. package/dist/scaffold/src/components/theme-provider.tsx +91 -0
  24. package/dist/scaffold/src/components/ui/attachment.tsx +204 -0
  25. package/dist/scaffold/src/components/ui/bubble.tsx +125 -0
  26. package/dist/scaffold/src/components/ui/button-group.tsx +83 -0
  27. package/dist/scaffold/src/components/ui/button.tsx +65 -0
  28. package/dist/scaffold/src/components/ui/dialog.tsx +171 -0
  29. package/dist/scaffold/src/components/ui/dropdown-menu.tsx +271 -0
  30. package/dist/scaffold/src/components/ui/input.tsx +19 -0
  31. package/dist/scaffold/src/components/ui/marker.tsx +69 -0
  32. package/dist/scaffold/src/components/ui/message-scroller.tsx +129 -0
  33. package/dist/scaffold/src/components/ui/message.tsx +92 -0
  34. package/dist/scaffold/src/components/ui/popover.tsx +40 -0
  35. package/dist/scaffold/src/components/ui/select.tsx +92 -0
  36. package/dist/scaffold/src/components/ui/separator.tsx +28 -0
  37. package/dist/scaffold/src/components/ui/sheet.tsx +150 -0
  38. package/dist/scaffold/src/components/ui/sidebar.tsx +677 -0
  39. package/dist/scaffold/src/components/ui/skeleton.tsx +13 -0
  40. package/dist/scaffold/src/components/ui/textarea.tsx +18 -0
  41. package/dist/scaffold/src/components/ui/tooltip.tsx +56 -0
  42. package/dist/scaffold/src/hooks/use-mobile.ts +24 -0
  43. package/dist/scaffold/src/identity.ts +17 -0
  44. package/dist/scaffold/src/index.css +221 -0
  45. package/dist/scaffold/src/index.tsx +39 -0
  46. package/dist/scaffold/src/lib/utils.ts +6 -0
  47. package/dist/scaffold/styles.css +3 -0
  48. package/dist/scaffold-manifest.json +66 -0
  49. package/package.json +7 -3
  50. package/templates/common/README.md +16 -3
  51. package/templates/common/index.html +3 -3
  52. package/templates/common/scripts/apply-preset.mjs +17 -0
  53. package/templates/common/scripts/scope-preset-theme.mjs +38 -0
  54. package/templates/common/src/styles.css +10 -6
  55. package/templates/common/src/use-thread-query-navigation.ts +41 -0
  56. package/templates/common/tsconfig.json +2 -1
  57. package/templates/integrations/bff/server/env.ts +3 -3
  58. package/templates/integrations/bff/server/index.ts +1 -2
  59. package/templates/integrations/bff/server/principal.ts +3 -3
  60. package/templates/integrations/bff/src/host-identity.ts +6 -0
  61. package/templates/integrations/bff/src/main.tsx +21 -16
  62. package/templates/integrations/bff/tsconfig.json +2 -1
  63. package/templates/integrations/bff/vite.config.ts +5 -1
  64. package/templates/integrations/client-deployment/src/main.tsx +26 -18
  65. package/templates/integrations/client-deployment/vite.config.ts +37 -4
  66. package/templates/starters/rag/agents24/agents/assistant.md +11 -0
  67. package/templates/starters/rag/agents24/agents24.yaml +19 -0
  68. package/templates/starters/rag/agents24/rag/knowledge.yaml +32 -0
  69. package/templates/starters/rag/agents24/stores/knowledge.yaml +10 -0
  70. package/templates/starters/rag/agents24/workflows/main.yaml +16 -0
  71. package/versions.json +4 -4
  72. package/dist/chunk-OCIF3LSI.js.map +0 -1
  73. package/templates/themes/essential/src/theme.css +0 -2
  74. package/templates/themes/flow/src/theme.css +0 -60
  75. package/templates/themes/workbench/src/theme.css +0 -51
package/dist/index.d.ts CHANGED
@@ -1,22 +1,21 @@
1
1
  declare const INTEGRATIONS: readonly ["client-deployment", "bff"];
2
- declare const THEMES: readonly ["essential", "workbench", "flow"];
3
2
  declare const PACKAGE_MANAGERS: readonly ["pnpm", "npm", "yarn", "bun"];
3
+ declare const STARTERS: readonly ["rag"];
4
+ declare const DEFAULT_PRESET = "b2D1FHzlY";
4
5
  type Integration = (typeof INTEGRATIONS)[number];
5
- type Theme = (typeof THEMES)[number];
6
6
  type PackageManager = (typeof PACKAGE_MANAGERS)[number];
7
+ type Starter = (typeof STARTERS)[number];
7
8
  type CreateAppOptions = {
8
9
  directory: string;
9
10
  integration: Integration;
10
- theme: Theme;
11
+ preset: string;
11
12
  packageManager: PackageManager;
12
13
  install: boolean;
13
14
  baseUrl: string;
15
+ starter?: Starter;
14
16
  deploymentId?: string;
15
17
  agentId?: string;
16
18
  agentVersionId?: string;
17
- organizationId?: string;
18
- projectId?: string;
19
- apiKey?: string;
20
19
  };
21
20
  type GeneratedApp = {
22
21
  directory: string;
@@ -26,4 +25,4 @@ type GeneratedApp = {
26
25
  declare function installDependencies(directory: string, packageManager: PackageManager): void;
27
26
  declare function createApp(options: CreateAppOptions): GeneratedApp;
28
27
 
29
- export { type CreateAppOptions, type GeneratedApp, type Integration, type PackageManager, type Theme, createApp, installDependencies };
28
+ export { type CreateAppOptions, DEFAULT_PRESET, type GeneratedApp, INTEGRATIONS, type Integration, PACKAGE_MANAGERS, type PackageManager, STARTERS, type Starter, createApp, installDependencies };
package/dist/index.js CHANGED
@@ -1,9 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ DEFAULT_PRESET,
4
+ INTEGRATIONS,
5
+ PACKAGE_MANAGERS,
6
+ STARTERS,
3
7
  createApp,
4
8
  installDependencies
5
- } from "./chunk-OCIF3LSI.js";
9
+ } from "./chunk-PXJTBSPX.js";
6
10
  export {
11
+ DEFAULT_PRESET,
12
+ INTEGRATIONS,
13
+ PACKAGE_MANAGERS,
14
+ STARTERS,
7
15
  createApp,
8
16
  installDependencies
9
17
  };
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "radix-luma",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/index.css",
9
+ "baseColor": "stone",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "rtl": false,
15
+ "aliases": {
16
+ "components": "@/components",
17
+ "utils": "@/lib/utils",
18
+ "ui": "@/components/ui",
19
+ "lib": "@/lib",
20
+ "hooks": "@/hooks"
21
+ },
22
+ "menuColor": "default",
23
+ "menuAccent": "subtle",
24
+ "registries": {}
25
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "schema_version": "agents24.chat-scaffold.v1",
3
+ "name": "agents24-agent-chat",
4
+ "default_preset": "b2D1FHzlY",
5
+ "hosted_themes": {
6
+ "soft": "b2D1FHzlY",
7
+ "sharp": "b4XdOjWBjV"
8
+ },
9
+ "description": "Canonical Shadcn source for Agents24 hosted and generated chat applications.",
10
+ "dependencies": {
11
+ "@fontsource-variable/inter": "^5.3.0",
12
+ "@fontsource-variable/jetbrains-mono": "^5.3.0",
13
+ "@fontsource-variable/merriweather": "^5.3.0",
14
+ "@shadcn/react": "^0.2.1",
15
+ "class-variance-authority": "^0.7.1",
16
+ "clsx": "^2.1.1",
17
+ "lucide-react": "0.542.0",
18
+ "radix-ui": "1.4.3",
19
+ "streamdown": "^2.5.0",
20
+ "tailwind-merge": "^3.6.0",
21
+ "tw-animate-css": "^1.4.0"
22
+ },
23
+ "devDependencies": {
24
+ "@tailwindcss/vite": "^4.1.16",
25
+ "shadcn": "4.13.1",
26
+ "tailwindcss": "^4.1.16"
27
+ }
28
+ }
@@ -0,0 +1,71 @@
1
+ import type { AgentClient, HitlResponse } from "@agents24/client"
2
+ import {
3
+ type AgentChatActiveThreadChangeHandler,
4
+ type AgentChatMcpAuthorizationCompletion,
5
+ type AgentChatRuntimeAgent,
6
+ useAgentChatRuntime,
7
+ } from "@agents24/chat-react/runtime"
8
+ import type { HitlActionState } from "@agents24/chat-react/ui"
9
+ import {
10
+ Agents24Provider,
11
+ type ChatHitlPart,
12
+ type ChatMessage,
13
+ type ChatPart,
14
+ } from "@agents24/react"
15
+ import * as React from "react"
16
+
17
+ import { ChatShell } from "./components/chat/chat-shell"
18
+ import { GUEST_CHAT_IDENTITY, type Agents24ChatIdentity } from "./identity"
19
+
20
+ const createBrowserAbortController = () => new AbortController()
21
+
22
+ export type Agents24ChatAppProps = {
23
+ activeThreadId?: string | null
24
+ agent?: AgentChatRuntimeAgent
25
+ client: AgentClient
26
+ completeMcpAuthorization?: AgentChatMcpAuthorizationCompletion
27
+ mcpRedirectUri?: string
28
+ modelId?: string | null
29
+ onActiveThreadIdChange?: AgentChatActiveThreadChangeHandler
30
+ onModelChange?: (modelId: string) => void
31
+ identity?: Agents24ChatIdentity
32
+ components?: Agents24ChatScaffoldComponents
33
+ }
34
+
35
+ export type Agents24ChatScaffoldComponents = {
36
+ Timeline?: React.ComponentType<Agents24ChatTimelineSlotProps>
37
+ }
38
+
39
+ export type ChatHitlResponse = HitlResponse
40
+
41
+ export type Agents24ChatTimelineSlotProps = {
42
+ message: ChatMessage
43
+ parts: readonly ChatPart[]
44
+ isLoading: boolean
45
+ getHitlActionState: (part: ChatHitlPart) => HitlActionState | undefined
46
+ onHitlAction: (part: ChatHitlPart, response: ChatHitlResponse) => void
47
+ }
48
+
49
+ function RuntimeChat({
50
+ components,
51
+ identity = GUEST_CHAT_IDENTITY,
52
+ ...props
53
+ }: Agents24ChatAppProps) {
54
+ const runtime = useAgentChatRuntime({
55
+ ...props,
56
+ mcpRedirectUri: props.mcpRedirectUri || (
57
+ typeof window === "undefined" ? undefined : window.location.href
58
+ ),
59
+ })
60
+
61
+ return <ChatShell components={components} identity={identity} runtime={runtime} />
62
+ }
63
+
64
+ export function App(props: Agents24ChatAppProps) {
65
+ const identity = props.identity || GUEST_CHAT_IDENTITY
66
+ return (
67
+ <Agents24Provider client={props.client} createAbortController={createBrowserAbortController}>
68
+ <RuntimeChat key={identity.state} {...props} />
69
+ </Agents24Provider>
70
+ )
71
+ }
@@ -0,0 +1,54 @@
1
+ [data-agents24-theme-root] {
2
+ background: var(--background);
3
+ color: var(--foreground);
4
+ font-family: var(--font-sans);
5
+ height: 100%;
6
+ overflow: hidden;
7
+ width: 100%;
8
+ }
9
+
10
+ [data-agents24-theme-root],
11
+ [data-agents24-theme-root] * {
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ [data-agents24-theme-root] .a24-message-response {
16
+ max-width: 100%;
17
+ min-width: 0;
18
+ padding-inline: 3px;
19
+ width: 100%;
20
+ }
21
+
22
+ [data-agents24-theme-root] .a24-message-response > :first-child { margin-top: 0; }
23
+ [data-agents24-theme-root] .a24-message-response > :last-child { margin-bottom: 0; }
24
+ [data-agents24-theme-root] .a24-message-response :is(pre, table) {
25
+ max-width: 100%;
26
+ overflow-x: auto;
27
+ }
28
+ [data-agents24-theme-root] .a24-message-response :is(p, li, blockquote, code) {
29
+ overflow-wrap: anywhere;
30
+ }
31
+ /* CSP-safe structural layout. Presets may change tokens, not these relationships. */
32
+ .agents24-composer-grid {
33
+ grid-template-areas: "attach textarea submit";
34
+ grid-template-columns: auto minmax(0, 1fr) auto;
35
+ }
36
+
37
+ .agents24-composer-grid[data-expanded] {
38
+ grid-template-areas: "textarea textarea textarea" "attach spacer submit";
39
+ }
40
+
41
+ [data-agents24-theme-root] .agents24-composer-grid:not([data-expanded]) {
42
+ border-radius: var(--a24-composer-single-row-radius);
43
+ }
44
+
45
+ .agents24-composer-attach { grid-area: attach; }
46
+ .agents24-composer-textarea { grid-area: textarea; }
47
+ .agents24-composer-actions { grid-area: submit; }
48
+
49
+ .agents24-user-message-collapsed {
50
+ -webkit-box-orient: vertical;
51
+ -webkit-line-clamp: 4;
52
+ display: -webkit-box;
53
+ overflow: hidden;
54
+ }
@@ -0,0 +1,237 @@
1
+ import { ArrowUp, LoaderCircle, Mic, Square, X } from "lucide-react"
2
+ import * as React from "react"
3
+ import { AgentChatContextStatus, useAudioRecorder } from "@agents24/chat-react/ui"
4
+ import { canonicalInputMimeType, inputMimeTypes, validateAgentChatSubmission, type AgentChatInputContract } from "@agents24/chat-react/runtime"
5
+ import type { ContextWindow } from "@agents24/client/protocol"
6
+
7
+ import { Button } from "../ui/button"
8
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
9
+ import { Textarea } from "../ui/textarea"
10
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"
11
+ import { cn } from "../../lib/utils"
12
+
13
+ type ChatComposerProps = {
14
+ inputContract: AgentChatInputContract
15
+ disabled?: boolean
16
+ contextWindow?: ContextWindow | null
17
+ forceExpanded?: boolean
18
+ isCancelling?: boolean
19
+ isRunning: boolean
20
+ modelId: string | null
21
+ models: Array<{ id: string; label: string }>
22
+ onModelChange: (modelId: string) => void
23
+ onStop: () => void
24
+ onSubmit: (text: string, files: File[]) => Promise<void>
25
+ utilityRow?: "inline" | "below"
26
+ }
27
+
28
+ export function ChatComposer({
29
+ inputContract,
30
+ disabled,
31
+ contextWindow,
32
+ forceExpanded = false,
33
+ isCancelling = false,
34
+ isRunning,
35
+ modelId,
36
+ models,
37
+ onModelChange,
38
+ onStop,
39
+ onSubmit,
40
+ utilityRow = "inline",
41
+ }: ChatComposerProps) {
42
+ const [text, setText] = React.useState("")
43
+ const [files, setFiles] = React.useState<File[]>([])
44
+ const [attachmentError, setAttachmentError] = React.useState<string | null>(null)
45
+ const inputRef = React.useRef<HTMLInputElement>(null)
46
+ const textareaRef = React.useRef<HTMLTextAreaElement>(null)
47
+ const submittingRef = React.useRef(false)
48
+ const draftRevisionRef = React.useRef(0)
49
+ const attachmentMimeTypes = inputMimeTypes(inputContract)
50
+ const allowAttachments = attachmentMimeTypes.length > 0
51
+ const recordingAllowed = inputContract.modalities.audio.recording_enabled
52
+ const addRecording = React.useCallback((file: File) => {
53
+ draftRevisionRef.current += 1
54
+ setFiles((current) => [...current, file])
55
+ }, [])
56
+ const recorder = useAudioRecorder(addRecording)
57
+ const isExpanded = forceExpanded || text.includes("\n") || text.length > 62
58
+ const validationError = validateAgentChatSubmission(
59
+ inputContract,
60
+ text,
61
+ files.map((file) => ({ data: file, mediaType: file.type })),
62
+ )
63
+
64
+ const modelSelector = models.length > 0 ? (
65
+ <div className="w-fit max-w-40">
66
+ <Select
67
+ disabled={disabled || isRunning}
68
+ onValueChange={onModelChange}
69
+ value={modelId || undefined}
70
+ >
71
+ <SelectTrigger aria-label="Runtime model">
72
+ <SelectValue placeholder="Model" />
73
+ </SelectTrigger>
74
+ <SelectContent align="start">
75
+ {models.map((model) => <SelectItem key={model.id} value={model.id}>{model.label}</SelectItem>)}
76
+ </SelectContent>
77
+ </Select>
78
+ </div>
79
+ ) : null
80
+ const hasContextWindow = Number.isFinite(Number(contextWindow?.max_tokens)) && Number(contextWindow?.max_tokens) > 0
81
+ const showUtilityRow = utilityRow === "below"
82
+
83
+ const submit = async () => {
84
+ if (disabled || isRunning || submittingRef.current || validationError) return
85
+ const value = text
86
+ const selectedFiles = files
87
+ const clearedRevision = draftRevisionRef.current + 1
88
+ draftRevisionRef.current = clearedRevision
89
+ submittingRef.current = true
90
+ setText("")
91
+ setFiles([])
92
+ textareaRef.current?.focus()
93
+ try {
94
+ await onSubmit(value, selectedFiles)
95
+ } catch {
96
+ if (draftRevisionRef.current === clearedRevision) {
97
+ draftRevisionRef.current += 1
98
+ setText(value)
99
+ setFiles(selectedFiles)
100
+ }
101
+ } finally {
102
+ submittingRef.current = false
103
+ }
104
+ }
105
+
106
+ return (
107
+ <div
108
+ className="mx-auto w-full max-w-3xl px-4 pb-[max(1rem,env(safe-area-inset-bottom))] sm:px-6"
109
+ data-agents24-chat-composer=""
110
+ >
111
+ {allowAttachments && files.length > 0 && (
112
+ <div className="mb-2 flex min-w-0 gap-2 overflow-x-auto">
113
+ {files.map((file, index) => (
114
+ <div key={`${file.name}-${index}`} className="flex max-w-48 items-center gap-2 border bg-card px-2 py-1 text-xs">
115
+ <span className="truncate">{file.name}</span>
116
+ <Button variant="ghost" size="icon-xs" aria-label={`Remove ${file.name}`} onClick={() => {
117
+ draftRevisionRef.current += 1
118
+ setFiles((current) => current.filter((_, itemIndex) => itemIndex !== index))
119
+ }}>
120
+ <X />
121
+ </Button>
122
+ </div>
123
+ ))}
124
+ </div>
125
+ )}
126
+ {attachmentError || recorder.error ? <p className="mb-2 text-xs text-destructive" role="alert">{attachmentError || recorder.error}</p> : null}
127
+ <div
128
+ data-expanded={isExpanded || undefined}
129
+ className={cn(
130
+ "agents24-composer-grid grid min-w-0 items-center gap-2 rounded-xl border bg-background shadow-sm",
131
+ isExpanded ? "p-2" : "p-1.5",
132
+ )}
133
+ >
134
+ {allowAttachments ? (
135
+ <>
136
+ <input
137
+ ref={inputRef}
138
+ className="sr-only"
139
+ type="file"
140
+ multiple
141
+ accept={attachmentMimeTypes.join(",")}
142
+ onChange={(event) => {
143
+ const selected = Array.from(event.target.files ?? [])
144
+ const accepted = selected.filter((file) => (
145
+ attachmentMimeTypes.includes(canonicalInputMimeType(file.type))
146
+ ))
147
+ draftRevisionRef.current += 1
148
+ setFiles((current) => [...current, ...accepted])
149
+ setAttachmentError(
150
+ accepted.length === selected.length
151
+ ? null
152
+ : "One or more selected file types are not supported by this Agent.",
153
+ )
154
+ event.currentTarget.value = ""
155
+ }}
156
+ />
157
+ <div className="agents24-composer-attach flex min-w-0 items-center">
158
+ <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()}>
159
+ <span aria-hidden>+</span>
160
+ </Button>
161
+ </div>
162
+ </>
163
+ ) : <span aria-hidden="true" className="agents24-composer-attach" />}
164
+ <Textarea
165
+ ref={textareaRef}
166
+ value={text}
167
+ rows={1}
168
+ placeholder="Message the agent"
169
+ aria-label="Message"
170
+ disabled={disabled}
171
+ className={cn(
172
+ "agents24-composer-textarea max-h-56 min-h-9 field-sizing-content resize-none overflow-y-auto border-0 bg-transparent px-2 shadow-none focus-visible:ring-0 dark:bg-transparent",
173
+ isExpanded ? "py-2" : "h-9 py-2",
174
+ )}
175
+ onChange={(event) => {
176
+ draftRevisionRef.current += 1
177
+ setText(event.target.value)
178
+ }}
179
+ onKeyDown={(event) => {
180
+ if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
181
+ event.preventDefault()
182
+ void submit()
183
+ }
184
+ }}
185
+ />
186
+ <div className="agents24-composer-actions flex items-center gap-1">
187
+ {utilityRow === "inline" ? <AgentChatContextStatus contextWindow={contextWindow} /> : null}
188
+ {utilityRow === "inline" ? modelSelector : null}
189
+ {recordingAllowed ? (
190
+ <Button
191
+ variant={recorder.isRecording ? "default" : "ghost"}
192
+ size="icon"
193
+ aria-label={recorder.isRecording ? "Stop recording" : "Record audio"}
194
+ disabled={disabled || isRunning}
195
+ onClick={() => { if (recorder.isRecording) recorder.stop(); else void recorder.start() }}
196
+ >
197
+ {recorder.isRecording ? <Square className="fill-current" /> : <Mic />}
198
+ </Button>
199
+ ) : null}
200
+ {isRunning ? (
201
+ <Button
202
+ size="icon"
203
+ aria-label={isCancelling ? "Cancelling response" : "Stop response"}
204
+ disabled={disabled || isCancelling}
205
+ onClick={onStop}
206
+ >
207
+ {isCancelling
208
+ ? <LoaderCircle className="animate-spin" />
209
+ : <Square className="fill-current" />}
210
+ </Button>
211
+ ) : (
212
+ validationError ? (
213
+ <Tooltip tabIndex={0}>
214
+ <TooltipTrigger asChild>
215
+ <Button size="icon" aria-label="Send message" disabled onClick={() => void submit()}><ArrowUp /></Button>
216
+ </TooltipTrigger>
217
+ <TooltipContent>{validationError}</TooltipContent>
218
+ </Tooltip>
219
+ ) : (
220
+ <Button size="icon" aria-label="Send message" disabled={disabled} onClick={() => void submit()}><ArrowUp /></Button>
221
+ )
222
+ )}
223
+ </div>
224
+ </div>
225
+ {showUtilityRow ? (
226
+ <div
227
+ aria-hidden={!modelSelector && !hasContextWindow ? "true" : undefined}
228
+ className="mt-2 flex min-h-8 items-center justify-between gap-2 px-1"
229
+ data-agents24-chat-composer-utility=""
230
+ >
231
+ <div className="min-w-0">{modelSelector}</div>
232
+ <AgentChatContextStatus contextWindow={contextWindow} />
233
+ </div>
234
+ ) : null}
235
+ </div>
236
+ )
237
+ }
@@ -0,0 +1,76 @@
1
+ import type { ChatHitlPart, ChatMessage as ChatMessageModel } from "@agents24/react"
2
+ import type { FeedbackReason, HitlResponse } from "@agents24/client"
3
+ import type { AgentChatRuntimeHitlActionState } from "@agents24/chat-react/runtime"
4
+ import { AgentChatDefaultActions, ChatAttachment, ChatAttachments } from "@agents24/chat-react/ui"
5
+ import * as React from "react"
6
+
7
+ import { MessageParts } from "./message-parts"
8
+ import { Bubble, BubbleContent } from "../ui/bubble"
9
+ import { Message, MessageContent, MessageFooter } from "../ui/message"
10
+ import type { Agents24ChatTimelineSlotProps } from "../../app"
11
+
12
+ type Props = {
13
+ message: ChatMessageModel
14
+ getHitlActionState: (part: ChatHitlPart) => AgentChatRuntimeHitlActionState | undefined
15
+ isLoading: boolean
16
+ onHitlAction: (part: ChatHitlPart, response: HitlResponse) => Promise<void>
17
+ onRegenerate: () => Promise<void>
18
+ allowFeedback: boolean
19
+ onSetFeedback: (
20
+ input: { rating: "like" | "dislike" | null; reason?: FeedbackReason | null; comment?: string | null },
21
+ ) => Promise<void>
22
+ Timeline?: React.ComponentType<Agents24ChatTimelineSlotProps>
23
+ }
24
+
25
+ export function ChatMessage({
26
+ allowFeedback,
27
+ getHitlActionState,
28
+ isLoading,
29
+ message,
30
+ onHitlAction,
31
+ onRegenerate,
32
+ onSetFeedback,
33
+ Timeline,
34
+ }: Props) {
35
+ const isUser = message.role === "user"
36
+ return (
37
+ <Message align={isUser ? "end" : "start"}>
38
+ <MessageContent>
39
+ {isUser && message.attachments?.length ? (
40
+ <ChatAttachments className="mb-2 justify-end">
41
+ {message.attachments.map((attachment, index) => (
42
+ <ChatAttachment
43
+ data={attachment}
44
+ key={String(attachment.id || attachment.url || attachment.filename || index)}
45
+ />
46
+ ))}
47
+ </ChatAttachments>
48
+ ) : null}
49
+ <Bubble variant={isUser ? "secondary" : "ghost"} align={isUser ? "end" : "start"} className={isUser ? "max-w-[min(75%,42rem)]" : "max-w-full overflow-visible"}>
50
+ <BubbleContent className={isUser ? "px-4 py-2.5" : "w-full overflow-visible"}>
51
+ <MessageParts
52
+ getHitlActionState={getHitlActionState}
53
+ isLoading={isLoading}
54
+ message={message}
55
+ onHitlAction={onHitlAction}
56
+ Timeline={Timeline}
57
+ />
58
+ </BubbleContent>
59
+ </Bubble>
60
+ {!isUser && message.isFinal !== false && (
61
+ <MessageFooter className="gap-1 opacity-70 transition-opacity group-hover/message:opacity-100">
62
+ <AgentChatDefaultActions
63
+ handlers={{
64
+ feedback: message.feedback,
65
+ onCopy: (content) => { void navigator.clipboard.writeText(content).catch(() => undefined) },
66
+ onRetry: () => onRegenerate(),
67
+ onSetFeedback: allowFeedback ? (_message, input) => onSetFeedback(input) : undefined,
68
+ }}
69
+ message={message}
70
+ />
71
+ </MessageFooter>
72
+ )}
73
+ </MessageContent>
74
+ </Message>
75
+ )
76
+ }