@agents24/chat-react 0.5.0 → 0.5.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 (133) hide show
  1. package/README.md +90 -21
  2. package/compatibility.json +10 -10
  3. package/dist/chunk-SSUO4EPD.js +189 -0
  4. package/dist/chunk-SSUO4EPD.js.map +1 -0
  5. package/dist/chunk-ZCNHLFRB.js +108 -0
  6. package/dist/chunk-ZCNHLFRB.js.map +1 -0
  7. package/dist/index.cjs +112 -95
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +0 -1
  10. package/dist/index.js +362 -68
  11. package/dist/index.js.map +1 -1
  12. package/dist/mcp-oauth.d.ts +13 -0
  13. package/dist/runtime/index.cjs +594 -0
  14. package/dist/runtime/index.cjs.map +1 -0
  15. package/dist/runtime/index.d.ts +2 -0
  16. package/dist/runtime/index.js +458 -0
  17. package/dist/runtime/index.js.map +1 -0
  18. package/dist/runtime/input-contract.d.ts +20 -0
  19. package/dist/runtime/use-agent-chat-runtime.d.ts +83 -0
  20. package/dist/scaffold/app.d.ts +30 -0
  21. package/dist/scaffold/components/chat/chat-composer.d.ts +21 -0
  22. package/dist/scaffold/components/chat/chat-message.d.ts +21 -0
  23. package/dist/scaffold/components/chat/chat-shell.d.ts +8 -0
  24. package/dist/scaffold/components/chat/collapsible-user-message.d.ts +3 -0
  25. package/dist/scaffold/components/chat/external-link-dialog.d.ts +2 -0
  26. package/dist/scaffold/components/chat/identity-control.d.ts +4 -0
  27. package/dist/scaffold/components/chat/message-parts.d.ts +12 -0
  28. package/dist/scaffold/components/chat/theme-menu.d.ts +1 -0
  29. package/dist/scaffold/components/chat/thread-sidebar.d.ts +16 -0
  30. package/dist/scaffold/components/chat/timeline-loading.d.ts +7 -0
  31. package/dist/scaffold/components/chat/turn-outline.d.ts +4 -0
  32. package/dist/scaffold/components/theme-provider.d.ts +20 -0
  33. package/dist/scaffold/components/ui/attachment.d.ts +24 -0
  34. package/dist/scaffold/components/ui/bubble.d.ts +17 -0
  35. package/dist/scaffold/components/ui/button-group.d.ts +11 -0
  36. package/dist/scaffold/components/ui/button.d.ts +10 -0
  37. package/dist/scaffold/components/ui/dialog.d.ts +17 -0
  38. package/dist/scaffold/components/ui/dropdown-menu.d.ts +29 -0
  39. package/dist/scaffold/components/ui/input.d.ts +3 -0
  40. package/dist/scaffold/components/ui/marker.d.ts +11 -0
  41. package/dist/scaffold/components/ui/message-scroller.d.ts +10 -0
  42. package/dist/scaffold/components/ui/message.d.ts +10 -0
  43. package/dist/scaffold/components/ui/popover.d.ts +6 -0
  44. package/dist/scaffold/components/ui/select.d.ts +8 -0
  45. package/dist/scaffold/components/ui/separator.d.ts +4 -0
  46. package/dist/scaffold/components/ui/sheet.d.ts +14 -0
  47. package/dist/scaffold/components/ui/sidebar.d.ts +69 -0
  48. package/dist/scaffold/components/ui/skeleton.d.ts +2 -0
  49. package/dist/scaffold/components/ui/textarea.d.ts +3 -0
  50. package/dist/scaffold/components/ui/tooltip.d.ts +15 -0
  51. package/dist/scaffold/hooks/use-mobile.d.ts +1 -0
  52. package/dist/scaffold/identity.d.ts +10 -0
  53. package/dist/scaffold/index.cjs +2270 -0
  54. package/dist/scaffold/index.cjs.map +1 -0
  55. package/dist/scaffold/index.d.ts +14 -0
  56. package/dist/scaffold/index.js +2244 -0
  57. package/dist/scaffold/index.js.map +1 -0
  58. package/dist/scaffold/lib/utils.d.ts +2 -0
  59. package/dist/styles.css +137 -378
  60. package/dist/types.d.ts +3 -13
  61. package/dist/ui/adapters.d.ts +1 -1
  62. package/dist/ui/agent-chat-actions.d.ts +12 -5
  63. package/dist/ui/agent-chat-composer.d.ts +2 -1
  64. package/dist/ui/agent-chat-context-compression-row.d.ts +5 -0
  65. package/dist/ui/agent-chat-context-status.d.ts +13 -0
  66. package/dist/ui/agent-chat-message.d.ts +7 -5
  67. package/dist/ui/agent-response-timeline.d.ts +29 -7
  68. package/dist/ui/ask-user-interaction-state.d.ts +20 -0
  69. package/dist/ui/ask-user-interaction.d.ts +8 -0
  70. package/dist/ui/index.cjs +1391 -271
  71. package/dist/ui/index.cjs.map +1 -1
  72. package/dist/ui/index.d.ts +5 -0
  73. package/dist/ui/index.js +2380 -65
  74. package/dist/ui/index.js.map +1 -1
  75. package/dist/ui/tool-sessions.d.ts +70 -0
  76. package/dist/ui/use-audio-recorder.d.ts +8 -0
  77. package/package.json +27 -12
  78. package/scaffold/components.json +25 -0
  79. package/scaffold/manifest.json +28 -0
  80. package/scaffold/src/app.tsx +71 -0
  81. package/scaffold/src/chat.css +54 -0
  82. package/scaffold/src/components/chat/chat-composer.tsx +237 -0
  83. package/scaffold/src/components/chat/chat-message.tsx +76 -0
  84. package/scaffold/src/components/chat/chat-shell.tsx +257 -0
  85. package/scaffold/src/components/chat/collapsible-user-message.tsx +37 -0
  86. package/scaffold/src/components/chat/external-link-dialog.tsx +48 -0
  87. package/scaffold/src/components/chat/identity-control.tsx +69 -0
  88. package/scaffold/src/components/chat/message-parts.tsx +83 -0
  89. package/scaffold/src/components/chat/theme-menu.tsx +79 -0
  90. package/scaffold/src/components/chat/thread-sidebar.tsx +114 -0
  91. package/scaffold/src/components/chat/timeline-loading.ts +17 -0
  92. package/scaffold/src/components/chat/turn-outline.tsx +73 -0
  93. package/scaffold/src/components/theme-provider.tsx +91 -0
  94. package/scaffold/src/components/ui/attachment.tsx +204 -0
  95. package/scaffold/src/components/ui/bubble.tsx +125 -0
  96. package/scaffold/src/components/ui/button-group.tsx +83 -0
  97. package/scaffold/src/components/ui/button.tsx +65 -0
  98. package/scaffold/src/components/ui/dialog.tsx +171 -0
  99. package/scaffold/src/components/ui/dropdown-menu.tsx +271 -0
  100. package/scaffold/src/components/ui/input.tsx +19 -0
  101. package/scaffold/src/components/ui/marker.tsx +69 -0
  102. package/scaffold/src/components/ui/message-scroller.tsx +129 -0
  103. package/scaffold/src/components/ui/message.tsx +92 -0
  104. package/scaffold/src/components/ui/popover.tsx +40 -0
  105. package/scaffold/src/components/ui/select.tsx +92 -0
  106. package/scaffold/src/components/ui/separator.tsx +28 -0
  107. package/scaffold/src/components/ui/sheet.tsx +150 -0
  108. package/scaffold/src/components/ui/sidebar.tsx +677 -0
  109. package/scaffold/src/components/ui/skeleton.tsx +13 -0
  110. package/scaffold/src/components/ui/textarea.tsx +18 -0
  111. package/scaffold/src/components/ui/tooltip.tsx +56 -0
  112. package/scaffold/src/hooks/use-mobile.ts +24 -0
  113. package/scaffold/src/identity.ts +17 -0
  114. package/scaffold/src/index.css +222 -0
  115. package/scaffold/src/index.tsx +39 -0
  116. package/scaffold/src/lib/utils.ts +6 -0
  117. package/scaffold/styles.css +3 -0
  118. package/dist/chunk-CHAU7FNW.js +0 -140
  119. package/dist/chunk-CHAU7FNW.js.map +0 -1
  120. package/dist/chunk-LI67M74F.js +0 -445
  121. package/dist/chunk-LI67M74F.js.map +0 -1
  122. package/dist/chunk-R7WFL3YR.js +0 -1327
  123. package/dist/chunk-R7WFL3YR.js.map +0 -1
  124. package/dist/renderers.d.ts +0 -10
  125. package/dist/templates/agent-chat-app.d.ts +0 -18
  126. package/dist/templates/agent-chat-layout.d.ts +0 -77
  127. package/dist/templates/agent-chat-shell.d.ts +0 -37
  128. package/dist/templates/appearance-control.d.ts +0 -5
  129. package/dist/templates/index.cjs +0 -2600
  130. package/dist/templates/index.cjs.map +0 -1
  131. package/dist/templates/index.d.ts +0 -4
  132. package/dist/templates/index.js +0 -846
  133. package/dist/templates/index.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents24/chat-react",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Accessible web UI and complete default layout for Agents24 chat applications.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -24,16 +24,23 @@
24
24
  "import": "./dist/ui/index.js",
25
25
  "require": "./dist/ui/index.cjs"
26
26
  },
27
- "./templates": {
28
- "types": "./dist/templates/index.d.ts",
29
- "import": "./dist/templates/index.js",
30
- "require": "./dist/templates/index.cjs"
27
+ "./runtime": {
28
+ "types": "./dist/runtime/index.d.ts",
29
+ "import": "./dist/runtime/index.js",
30
+ "require": "./dist/runtime/index.cjs"
31
31
  },
32
+ "./scaffold": {
33
+ "types": "./dist/scaffold/index.d.ts",
34
+ "import": "./dist/scaffold/index.js",
35
+ "require": "./dist/scaffold/index.cjs"
36
+ },
37
+ "./scaffold/styles.css": "./scaffold/styles.css",
32
38
  "./styles.css": "./dist/styles.css",
33
39
  "./compatibility.json": "./compatibility.json"
34
40
  },
35
41
  "files": [
36
42
  "dist",
43
+ "scaffold",
37
44
  "compatibility.json",
38
45
  "README.md",
39
46
  "LICENSE"
@@ -45,21 +52,28 @@
45
52
  "scripts": {
46
53
  "sync:contract": "node scripts/sync-contract.mjs",
47
54
  "check:contract": "node scripts/sync-contract.mjs --check",
48
- "build": "pnpm run sync:contract && tsup && tsc -p tsconfig.build.json",
55
+ "build": "pnpm run sync:contract && tsup && tsc -p tsconfig.build.json && tsc -p tsconfig.scaffold-build.json",
49
56
  "test": "tsx --test tests/*.test.ts",
50
57
  "check:boundaries": "node tests/check-boundaries.mjs",
51
58
  "prepack": "pnpm run build && pnpm run check:boundaries"
52
59
  },
53
60
  "dependencies": {
54
- "@agents24/client": "0.2.0",
55
- "@agents24/react": "0.2.0",
56
- "@radix-ui/react-slot": "^1.2.4",
57
- "@shadcn/react": "0.1.0",
61
+ "@agents24/client": "0.2.1",
62
+ "@agents24/react": "0.2.1",
63
+ "@fontsource-variable/inter": "^5.3.0",
64
+ "@fontsource-variable/jetbrains-mono": "^5.3.0",
65
+ "@fontsource-variable/merriweather": "^5.3.0",
66
+ "@radix-ui/react-slot": "1.2.4",
67
+ "@shadcn/react": "^0.2.1",
58
68
  "class-variance-authority": "^0.7.1",
59
69
  "clsx": "^2.1.1",
60
70
  "lucide-react": "0.542.0",
61
- "streamdown": "^1.5.1",
62
- "tailwind-merge": "^3.6.0"
71
+ "radix-ui": "1.4.3",
72
+ "streamdown": "^2.5.0",
73
+ "tailwind-merge": "^3.6.0",
74
+ "tailwindcss": "^4.1.16",
75
+ "thinking-orbs": "0.2.0",
76
+ "tw-animate-css": "^1.4.0"
63
77
  },
64
78
  "peerDependencies": {
65
79
  "react": ">=19.0.0 <20.0.0",
@@ -69,6 +83,7 @@
69
83
  "@types/react": "^19.2.3",
70
84
  "react": "^19.2.3",
71
85
  "react-dom": "^19.2.3",
86
+ "shadcn": "4.13.1",
72
87
  "tsup": "^8.5.1",
73
88
  "tsx": "^4.20.6",
74
89
  "typescript": "^5.9.3"
@@ -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
+ }