@agents24/chat-react 0.5.0 → 0.5.1
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.
- package/README.md +90 -21
- package/compatibility.json +10 -10
- package/dist/chunk-SSUO4EPD.js +189 -0
- package/dist/chunk-SSUO4EPD.js.map +1 -0
- package/dist/chunk-ZCNHLFRB.js +108 -0
- package/dist/chunk-ZCNHLFRB.js.map +1 -0
- package/dist/index.cjs +112 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +362 -68
- package/dist/index.js.map +1 -1
- package/dist/mcp-oauth.d.ts +13 -0
- package/dist/runtime/index.cjs +594 -0
- package/dist/runtime/index.cjs.map +1 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +458 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/input-contract.d.ts +20 -0
- package/dist/runtime/use-agent-chat-runtime.d.ts +83 -0
- package/dist/scaffold/app.d.ts +30 -0
- package/dist/scaffold/components/chat/chat-composer.d.ts +21 -0
- package/dist/scaffold/components/chat/chat-message.d.ts +21 -0
- package/dist/scaffold/components/chat/chat-shell.d.ts +8 -0
- package/dist/scaffold/components/chat/collapsible-user-message.d.ts +3 -0
- package/dist/scaffold/components/chat/external-link-dialog.d.ts +2 -0
- package/dist/scaffold/components/chat/identity-control.d.ts +4 -0
- package/dist/scaffold/components/chat/message-parts.d.ts +12 -0
- package/dist/scaffold/components/chat/theme-menu.d.ts +1 -0
- package/dist/scaffold/components/chat/thread-sidebar.d.ts +16 -0
- package/dist/scaffold/components/chat/timeline-loading.d.ts +7 -0
- package/dist/scaffold/components/chat/turn-outline.d.ts +4 -0
- package/dist/scaffold/components/theme-provider.d.ts +20 -0
- package/dist/scaffold/components/ui/attachment.d.ts +24 -0
- package/dist/scaffold/components/ui/bubble.d.ts +17 -0
- package/dist/scaffold/components/ui/button-group.d.ts +11 -0
- package/dist/scaffold/components/ui/button.d.ts +10 -0
- package/dist/scaffold/components/ui/dialog.d.ts +17 -0
- package/dist/scaffold/components/ui/dropdown-menu.d.ts +29 -0
- package/dist/scaffold/components/ui/input.d.ts +3 -0
- package/dist/scaffold/components/ui/marker.d.ts +11 -0
- package/dist/scaffold/components/ui/message-scroller.d.ts +10 -0
- package/dist/scaffold/components/ui/message.d.ts +10 -0
- package/dist/scaffold/components/ui/popover.d.ts +6 -0
- package/dist/scaffold/components/ui/select.d.ts +8 -0
- package/dist/scaffold/components/ui/separator.d.ts +4 -0
- package/dist/scaffold/components/ui/sheet.d.ts +14 -0
- package/dist/scaffold/components/ui/sidebar.d.ts +69 -0
- package/dist/scaffold/components/ui/skeleton.d.ts +2 -0
- package/dist/scaffold/components/ui/textarea.d.ts +3 -0
- package/dist/scaffold/components/ui/tooltip.d.ts +15 -0
- package/dist/scaffold/hooks/use-mobile.d.ts +1 -0
- package/dist/scaffold/identity.d.ts +10 -0
- package/dist/scaffold/index.cjs +2270 -0
- package/dist/scaffold/index.cjs.map +1 -0
- package/dist/scaffold/index.d.ts +14 -0
- package/dist/scaffold/index.js +2244 -0
- package/dist/scaffold/index.js.map +1 -0
- package/dist/scaffold/lib/utils.d.ts +2 -0
- package/dist/styles.css +137 -378
- package/dist/types.d.ts +3 -13
- package/dist/ui/adapters.d.ts +1 -1
- package/dist/ui/agent-chat-actions.d.ts +12 -5
- package/dist/ui/agent-chat-composer.d.ts +2 -1
- package/dist/ui/agent-chat-context-compression-row.d.ts +5 -0
- package/dist/ui/agent-chat-context-status.d.ts +13 -0
- package/dist/ui/agent-chat-message.d.ts +7 -5
- package/dist/ui/agent-response-timeline.d.ts +29 -7
- package/dist/ui/ask-user-interaction-state.d.ts +20 -0
- package/dist/ui/ask-user-interaction.d.ts +8 -0
- package/dist/ui/index.cjs +1391 -271
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.ts +5 -0
- package/dist/ui/index.js +2380 -65
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/tool-sessions.d.ts +70 -0
- package/dist/ui/use-audio-recorder.d.ts +8 -0
- package/package.json +27 -12
- package/scaffold/components.json +25 -0
- package/scaffold/manifest.json +28 -0
- package/scaffold/src/app.tsx +71 -0
- package/scaffold/src/chat.css +54 -0
- package/scaffold/src/components/chat/chat-composer.tsx +237 -0
- package/scaffold/src/components/chat/chat-message.tsx +76 -0
- package/scaffold/src/components/chat/chat-shell.tsx +257 -0
- package/scaffold/src/components/chat/collapsible-user-message.tsx +37 -0
- package/scaffold/src/components/chat/external-link-dialog.tsx +48 -0
- package/scaffold/src/components/chat/identity-control.tsx +69 -0
- package/scaffold/src/components/chat/message-parts.tsx +83 -0
- package/scaffold/src/components/chat/theme-menu.tsx +79 -0
- package/scaffold/src/components/chat/thread-sidebar.tsx +114 -0
- package/scaffold/src/components/chat/timeline-loading.ts +17 -0
- package/scaffold/src/components/chat/turn-outline.tsx +73 -0
- package/scaffold/src/components/theme-provider.tsx +91 -0
- package/scaffold/src/components/ui/attachment.tsx +204 -0
- package/scaffold/src/components/ui/bubble.tsx +125 -0
- package/scaffold/src/components/ui/button-group.tsx +83 -0
- package/scaffold/src/components/ui/button.tsx +65 -0
- package/scaffold/src/components/ui/dialog.tsx +171 -0
- package/scaffold/src/components/ui/dropdown-menu.tsx +271 -0
- package/scaffold/src/components/ui/input.tsx +19 -0
- package/scaffold/src/components/ui/marker.tsx +69 -0
- package/scaffold/src/components/ui/message-scroller.tsx +129 -0
- package/scaffold/src/components/ui/message.tsx +92 -0
- package/scaffold/src/components/ui/popover.tsx +40 -0
- package/scaffold/src/components/ui/select.tsx +92 -0
- package/scaffold/src/components/ui/separator.tsx +28 -0
- package/scaffold/src/components/ui/sheet.tsx +150 -0
- package/scaffold/src/components/ui/sidebar.tsx +677 -0
- package/scaffold/src/components/ui/skeleton.tsx +13 -0
- package/scaffold/src/components/ui/textarea.tsx +18 -0
- package/scaffold/src/components/ui/tooltip.tsx +56 -0
- package/scaffold/src/hooks/use-mobile.ts +24 -0
- package/scaffold/src/identity.ts +17 -0
- package/scaffold/src/index.css +222 -0
- package/scaffold/src/index.tsx +39 -0
- package/scaffold/src/lib/utils.ts +6 -0
- package/scaffold/styles.css +3 -0
- package/dist/chunk-CHAU7FNW.js +0 -140
- package/dist/chunk-CHAU7FNW.js.map +0 -1
- package/dist/chunk-LI67M74F.js +0 -445
- package/dist/chunk-LI67M74F.js.map +0 -1
- package/dist/chunk-R7WFL3YR.js +0 -1327
- package/dist/chunk-R7WFL3YR.js.map +0 -1
- package/dist/renderers.d.ts +0 -10
- package/dist/templates/agent-chat-app.d.ts +0 -18
- package/dist/templates/agent-chat-layout.d.ts +0 -77
- package/dist/templates/agent-chat-shell.d.ts +0 -37
- package/dist/templates/appearance-control.d.ts +0 -5
- package/dist/templates/index.cjs +0 -2600
- package/dist/templates/index.cjs.map +0 -1
- package/dist/templates/index.d.ts +0 -4
- package/dist/templates/index.js +0 -846
- 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.
|
|
3
|
+
"version": "0.5.1",
|
|
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
|
-
"./
|
|
28
|
-
"types": "./dist/
|
|
29
|
-
"import": "./dist/
|
|
30
|
-
"require": "./dist/
|
|
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,29 @@
|
|
|
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.
|
|
55
|
-
"@agents24/react": "0.2.
|
|
56
|
-
"@
|
|
57
|
-
"@
|
|
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
|
-
"
|
|
62
|
-
"
|
|
71
|
+
"radix-ui": "1.4.3",
|
|
72
|
+
"shadcn": "4.13.1",
|
|
73
|
+
"streamdown": "^2.5.0",
|
|
74
|
+
"tailwind-merge": "^3.6.0",
|
|
75
|
+
"tailwindcss": "^4.1.16",
|
|
76
|
+
"thinking-orbs": "0.2.0",
|
|
77
|
+
"tw-animate-css": "^1.4.0"
|
|
63
78
|
},
|
|
64
79
|
"peerDependencies": {
|
|
65
80
|
"react": ">=19.0.0 <20.0.0",
|
|
@@ -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
|
+
}
|