@agents24/chat-react 0.4.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 +92 -18
- 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 +143 -298
- 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 +1392 -272
- 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-NLEOWTZG.js +0 -1327
- package/dist/chunk-NLEOWTZG.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 -69
- package/dist/templates/agent-chat-shell.d.ts +0 -37
- package/dist/templates/index.cjs +0 -2476
- package/dist/templates/index.cjs.map +0 -1
- package/dist/templates/index.d.ts +0 -3
- package/dist/templates/index.js +0 -723
- package/dist/templates/index.js.map +0 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { getThreadActivity, type StoredChatThread } from "@agents24/react"
|
|
2
|
+
import { LoaderCircle, SquarePen, Trash2 } from "lucide-react"
|
|
3
|
+
import type { Agents24ChatIdentity } from "../../identity"
|
|
4
|
+
import { IdentityControl } from "./identity-control"
|
|
5
|
+
|
|
6
|
+
import { Button } from "../ui/button"
|
|
7
|
+
import {
|
|
8
|
+
Sidebar,
|
|
9
|
+
SidebarContent,
|
|
10
|
+
SidebarFooter,
|
|
11
|
+
SidebarGroup,
|
|
12
|
+
SidebarGroupContent,
|
|
13
|
+
SidebarHeader,
|
|
14
|
+
SidebarMenu,
|
|
15
|
+
SidebarMenuAction,
|
|
16
|
+
SidebarMenuButton,
|
|
17
|
+
SidebarMenuItem,
|
|
18
|
+
SidebarRail,
|
|
19
|
+
useSidebar,
|
|
20
|
+
} from "../ui/sidebar"
|
|
21
|
+
|
|
22
|
+
type Props = {
|
|
23
|
+
activeThreadId: string | null
|
|
24
|
+
hasMore: boolean
|
|
25
|
+
isLoading: boolean
|
|
26
|
+
isLoadingMore: boolean
|
|
27
|
+
onDelete: (threadId: string) => Promise<void>
|
|
28
|
+
onLoadMore: () => Promise<void>
|
|
29
|
+
onNew: () => void
|
|
30
|
+
onOpen: (threadId: string) => Promise<void>
|
|
31
|
+
threads: StoredChatThread[]
|
|
32
|
+
identity: Agents24ChatIdentity
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function ThreadStatus({ thread }: { thread: StoredChatThread }) {
|
|
36
|
+
const activity = getThreadActivity(thread)
|
|
37
|
+
if (!activity.isSpinning) return null
|
|
38
|
+
const label = activity.status === "cancelling"
|
|
39
|
+
? "Cancelling"
|
|
40
|
+
: activity.status === "queued"
|
|
41
|
+
? "Queued"
|
|
42
|
+
: "Running"
|
|
43
|
+
return (
|
|
44
|
+
<span
|
|
45
|
+
className="pointer-events-none absolute end-1 top-1/2 hidden size-5 -translate-y-1/2 items-center justify-center text-muted-foreground transition-opacity group-hover/menu-item:opacity-0 md:flex"
|
|
46
|
+
role="status"
|
|
47
|
+
aria-label={label}
|
|
48
|
+
title={label}
|
|
49
|
+
>
|
|
50
|
+
<LoaderCircle className="size-3.5 animate-spin" />
|
|
51
|
+
</span>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function ThreadSidebar({ activeThreadId, hasMore, identity, isLoading, isLoadingMore, onDelete, onLoadMore, onNew, onOpen, threads }: Props) {
|
|
56
|
+
const { setOpenMobile } = useSidebar()
|
|
57
|
+
return (
|
|
58
|
+
<Sidebar
|
|
59
|
+
aria-label="Conversations"
|
|
60
|
+
collapsible="offcanvas"
|
|
61
|
+
data-agents24-chat-sidebar=""
|
|
62
|
+
role="navigation"
|
|
63
|
+
>
|
|
64
|
+
<SidebarHeader className="gap-1 p-2">
|
|
65
|
+
<div className="h-8 shrink-0" aria-hidden="true" />
|
|
66
|
+
<Button className="w-full justify-start" variant="ghost" onClick={() => { onNew(); setOpenMobile(false) }}><SquarePen /> New chat</Button>
|
|
67
|
+
</SidebarHeader>
|
|
68
|
+
<SidebarContent>
|
|
69
|
+
<SidebarGroup>
|
|
70
|
+
<SidebarGroupContent>
|
|
71
|
+
<SidebarMenu>
|
|
72
|
+
{threads.map((thread) => (
|
|
73
|
+
<SidebarMenuItem key={thread.id}>
|
|
74
|
+
<SidebarMenuButton
|
|
75
|
+
className="pr-8 pl-3"
|
|
76
|
+
isActive={thread.id === activeThreadId}
|
|
77
|
+
title={String(thread.title || "New conversation")}
|
|
78
|
+
onClick={() => { void onOpen(thread.id).catch(() => undefined); setOpenMobile(false) }}
|
|
79
|
+
>
|
|
80
|
+
<span className="min-w-0 truncate">{String(thread.title || "New conversation")}</span>
|
|
81
|
+
</SidebarMenuButton>
|
|
82
|
+
<ThreadStatus thread={thread} />
|
|
83
|
+
<SidebarMenuAction
|
|
84
|
+
showOnHover
|
|
85
|
+
className="group-focus-within/menu-item:opacity-0 group-hover/menu-item:opacity-100"
|
|
86
|
+
aria-label={`Delete ${String(thread.title || "conversation")}`}
|
|
87
|
+
onClick={(event) => { event.stopPropagation(); if (window.confirm("Delete this conversation?")) void onDelete(thread.id).catch(() => undefined) }}
|
|
88
|
+
>
|
|
89
|
+
<Trash2 />
|
|
90
|
+
</SidebarMenuAction>
|
|
91
|
+
</SidebarMenuItem>
|
|
92
|
+
))}
|
|
93
|
+
</SidebarMenu>
|
|
94
|
+
{!isLoading && threads.length === 0 && <p className="px-2 py-8 text-center text-xs text-muted-foreground">No conversations</p>}
|
|
95
|
+
{hasMore && (
|
|
96
|
+
<Button
|
|
97
|
+
className="mt-2 w-full"
|
|
98
|
+
disabled={isLoadingMore}
|
|
99
|
+
size="sm"
|
|
100
|
+
variant="ghost"
|
|
101
|
+
onClick={() => void onLoadMore().catch(() => undefined)}
|
|
102
|
+
>
|
|
103
|
+
{isLoadingMore && <LoaderCircle className="animate-spin" />}
|
|
104
|
+
Load more
|
|
105
|
+
</Button>
|
|
106
|
+
)}
|
|
107
|
+
</SidebarGroupContent>
|
|
108
|
+
</SidebarGroup>
|
|
109
|
+
</SidebarContent>
|
|
110
|
+
<SidebarFooter className="w-full min-w-0 p-2"><IdentityControl identity={identity} /></SidebarFooter>
|
|
111
|
+
<SidebarRail />
|
|
112
|
+
</Sidebar>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AgentChatRunState, ChatMessage } from "@agents24/react"
|
|
2
|
+
|
|
3
|
+
export function isScaffoldTimelineLoading({
|
|
4
|
+
activeRunId,
|
|
5
|
+
message,
|
|
6
|
+
runState,
|
|
7
|
+
streamingMessageId,
|
|
8
|
+
}: {
|
|
9
|
+
activeRunId: string | null
|
|
10
|
+
message: ChatMessage
|
|
11
|
+
runState: AgentChatRunState
|
|
12
|
+
streamingMessageId: string | null
|
|
13
|
+
}): boolean {
|
|
14
|
+
if (runState !== "streaming" && runState !== "reconnecting") return false
|
|
15
|
+
return message.id === streamingMessageId
|
|
16
|
+
|| Boolean(message.runId && message.runId === activeRunId)
|
|
17
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ChatMessage } from "@agents24/react"
|
|
2
|
+
import { useState } from "react"
|
|
3
|
+
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"
|
|
5
|
+
import { useMessageScroller, useMessageScrollerVisibility } from "../ui/message-scroller"
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
|
|
8
|
+
type Turn = { id: string; label: string; messageIds: string[]; response: string }
|
|
9
|
+
|
|
10
|
+
function turnsFromMessages(messages: ChatMessage[]): Turn[] {
|
|
11
|
+
return messages.reduce<Turn[]>((turns, message) => {
|
|
12
|
+
if (message.role === "user") {
|
|
13
|
+
turns.push({ id: message.id, label: message.content.trim() || `Turn ${turns.length + 1}`, messageIds: [message.id], response: "" })
|
|
14
|
+
} else if (turns.length) {
|
|
15
|
+
const turn = turns[turns.length - 1]
|
|
16
|
+
turn.messageIds.push(message.id)
|
|
17
|
+
if (!turn.response) turn.response = message.content.trim()
|
|
18
|
+
}
|
|
19
|
+
return turns
|
|
20
|
+
}, [])
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function TurnOutline({ messages }: { messages: ChatMessage[] }) {
|
|
24
|
+
const turns = turnsFromMessages(messages)
|
|
25
|
+
const { scrollToMessage } = useMessageScroller()
|
|
26
|
+
const { currentAnchorId, visibleMessageIds } = useMessageScrollerVisibility()
|
|
27
|
+
const visible = new Set(visibleMessageIds)
|
|
28
|
+
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null)
|
|
29
|
+
const focusIndex = hoveredIndex ?? -1
|
|
30
|
+
if (turns.length < 2) return null
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<nav aria-label="Transcript outline" className="pointer-events-none absolute end-2 top-1/2 z-20 hidden max-h-72 w-14 -translate-y-1/2 md:block xl:end-5">
|
|
34
|
+
<div className="pointer-events-auto flex flex-col items-end gap-0.5 py-2">
|
|
35
|
+
{turns.map((turn, index) => {
|
|
36
|
+
const active = turn.messageIds.some((id) => visible.has(id) || currentAnchorId === id)
|
|
37
|
+
const distance = focusIndex >= 0 ? Math.abs(index - focusIndex) : Number.POSITIVE_INFINITY
|
|
38
|
+
const barWidth = focusIndex < 0
|
|
39
|
+
? 12
|
|
40
|
+
: distance === 0 ? 50 : distance === 1 ? 36 : distance === 2 ? 23 : 10
|
|
41
|
+
const barOpacity = focusIndex < 0
|
|
42
|
+
? active ? 1 : 0.4
|
|
43
|
+
: distance === 0 ? 1 : distance === 1 ? 0.78 : distance === 2 ? 0.58 : 0.36
|
|
44
|
+
return (
|
|
45
|
+
<Tooltip key={turn.id}>
|
|
46
|
+
<TooltipTrigger asChild>
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
aria-label={`Jump to ${turn.label}`}
|
|
50
|
+
className="group relative flex h-2 w-14 items-center justify-end outline-none after:pointer-events-auto after:absolute after:-inset-y-0.5 after:inset-x-0 after:content-['']"
|
|
51
|
+
onBlur={() => setHoveredIndex(null)}
|
|
52
|
+
onFocus={() => setHoveredIndex(index)}
|
|
53
|
+
onMouseEnter={() => setHoveredIndex(index)}
|
|
54
|
+
onMouseLeave={() => setHoveredIndex(null)}
|
|
55
|
+
onClick={() => scrollToMessage(turn.id, { align: "start", behavior: "smooth" })}
|
|
56
|
+
>
|
|
57
|
+
<span
|
|
58
|
+
className={cn("h-0.5 rounded-full bg-muted-foreground/40 transition-[width,background-color,opacity] duration-150 ease-out group-hover:bg-foreground group-focus-visible:bg-foreground", active && "bg-foreground")}
|
|
59
|
+
style={{ opacity: barOpacity, width: barWidth }}
|
|
60
|
+
/>
|
|
61
|
+
</button>
|
|
62
|
+
</TooltipTrigger>
|
|
63
|
+
<TooltipContent side="left" className="w-64 flex-col items-stretch gap-0 overflow-hidden p-3">
|
|
64
|
+
<p className="line-clamp-2 max-w-full wrap-break-word text-sm font-medium">{turn.label}</p>
|
|
65
|
+
{turn.response && <p className="mt-1 line-clamp-2 max-w-full wrap-break-word text-xs opacity-70">{turn.response}</p>}
|
|
66
|
+
</TooltipContent>
|
|
67
|
+
</Tooltip>
|
|
68
|
+
)
|
|
69
|
+
})}
|
|
70
|
+
</div>
|
|
71
|
+
</nav>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
export type Agents24ChatAppearance = "system" | "light" | "dark"
|
|
4
|
+
export type Agents24ChatTheme = "soft" | "sharp"
|
|
5
|
+
|
|
6
|
+
export type Agents24ChatAppearanceController = {
|
|
7
|
+
appearance: Agents24ChatAppearance
|
|
8
|
+
setAppearance: (appearance: Agents24ChatAppearance) => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type ThemeContextValue = {
|
|
12
|
+
appearance: Agents24ChatAppearance
|
|
13
|
+
portalContainer: HTMLElement | null
|
|
14
|
+
setAppearance: (appearance: Agents24ChatAppearance) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const ThemeContext = React.createContext<ThemeContextValue | null>(null)
|
|
18
|
+
const storageKey = "agents24-chat-appearance"
|
|
19
|
+
|
|
20
|
+
function storedAppearance(): Agents24ChatAppearance {
|
|
21
|
+
try {
|
|
22
|
+
const stored = window.localStorage.getItem(storageKey)
|
|
23
|
+
return stored === "light" || stored === "dark" || stored === "system" ? stored : "system"
|
|
24
|
+
} catch {
|
|
25
|
+
return "system"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function ThemeProvider({
|
|
30
|
+
children,
|
|
31
|
+
controller,
|
|
32
|
+
theme = "soft",
|
|
33
|
+
}: {
|
|
34
|
+
children: React.ReactNode
|
|
35
|
+
controller?: Agents24ChatAppearanceController
|
|
36
|
+
theme?: Agents24ChatTheme
|
|
37
|
+
}) {
|
|
38
|
+
const [internalAppearance, setInternalAppearance] = React.useState<Agents24ChatAppearance>("system")
|
|
39
|
+
const [resolvedAppearance, setResolvedAppearance] = React.useState<"light" | "dark">("light")
|
|
40
|
+
const [mounted, setMounted] = React.useState(false)
|
|
41
|
+
const [portalContainer, setPortalContainer] = React.useState<HTMLDivElement | null>(null)
|
|
42
|
+
const appearance = controller?.appearance ?? internalAppearance
|
|
43
|
+
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
if (!controller) setInternalAppearance(storedAppearance())
|
|
46
|
+
setMounted(true)
|
|
47
|
+
}, [controller])
|
|
48
|
+
|
|
49
|
+
React.useEffect(() => {
|
|
50
|
+
if (!mounted || typeof window === "undefined" || typeof window.matchMedia !== "function") return
|
|
51
|
+
const media = window.matchMedia("(prefers-color-scheme: dark)")
|
|
52
|
+
const apply = () => setResolvedAppearance(
|
|
53
|
+
appearance === "system" ? media.matches ? "dark" : "light" : appearance,
|
|
54
|
+
)
|
|
55
|
+
apply()
|
|
56
|
+
media.addEventListener("change", apply)
|
|
57
|
+
return () => media.removeEventListener("change", apply)
|
|
58
|
+
}, [appearance, mounted])
|
|
59
|
+
|
|
60
|
+
const change = React.useCallback((next: Agents24ChatAppearance) => {
|
|
61
|
+
if (controller) {
|
|
62
|
+
controller.setAppearance(next)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
try { window.localStorage.setItem(storageKey, next) } catch { /* storage is optional */ }
|
|
66
|
+
setInternalAppearance(next)
|
|
67
|
+
}, [controller])
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<ThemeContext.Provider value={{ appearance, portalContainer, setAppearance: change }}>
|
|
71
|
+
<div
|
|
72
|
+
ref={setPortalContainer}
|
|
73
|
+
className={`${resolvedAppearance} size-full`}
|
|
74
|
+
data-agents24-theme={theme}
|
|
75
|
+
data-agents24-theme-root=""
|
|
76
|
+
>
|
|
77
|
+
{children}
|
|
78
|
+
</div>
|
|
79
|
+
</ThemeContext.Provider>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function useTheme() {
|
|
84
|
+
const context = React.useContext(ThemeContext)
|
|
85
|
+
if (!context) throw new Error("useTheme must be used inside ThemeProvider")
|
|
86
|
+
return context
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function useThemePortalContainer() {
|
|
90
|
+
return useTheme().portalContainer
|
|
91
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { Slot } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
import { Button } from "./button"
|
|
7
|
+
|
|
8
|
+
const attachmentVariants = cva(
|
|
9
|
+
"group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-3xl border bg-card text-card-foreground transition-colors focus-within:ring-1 focus-within:ring-ring/30 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed",
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
size: {
|
|
13
|
+
default:
|
|
14
|
+
"gap-2 text-sm has-data-[slot=attachment-content]:px-2.5 has-data-[slot=attachment-content]:py-2 has-data-[slot=attachment-media]:p-2",
|
|
15
|
+
sm: "gap-2.5 text-xs has-data-[slot=attachment-content]:px-2 has-data-[slot=attachment-content]:py-1.5 has-data-[slot=attachment-media]:p-1.5",
|
|
16
|
+
xs: "gap-1.5 rounded-2xl text-xs has-data-[slot=attachment-content]:px-1.5 has-data-[slot=attachment-content]:py-1 has-data-[slot=attachment-media]:p-1",
|
|
17
|
+
},
|
|
18
|
+
orientation: {
|
|
19
|
+
horizontal: "min-w-40 items-center",
|
|
20
|
+
vertical: "w-24 flex-col has-data-[slot=attachment-content]:w-30",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
function Attachment({
|
|
27
|
+
className,
|
|
28
|
+
state = "done",
|
|
29
|
+
size = "default",
|
|
30
|
+
orientation = "horizontal",
|
|
31
|
+
...props
|
|
32
|
+
}: React.ComponentProps<"div"> &
|
|
33
|
+
VariantProps<typeof attachmentVariants> & {
|
|
34
|
+
state?: "idle" | "uploading" | "processing" | "error" | "done"
|
|
35
|
+
}) {
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
data-slot="attachment"
|
|
39
|
+
data-state={state}
|
|
40
|
+
data-size={size}
|
|
41
|
+
data-orientation={orientation}
|
|
42
|
+
className={cn(attachmentVariants({ size, orientation }), className)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const attachmentMediaVariants = cva(
|
|
49
|
+
"relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-2xl bg-muted text-foreground group-data-[orientation=vertical]/attachment:w-full group-data-[size=sm]/attachment:w-8 group-data-[size=xs]/attachment:w-7 group-data-[size=xs]/attachment:rounded-xl group-data-[state=error]/attachment:bg-destructive/10 group-data-[state=error]/attachment:text-destructive group-data-[orientation=vertical]/attachment:*:data-[slot=spinner]:size-6! [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 group-data-[orientation=vertical]/attachment:[&_svg:not([class*='size-'])]:size-6 group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5",
|
|
50
|
+
{
|
|
51
|
+
variants: {
|
|
52
|
+
variant: {
|
|
53
|
+
icon: "",
|
|
54
|
+
image:
|
|
55
|
+
"opacity-60 group-data-[state=done]/attachment:opacity-100 group-data-[state=idle]/attachment:opacity-100 *:[img]:aspect-square *:[img]:w-full *:[img]:object-cover",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
defaultVariants: {
|
|
59
|
+
variant: "icon",
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
function AttachmentMedia({
|
|
65
|
+
className,
|
|
66
|
+
variant = "icon",
|
|
67
|
+
...props
|
|
68
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof attachmentMediaVariants>) {
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
data-slot="attachment-media"
|
|
72
|
+
data-variant={variant}
|
|
73
|
+
className={cn(attachmentMediaVariants({ variant }), className)}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function AttachmentContent({
|
|
80
|
+
className,
|
|
81
|
+
...props
|
|
82
|
+
}: React.ComponentProps<"div">) {
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
data-slot="attachment-content"
|
|
86
|
+
className={cn(
|
|
87
|
+
"max-w-full min-w-0 flex-1 leading-tight group-data-[orientation=vertical]/attachment:px-1",
|
|
88
|
+
className
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function AttachmentTitle({
|
|
96
|
+
className,
|
|
97
|
+
...props
|
|
98
|
+
}: React.ComponentProps<"span">) {
|
|
99
|
+
return (
|
|
100
|
+
<span
|
|
101
|
+
data-slot="attachment-title"
|
|
102
|
+
className={cn(
|
|
103
|
+
"block max-w-full min-w-0 truncate font-medium group-data-[state=processing]/attachment:shimmer group-data-[state=uploading]/attachment:shimmer",
|
|
104
|
+
className
|
|
105
|
+
)}
|
|
106
|
+
{...props}
|
|
107
|
+
/>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function AttachmentDescription({
|
|
112
|
+
className,
|
|
113
|
+
...props
|
|
114
|
+
}: React.ComponentProps<"span">) {
|
|
115
|
+
return (
|
|
116
|
+
<span
|
|
117
|
+
data-slot="attachment-description"
|
|
118
|
+
className={cn(
|
|
119
|
+
"mt-0.5 block min-w-0 truncate text-xs text-muted-foreground group-data-[state=error]/attachment:text-destructive/80",
|
|
120
|
+
"max-w-full",
|
|
121
|
+
className
|
|
122
|
+
)}
|
|
123
|
+
{...props}
|
|
124
|
+
/>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function AttachmentActions({
|
|
129
|
+
className,
|
|
130
|
+
...props
|
|
131
|
+
}: React.ComponentProps<"div">) {
|
|
132
|
+
return (
|
|
133
|
+
<div
|
|
134
|
+
data-slot="attachment-actions"
|
|
135
|
+
className={cn(
|
|
136
|
+
"relative z-20 flex shrink-0 items-center group-data-[orientation=vertical]/attachment:absolute group-data-[orientation=vertical]/attachment:top-3 group-data-[orientation=vertical]/attachment:right-3 group-data-[orientation=vertical]/attachment:gap-1",
|
|
137
|
+
className
|
|
138
|
+
)}
|
|
139
|
+
{...props}
|
|
140
|
+
/>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function AttachmentAction({
|
|
145
|
+
className,
|
|
146
|
+
variant,
|
|
147
|
+
size = "icon-xs",
|
|
148
|
+
...props
|
|
149
|
+
}: React.ComponentProps<typeof Button>) {
|
|
150
|
+
return (
|
|
151
|
+
<Button
|
|
152
|
+
data-slot="attachment-action"
|
|
153
|
+
variant={variant ?? "ghost"}
|
|
154
|
+
size={size}
|
|
155
|
+
className={cn(className)}
|
|
156
|
+
{...props}
|
|
157
|
+
/>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function AttachmentTrigger({
|
|
162
|
+
className,
|
|
163
|
+
asChild = false,
|
|
164
|
+
type,
|
|
165
|
+
...props
|
|
166
|
+
}: React.ComponentProps<"button"> & {
|
|
167
|
+
asChild?: boolean
|
|
168
|
+
}) {
|
|
169
|
+
const Comp = asChild ? Slot.Root : "button"
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<Comp
|
|
173
|
+
data-slot="attachment-trigger"
|
|
174
|
+
type={asChild ? undefined : (type ?? "button")}
|
|
175
|
+
className={cn("absolute inset-0 z-10 outline-none", className)}
|
|
176
|
+
{...props}
|
|
177
|
+
/>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function AttachmentGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
182
|
+
return (
|
|
183
|
+
<div
|
|
184
|
+
data-slot="attachment-group"
|
|
185
|
+
className={cn(
|
|
186
|
+
"flex min-w-0 scroll-fade-x snap-x snap-mandatory scroll-px-1 scrollbar-none gap-3 overflow-x-auto overscroll-x-contain py-1 *:data-[slot=attachment]:flex-none *:data-[slot=attachment]:snap-start",
|
|
187
|
+
className
|
|
188
|
+
)}
|
|
189
|
+
{...props}
|
|
190
|
+
/>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export {
|
|
195
|
+
Attachment,
|
|
196
|
+
AttachmentGroup,
|
|
197
|
+
AttachmentMedia,
|
|
198
|
+
AttachmentContent,
|
|
199
|
+
AttachmentTitle,
|
|
200
|
+
AttachmentDescription,
|
|
201
|
+
AttachmentActions,
|
|
202
|
+
AttachmentAction,
|
|
203
|
+
AttachmentTrigger,
|
|
204
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { Slot } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
function BubbleGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
data-slot="bubble-group"
|
|
11
|
+
className={cn("flex min-w-0 flex-col gap-2", className)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const bubbleVariants = cva(
|
|
18
|
+
"group/bubble relative flex w-fit max-w-[80%] min-w-0 flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[variant=ghost]:max-w-full",
|
|
19
|
+
{
|
|
20
|
+
variants: {
|
|
21
|
+
variant: {
|
|
22
|
+
default:
|
|
23
|
+
"*:data-[slot=bubble-content]:bg-primary *:data-[slot=bubble-content]:text-primary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-primary/80",
|
|
24
|
+
secondary:
|
|
25
|
+
"*:data-[slot=bubble-content]:bg-secondary *:data-[slot=bubble-content]:text-secondary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]",
|
|
26
|
+
muted:
|
|
27
|
+
"*:data-[slot=bubble-content]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--muted),var(--foreground)_5%)]",
|
|
28
|
+
tinted:
|
|
29
|
+
"*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.93_calc(c*0.4)_h)] *:data-[slot=bubble-content]:text-foreground dark:*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.3_calc(c*0.4)_h)] [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.88_calc(c*0.5)_h)] dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.35_calc(c*0.5)_h)]",
|
|
30
|
+
outline:
|
|
31
|
+
"*:data-[slot=bubble-content]:border-border *:data-[slot=bubble-content]:bg-background [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-input/30",
|
|
32
|
+
ghost:
|
|
33
|
+
"border-none *:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted/50",
|
|
34
|
+
destructive:
|
|
35
|
+
"*:data-[slot=bubble-content]:bg-destructive/10 *:data-[slot=bubble-content]:text-destructive dark:*:data-[slot=bubble-content]:bg-destructive/20 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/20 dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/30",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: {
|
|
39
|
+
variant: "default",
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
function Bubble({
|
|
45
|
+
variant = "default",
|
|
46
|
+
align = "start",
|
|
47
|
+
className,
|
|
48
|
+
...props
|
|
49
|
+
}: React.ComponentProps<"div"> &
|
|
50
|
+
VariantProps<typeof bubbleVariants> & {
|
|
51
|
+
align?: "start" | "end"
|
|
52
|
+
}) {
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
data-slot="bubble"
|
|
56
|
+
data-variant={variant}
|
|
57
|
+
data-align={align}
|
|
58
|
+
className={cn(bubbleVariants({ variant }), className)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function BubbleContent({
|
|
65
|
+
asChild = false,
|
|
66
|
+
className,
|
|
67
|
+
...props
|
|
68
|
+
}: React.ComponentProps<"div"> & {
|
|
69
|
+
asChild?: boolean
|
|
70
|
+
}) {
|
|
71
|
+
const Comp = asChild ? Slot.Root : "div"
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Comp
|
|
75
|
+
data-slot="bubble-content"
|
|
76
|
+
className={cn(
|
|
77
|
+
"w-fit max-w-full min-w-0 overflow-hidden rounded-3xl border border-transparent px-3.5 py-2.5 text-sm leading-relaxed wrap-break-word group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/30",
|
|
78
|
+
className
|
|
79
|
+
)}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const bubbleReactionsVariants = cva(
|
|
86
|
+
"absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-full bg-muted px-1.5 py-0.5 text-sm ring-3 ring-card has-[button]:p-0",
|
|
87
|
+
{
|
|
88
|
+
variants: {
|
|
89
|
+
side: {
|
|
90
|
+
top: "top-0 -translate-y-3/4",
|
|
91
|
+
bottom: "bottom-0 translate-y-3/4",
|
|
92
|
+
},
|
|
93
|
+
align: {
|
|
94
|
+
start: "left-3",
|
|
95
|
+
end: "right-3",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
defaultVariants: {
|
|
99
|
+
side: "bottom",
|
|
100
|
+
align: "end",
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
function BubbleReactions({
|
|
106
|
+
side = "bottom",
|
|
107
|
+
align = "end",
|
|
108
|
+
className,
|
|
109
|
+
...props
|
|
110
|
+
}: React.ComponentProps<"div"> & {
|
|
111
|
+
align?: "start" | "end"
|
|
112
|
+
side?: "top" | "bottom"
|
|
113
|
+
}) {
|
|
114
|
+
return (
|
|
115
|
+
<div
|
|
116
|
+
data-slot="bubble-reactions"
|
|
117
|
+
data-align={align}
|
|
118
|
+
data-side={side}
|
|
119
|
+
className={cn(bubbleReactionsVariants({ side, align }), className)}
|
|
120
|
+
{...props}
|
|
121
|
+
/>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export { BubbleGroup, Bubble, BubbleContent, BubbleReactions }
|