eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
100 lines • 195 kB
TypeScript
export declare const WEB_APP_TEMPLATE_FILES: {
readonly "agent/channels/eve.ts": 'import { eveChannel } from "eve/channels/eve";\nimport { localDev, placeholderAuth, vercelOidc } from "eve/channels/auth";\n\nexport default eveChannel({\n auth: [\n // Lets the eve TUI and your Vercel deployments reach the deployed agent.\n vercelOidc(),\n // Open on localhost for `eve dev` and the REPL; ignored in production.\n localDev(),\n // This placeholder will not allow browser requests in production.\n // Replace it with your app\'s auth provider, like Auth.js or Clerk,\n // or use none() for a public demo.\n placeholderAuth(),\n ],\n});\n';
readonly "app/_components/agent-chat.tsx": '"use client";\n\nimport type { UserContent } from "ai";\nimport { useEveAgent } from "eve/react";\nimport { AlertCircleIcon, BrainIcon, PlusIcon, SquareIcon } from "lucide-react";\nimport { useState } from "react";\nimport {\n Conversation,\n ConversationContent,\n ConversationScrollButton,\n ConversationTopFade,\n} from "@/components/ai-elements/conversation";\nimport { Message, MessageContent } from "@/components/ai-elements/message";\nimport {\n PromptInput,\n PromptInputButton,\n type PromptInputMessage,\n PromptInputSubmit,\n PromptInputTextarea,\n usePromptInputAttachments,\n} from "@/components/ai-elements/prompt-input";\nimport { Shimmer } from "@/components/ai-elements/shimmer";\nimport { Button } from "@/components/ui/button";\nimport { cn } from "@/lib/utils";\nimport { AgentMessage } from "./agent-message";\n\nconst AGENT_NAME = "__EVE_INIT_APP_NAME__";\n\nexport function AgentChat({\n sessionId,\n sessionless = false,\n}: {\n readonly sessionId?: string;\n readonly sessionless?: boolean;\n}) {\n const [cancellationError, setCancellationError] = useState<string>();\n const [hasInputText, setHasInputText] = useState(false);\n const agent = useEveAgent({\n initialSession:\n sessionId === undefined\n ? undefined\n : {\n sessionId,\n streamIndex: 0,\n },\n resume: sessionId !== undefined,\n onSessionChange(session) {\n if (sessionId === undefined && session !== undefined) {\n // Next patches window.history to navigate, which would detach the active stream.\n History.prototype.replaceState.call(\n window.history,\n window.history.state,\n "",\n `/s/${encodeURIComponent(session.sessionId)}`,\n );\n }\n },\n });\n\n const isBusy = agent.status === "submitted" || agent.status === "streaming";\n const isResuming = agent.status === "resuming";\n const isEmpty = agent.data.messages.length === 0;\n const lastMessage = agent.data.messages.at(-1);\n const isPendingAssistantShell =\n lastMessage?.role === "assistant" &&\n lastMessage.parts.every((part) => part.type === "step-start");\n const showPendingThinking =\n isBusy &&\n (agent.status === "submitted" || lastMessage?.role !== "assistant" || isPendingAssistantShell);\n const turnFailure = isBusy || isResuming ? undefined : getLatestTurnFailure(agent.events);\n const errorMessage = cancellationError ?? agent.error?.message ?? turnFailure;\n const hasConversationContent = sessionless || !isEmpty || errorMessage !== undefined;\n const showConversationLayout = isResuming || hasConversationContent;\n const activeSessionId = sessionId ?? agent.session?.sessionId;\n\n const requestCancellation = () => {\n setCancellationError(undefined);\n void agent.cancel().catch((error: unknown) => {\n setCancellationError(toErrorMessage(error));\n });\n };\n\n const handleSubmit = async (message: PromptInputMessage) => {\n const text = message.text.trim();\n if ((text.length === 0 && message.files.length === 0) || isResuming) return;\n\n setHasInputText(false);\n setCancellationError(undefined);\n const options = isBusy ? { turnPolicy: "steer" as const } : undefined;\n\n if (message.files.length === 0) {\n await agent.send(text, options);\n return;\n }\n\n const parts: UserContent = [];\n if (text.length > 0) {\n parts.push({ text, type: "text" });\n }\n for (const file of message.files) {\n parts.push({\n data: file.url,\n filename: file.filename,\n mediaType: file.mediaType,\n type: "file",\n });\n }\n\n await agent.send(parts, options);\n };\n\n const composer = (\n <PromptInput onSubmit={handleSubmit}>\n <PromptInputTextarea\n disabled={isResuming}\n onChange={(event) => setHasInputText(event.currentTarget.value.trim().length > 0)}\n placeholder="Send a message…"\n />\n <ComposerAction\n hasInputText={hasInputText}\n isBusy={isBusy}\n isResuming={isResuming}\n onCancel={requestCancellation}\n />\n </PromptInput>\n );\n\n return (\n <main className="flex h-dvh flex-col overflow-hidden bg-background text-foreground">\n {showConversationLayout ? (\n <ChatHeader canStartNewChat={activeSessionId !== undefined} />\n ) : null}\n\n {showConversationLayout ? (\n <Conversation\n className="min-h-0 flex-1"\n initial={sessionId === undefined ? undefined : false}\n resize={activeSessionId === undefined ? "smooth" : "instant"}\n scrollRestorationKey={\n isEmpty || activeSessionId === undefined\n ? undefined\n : `eve:web-chat-scroll:${activeSessionId}`\n }\n >\n <ConversationTopFade className="top-14" />\n <ConversationContent className="mx-auto w-full max-w-3xl gap-6 px-4 pt-20 pb-36 sm:px-6">\n {agent.data.messages.map((message, index) =>\n showPendingThinking &&\n isPendingAssistantShell &&\n message.id === lastMessage.id ? null : (\n <AgentMessage\n canRespond={!isBusy && !isResuming}\n isStreaming={\n agent.status === "streaming" && index === agent.data.messages.length - 1\n }\n key={message.id}\n message={message}\n onInputResponses={(inputResponses) => {\n setCancellationError(undefined);\n return agent.respond(inputResponses);\n }}\n />\n ),\n )}\n {showPendingThinking ? <PendingThinking /> : null}\n {errorMessage ? <ErrorMessage message={errorMessage} /> : null}\n </ConversationContent>\n <ConversationScrollButton />\n </Conversation>\n ) : null}\n\n <div\n className={cn(\n "mx-auto w-full px-4 sm:px-6",\n showConversationLayout\n ? "fixed bottom-0 left-1/2 z-20 max-w-3xl -translate-x-1/2 bg-gradient-to-t from-background via-background to-transparent pt-4 pb-6"\n : "flex max-w-xl flex-1 flex-col items-center justify-center gap-8 pb-[10vh]",\n )}\n >\n {showConversationLayout ? null : (\n <div className="flex flex-col items-center gap-3 text-center">\n <h1 className="font-medium text-5xl tracking-tighter">{AGENT_NAME}</h1>\n </div>\n )}\n <div className="w-full">{composer}</div>\n </div>\n </main>\n );\n}\n\nfunction ComposerAction({\n hasInputText,\n isBusy,\n isResuming,\n onCancel,\n}: {\n readonly hasInputText: boolean;\n readonly isBusy: boolean;\n readonly isResuming: boolean;\n readonly onCancel: () => void;\n}) {\n const attachments = usePromptInputAttachments();\n const canSubmit = hasInputText || attachments.files.length > 0;\n\n if (!isBusy || canSubmit) {\n return <PromptInputSubmit disabled={isResuming} />;\n }\n\n return (\n <PromptInputButton\n aria-label="Stop"\n className="absolute right-2.5 bottom-2.5"\n onClick={onCancel}\n variant="outline"\n >\n <SquareIcon className="size-3 fill-current" />\n </PromptInputButton>\n );\n}\n\nfunction ErrorMessage({ message }: { readonly message: string }) {\n return (\n <Message className="max-w-full" from="assistant">\n <MessageContent>\n <div\n className="flex w-full items-start gap-3 rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2.5 text-sm"\n role="alert"\n >\n <AlertCircleIcon className="mt-0.5 size-4 shrink-0 text-destructive" />\n <div>\n <p className="font-medium">Request failed</p>\n <p className="mt-0.5 text-muted-foreground">{message}</p>\n </div>\n </div>\n </MessageContent>\n </Message>\n );\n}\n\nfunction ChatHeader({ canStartNewChat }: { readonly canStartNewChat: boolean }) {\n return (\n <header className="pointer-events-none fixed top-0 right-0 left-0 z-20 h-14">\n <div className="relative mx-auto flex h-full w-full max-w-3xl items-center justify-center bg-background px-24">\n <span className="truncate text-muted-foreground text-sm">{AGENT_NAME}</span>\n {canStartNewChat ? (\n <Button\n aria-label="Start a new chat"\n className="pointer-events-auto fixed top-3 right-6 pr-4"\n onClick={() => window.location.assign("/s")}\n size="sm"\n type="button"\n variant="ghost"\n >\n <PlusIcon className="size-4" />\n <span className="hidden font-normal text-sm sm:inline">New chat</span>\n </Button>\n ) : null}\n </div>\n </header>\n );\n}\n\nfunction PendingThinking() {\n return (\n <Message aria-live="polite" from="assistant">\n <MessageContent>\n <div className="mb-4 flex w-full items-center gap-2 text-muted-foreground text-sm">\n <BrainIcon className="size-4" />\n <Shimmer duration={1}>Thinking</Shimmer>\n </div>\n </MessageContent>\n </Message>\n );\n}\n\nfunction toErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : "Unable to cancel the response.";\n}\n\nfunction getLatestTurnFailure(\n events: ReturnType<typeof useEveAgent>["events"],\n): string | undefined {\n for (let index = events.length - 1; index >= 0; index -= 1) {\n const event = events[index];\n\n if (event.type === "turn.failed") {\n return event.data.code === "MODEL_CALL_FAILED"\n ? "The model is temporarily unavailable. Please try again."\n : event.data.message;\n }\n\n if (event.type === "turn.completed" || event.type === "turn.cancelled") {\n return undefined;\n }\n\n if (event.type === "message.received") {\n return undefined;\n }\n }\n\n return undefined;\n}\n';
readonly "app/_components/agent-message.tsx": '"use client";\n\nimport type {\n EveAuthorizationPart,\n EveDynamicToolPart,\n EveMessage,\n EveMessageInputRequest,\n EveMessagePart,\n} from "eve/react";\nimport { useState } from "react";\nimport {\n ArrowRightIcon,\n CheckCircleIcon,\n CheckIcon,\n ExternalLinkIcon,\n FileIcon,\n ImageIcon,\n KeyRoundIcon,\n XCircleIcon,\n} from "lucide-react";\nimport { Message, MessageContent, MessageResponse } from "@/components/ai-elements/message";\nimport {\n Question,\n QuestionInput,\n QuestionOption,\n QuestionOptions,\n QuestionPrompt,\n type QuestionResponse,\n QuestionSubmit,\n type QuestionValue,\n} from "@/components/ai-elements/question";\nimport { Reasoning, ReasoningContent, ReasoningTrigger } from "@/components/ai-elements/reasoning";\nimport {\n BashToolContent,\n Tool,\n ToolContent,\n ToolHeader,\n ToolInput,\n ToolOutput,\n} from "@/components/ai-elements/tool";\nimport { Button } from "@/components/ui/button";\nimport { cn } from "@/lib/utils";\n\nexport type AgentInputResponse = {\n readonly optionId?: string;\n readonly requestId: string;\n readonly text?: string;\n};\n\ntype EveFilePart = Extract<EveMessagePart, { type: "file" }>;\n\nexport function AgentMessage({\n canRespond,\n isStreaming,\n message,\n onInputResponses,\n}: {\n readonly canRespond: boolean;\n readonly isStreaming: boolean;\n readonly message: EveMessage;\n readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;\n}) {\n const lastTextIndex = message.parts.reduce(\n (last, part, index) => (part.type === "text" ? index : last),\n -1,\n );\n const hasAssistantText =\n message.role === "assistant" &&\n message.parts.some((part) => part.type === "text" && part.text.length > 0);\n\n return (\n <Message\n data-optimistic={message.metadata?.optimistic ? "true" : undefined}\n from={message.role}\n >\n <MessageContent>\n {message.parts.map((part, index) =>\n hasAssistantText && part.type === "reasoning" ? null : (\n <AgentMessagePart\n canRespond={canRespond}\n key={partKey(part, index)}\n onInputResponses={onInputResponses}\n part={part}\n showCaret={isStreaming && message.role === "assistant" && index === lastTextIndex}\n />\n ),\n )}\n </MessageContent>\n </Message>\n );\n}\n\nfunction AgentMessagePart({\n canRespond,\n onInputResponses,\n part,\n showCaret,\n}: {\n readonly canRespond: boolean;\n readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;\n readonly part: EveMessagePart;\n readonly showCaret: boolean;\n}) {\n switch (part.type) {\n case "step-start":\n return null;\n case "text":\n return (\n <MessageResponse caret="block" isAnimating={showCaret}>\n {part.text}\n </MessageResponse>\n );\n case "reasoning":\n return (\n <Reasoning defaultOpen isStreaming={part.state === "streaming"}>\n <ReasoningTrigger />\n <ReasoningContent>{part.text}</ReasoningContent>\n </Reasoning>\n );\n case "file":\n return <AttachmentPart part={part} />;\n case "authorization":\n return <AuthorizationPrompt part={part} />;\n case "dynamic-tool": {\n const inputRequest = part.toolMetadata?.eve?.inputRequest;\n if (inputRequest?.kind === "question") {\n return (\n <QuestionRequest\n canRespond={canRespond}\n inputRequest={inputRequest}\n inputResponse={part.toolMetadata?.eve?.inputResponse}\n onInputResponses={onInputResponses}\n />\n );\n }\n\n return (\n <Tool\n defaultOpen={part.state === "approval-requested" || part.state === "approval-responded"}\n >\n <ToolHeader\n state={part.state}\n title={part.toolName}\n toolName={part.toolName}\n type="dynamic-tool"\n />\n <ToolContent>\n {part.toolName === "bash" ? (\n <BashToolContent errorText={part.errorText} input={part.input} output={part.output} />\n ) : (\n <ToolInput input={part.input} />\n )}\n <InputRequestActions\n canRespond={canRespond}\n part={part}\n onInputResponses={onInputResponses}\n />\n {part.toolName === "bash" ? null : (\n <ToolOutput errorText={part.errorText} output={part.output} />\n )}\n </ToolContent>\n </Tool>\n );\n }\n }\n}\n\nfunction QuestionRequest({\n canRespond,\n inputRequest,\n inputResponse,\n onInputResponses,\n}: {\n readonly canRespond: boolean;\n readonly inputRequest: EveMessageInputRequest;\n readonly inputResponse?: AgentInputResponse;\n readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;\n}) {\n const hasOptions = (inputRequest.options?.length ?? 0) > 0;\n const acceptsFreeform = inputRequest.allowFreeform === true || !hasOptions;\n const [questionValue, setQuestionValue] = useState<QuestionValue>({\n selectedValues: inputResponse?.optionId ? [inputResponse.optionId] : [],\n text: inputResponse?.text ?? "",\n });\n\n const submitOption = (optionId: string) => {\n setQuestionValue((value) => ({ ...value, selectedValues: [optionId] }));\n return onInputResponses([\n {\n optionId,\n requestId: inputRequest.requestId,\n },\n ]);\n };\n\n const submitResponse = ({ selectedValues, text }: QuestionResponse) =>\n onInputResponses([\n {\n optionId: selectedValues[0],\n requestId: inputRequest.requestId,\n text,\n },\n ]);\n\n return (\n <Question\n disabled={!canRespond || inputResponse !== undefined}\n onSubmit={submitResponse}\n onValueChange={setQuestionValue}\n value={questionValue}\n >\n <QuestionPrompt>{inputRequest.prompt}</QuestionPrompt>\n {hasOptions ? (\n <QuestionOptions className="flex-col items-stretch" aria-label={inputRequest.prompt}>\n {inputRequest.options?.map((option, index) => (\n <QuestionOption\n className="justify-start px-3 py-2 text-left"\n key={option.id}\n onClick={() => void submitOption(option.id)}\n value={option.id}\n >\n <span className="min-w-0 flex-1">\n <span className="block text-foreground text-sm leading-tight">{option.label}</span>\n {option.description ? (\n <span className="block text-sm text-muted-foreground leading-tight">\n {option.description}\n </span>\n ) : null}\n </span>\n {inputResponse === undefined ? (\n <span aria-hidden="true" className="relative size-6 shrink-0">\n <span className="absolute inset-0 flex items-center justify-center rounded-full bg-foreground/8 text-xs text-muted-foreground transition-opacity group-hover/option:opacity-0 group-focus-visible/option:opacity-0">\n {index + 1}\n </span>\n <ArrowRightIcon className="absolute top-1/2 left-1/2 size-4 -translate-x-1/2 -translate-y-1/2 text-muted-foreground opacity-0 transition-[color,opacity] group-hover/option:text-foreground group-hover/option:opacity-100 group-focus-visible/option:opacity-100" />\n </span>\n ) : (\n <CheckIcon className="size-4 shrink-0 opacity-0 transition-opacity group-data-[state=checked]/option:opacity-100" />\n )}\n </QuestionOption>\n ))}\n </QuestionOptions>\n ) : null}\n {acceptsFreeform ? (\n <div className="relative">\n <QuestionInput\n aria-label="Answer"\n className={inputResponse === undefined ? "pr-12 pb-12" : undefined}\n placeholder="Type your answer…"\n />\n {inputResponse === undefined && questionValue.text.trim().length > 0 ? (\n <QuestionSubmit\n aria-label="Answer"\n className="absolute right-2 bottom-2"\n size="icon-sm"\n >\n <ArrowRightIcon />\n </QuestionSubmit>\n ) : null}\n </div>\n ) : null}\n </Question>\n );\n}\n\nfunction AttachmentPart({ part }: { readonly part: EveFilePart }) {\n const label = part.filename ?? "Attachment";\n const detail = [part.mediaType, formatBytes(part.size)].filter(Boolean).join(" - ");\n const isImage = part.mediaType.startsWith("image/") && part.url !== undefined;\n const Icon = isImage ? ImageIcon : FileIcon;\n const body = (\n <span className="flex max-w-sm items-center gap-3 rounded-md border bg-background/60 p-2 text-sm">\n {isImage ? (\n <img alt={label} className="size-12 shrink-0 rounded-sm object-cover" src={part.url} />\n ) : (\n <span className="flex size-10 shrink-0 items-center justify-center rounded-sm bg-muted text-muted-foreground">\n <Icon className="size-4" />\n </span>\n )}\n <span className="min-w-0 flex-1">\n <span className="block truncate font-medium">{label}</span>\n {detail ? <span className="block truncate text-muted-foreground">{detail}</span> : null}\n </span>\n {part.url ? <ExternalLinkIcon className="size-4 shrink-0 text-muted-foreground" /> : null}\n </span>\n );\n\n return part.url ? (\n <a href={part.url} rel="noreferrer" target="_blank">\n {body}\n </a>\n ) : (\n body\n );\n}\n\nfunction AuthorizationPrompt({ part }: { readonly part: EveAuthorizationPart }) {\n const isAuthorized = part.state === "completed" && part.outcome === "authorized";\n const isCompleted = part.state === "completed";\n const Icon = isAuthorized ? CheckCircleIcon : isCompleted ? XCircleIcon : KeyRoundIcon;\n const instructions = part.authorization?.instructions;\n const shouldShowInstructions = instructions !== undefined && instructions !== part.description;\n\n return (\n <div\n className={cn(\n "space-y-3 rounded-md border p-3",\n isAuthorized\n ? "border-emerald-500/30 bg-emerald-500/5"\n : isCompleted\n ? "border-destructive/30 bg-destructive/5"\n : "border-blue-500/30 bg-blue-500/5",\n )}\n >\n <div className="flex items-start gap-3">\n <span\n className={cn(\n "mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-full",\n isAuthorized\n ? "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300"\n : isCompleted\n ? "bg-destructive/10 text-destructive"\n : "bg-blue-500/10 text-blue-700 dark:text-blue-300",\n )}\n >\n <Icon className="size-4" />\n </span>\n <div className="min-w-0 flex-1 space-y-2">\n <p className="font-medium text-sm">{authorizationTitle(part)}</p>\n <p className="text-muted-foreground text-sm">{authorizationDescription(part)}</p>\n {shouldShowInstructions ? (\n <p className="text-muted-foreground text-sm">{instructions}</p>\n ) : null}\n {part.state === "required" && part.authorization?.userCode ? (\n <div className="flex flex-wrap items-center gap-2 text-sm">\n <span className="text-muted-foreground">Code</span>\n <code className="rounded-md bg-background px-2 py-1 font-mono">\n {part.authorization.userCode}\n </code>\n </div>\n ) : null}\n {part.state === "required" && part.authorization?.url ? (\n <Button asChild size="sm">\n <a href={part.authorization.url} rel="noreferrer" target="_blank">\n <ExternalLinkIcon className="size-4" />\n Sign in with {part.displayName}\n </a>\n </Button>\n ) : null}\n </div>\n </div>\n </div>\n );\n}\n\nfunction authorizationTitle(part: EveAuthorizationPart): string {\n if (part.state === "required") {\n return `Connect ${part.displayName}`;\n }\n if (part.outcome === "authorized") {\n return `${part.displayName} connected`;\n }\n return `${part.displayName} authorization ${formatAuthorizationOutcome(part.outcome)}`;\n}\n\nfunction authorizationDescription(part: EveAuthorizationPart): string {\n if (part.state === "required") {\n return part.description;\n }\n if (part.outcome === "authorized") {\n return `${part.displayName} connected.`;\n }\n const tail = part.reason !== undefined ? ` (${part.reason})` : "";\n return `${part.displayName} authorization ${formatAuthorizationOutcome(part.outcome)}${tail}.`;\n}\n\nfunction formatAuthorizationOutcome(outcome: NonNullable<EveAuthorizationPart["outcome"]>): string {\n switch (outcome) {\n case "authorized":\n return "authorized";\n case "declined":\n return "declined";\n case "failed":\n return "failed";\n case "timed-out":\n return "timed out";\n }\n}\n\nfunction formatBytes(size: number | undefined): string | undefined {\n if (size === undefined) {\n return undefined;\n }\n if (size < 1024) {\n return `${size} B`;\n }\n if (size < 1024 * 1024) {\n return `${(size / 1024).toFixed(1)} KB`;\n }\n return `${(size / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction InputRequestActions({\n canRespond,\n onInputResponses,\n part,\n}: {\n readonly canRespond: boolean;\n readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;\n readonly part: EveDynamicToolPart;\n}) {\n const inputRequest = part.toolMetadata?.eve?.inputRequest;\n if (!inputRequest) {\n return null;\n }\n\n const inputResponse = part.toolMetadata?.eve?.inputResponse;\n const selectedOption = inputRequest.options?.find(\n (option) => option.id === inputResponse?.optionId,\n );\n\n return (\n <div className="space-y-3 rounded-md border border-yellow-500/30 bg-yellow-500/5 p-3">\n <p className="text-muted-foreground text-sm">{inputRequest.prompt}</p>\n {inputResponse ? (\n <p className="font-medium text-sm">\n Responded: {selectedOption?.label ?? inputResponse.text ?? inputResponse.optionId}\n </p>\n ) : (\n <div className="flex flex-wrap gap-2">\n {inputRequest.options?.map((option) => (\n <Button\n disabled={!canRespond}\n key={option.id}\n onClick={() => {\n void onInputResponses([\n {\n optionId: option.id,\n requestId: inputRequest.requestId,\n },\n ]);\n }}\n size="sm"\n type="button"\n variant={option.style === "danger" ? "destructive" : "default"}\n >\n {option.label}\n </Button>\n ))}\n </div>\n )}\n </div>\n );\n}\n\nfunction partKey(part: EveMessagePart, index: number): string {\n switch (part.type) {\n case "authorization":\n return `authorization:${part.turnId}:${part.stepIndex}:${part.name}`;\n case "dynamic-tool":\n return part.toolCallId;\n default:\n return `${part.type}:${index}`;\n }\n}\n';
readonly "app/apple-icon.tsx": 'import { ImageResponse } from "next/og";\n\nexport const size = {\n width: 180,\n height: 180,\n};\n\nexport const contentType = "image/png";\n\nexport default function AppleIcon() {\n return new ImageResponse(\n <svg fill="none" viewBox="0 0 102 102" xmlns="http://www.w3.org/2000/svg">\n <path d="M0 0h102v102H0z" fill="#000" />\n <path\n d="M49.28 66.94 75.03 34.96h-6.89L47.91 60.11l-5.49 6.83h6.86ZM0 34.96h42.4v5.11H0zm0 13.32h27.66v5.11H0zm0 13.54h27.66v5.11H0zm69.63-26.86H102v5.11H69.63zm4.71 13.32H102v5.11H74.34zm0 13.54H102v5.11H74.34z"\n fill="#fff"\n />\n </svg>,\n size,\n );\n}\n';
readonly "app/globals.css": '@import "tailwindcss";\n@source "../node_modules/streamdown/dist/*.js";\n\n@theme inline {\n --color-background: var(--background);\n --color-foreground: var(--foreground);\n --color-card: var(--card);\n --color-card-foreground: var(--card-foreground);\n --color-popover: var(--popover);\n --color-popover-foreground: var(--popover-foreground);\n --color-primary: var(--primary);\n --color-primary-foreground: var(--primary-foreground);\n --color-secondary: var(--secondary);\n --color-secondary-foreground: var(--secondary-foreground);\n --color-muted: var(--muted);\n --color-muted-foreground: var(--muted-foreground);\n --color-accent: var(--accent);\n --color-accent-foreground: var(--accent-foreground);\n --color-destructive: var(--destructive);\n --color-border: var(--border);\n --color-input: var(--input);\n --color-ring: var(--ring);\n --radius-sm: calc(var(--radius) - 4px);\n --radius-md: calc(var(--radius) - 2px);\n --radius-lg: var(--radius);\n --radius-xl: calc(var(--radius) + 4px);\n --font-sans: "Geist", "Geist Fallback", ui-sans-serif, system-ui, sans-serif;\n --font-mono: "Geist Mono", "Geist Mono Fallback", ui-monospace, monospace;\n}\n\n:root {\n color-scheme: light;\n /* Soft neutral page with white elevated surfaces so cards/composer pop. */\n --background: oklch(0.971 0 0);\n --foreground: oklch(0.16 0 0);\n --card: oklch(1 0 0);\n --card-foreground: oklch(0.16 0 0);\n --popover: oklch(1 0 0);\n --popover-foreground: oklch(0.16 0 0);\n --primary: oklch(0.19 0 0);\n --primary-foreground: oklch(0.985 0 0);\n --secondary: oklch(0.94 0 0);\n --secondary-foreground: oklch(0.19 0 0);\n --muted: oklch(0.94 0 0);\n --muted-foreground: oklch(0.6 0 0);\n --accent: oklch(0.94 0 0);\n --accent-foreground: oklch(0.19 0 0);\n --destructive: oklch(0.577 0.245 27.325);\n --border: oklch(0.916 0 0);\n --input: oklch(0.916 0 0);\n --ring: oklch(0.708 0 0);\n --radius: 0.625rem;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n color-scheme: dark;\n --background: oklch(0.145 0 0);\n --foreground: oklch(0.985 0 0);\n --card: oklch(0.205 0 0);\n --card-foreground: oklch(0.985 0 0);\n --popover: oklch(0.205 0 0);\n --popover-foreground: oklch(0.985 0 0);\n --primary: oklch(0.922 0 0);\n --primary-foreground: oklch(0.205 0 0);\n --secondary: oklch(0.269 0 0);\n --secondary-foreground: oklch(0.985 0 0);\n --muted: oklch(0.269 0 0);\n --muted-foreground: oklch(0.708 0 0);\n --accent: oklch(0.269 0 0);\n --accent-foreground: oklch(0.985 0 0);\n --destructive: oklch(0.704 0.191 22.216);\n --border: oklch(1 0 0 / 10%);\n --input: oklch(1 0 0 / 15%);\n --ring: oklch(0.556 0 0);\n }\n}\n\n* {\n border-color: var(--border);\n}\n\nhtml {\n height: 100%;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\nbody {\n min-height: 100%;\n margin: 0;\n background: var(--background);\n font-family: var(--font-sans);\n}\n\nbutton,\ninput,\ntextarea {\n font: inherit;\n}\n';
readonly "app/icon.svg": '<svg width="102" height="102" viewBox="0 0 102 102" fill="none" xmlns="http://www.w3.org/2000/svg">\n <path fill="#000" d="M0 0h102v102H0z" />\n <path\n fill="#fff"\n d="M49.28 66.94 75.03 34.96h-6.89L47.91 60.11l-5.49 6.83h6.86ZM0 34.96h42.4v5.11H0zm0 13.32h27.66v5.11H0zm0 13.54h27.66v5.11H0zm69.63-26.86H102v5.11H69.63zm4.71 13.32H102v5.11H74.34zm0 13.54H102v5.11H74.34z"\n />\n</svg>\n';
readonly "app/layout.tsx": 'import type { Metadata } from "next";\nimport { Geist, Geist_Mono } from "next/font/google";\nimport type { ReactNode } from "react";\nimport { TooltipProvider } from "@/components/ui/tooltip";\nimport { cn } from "@/lib/utils";\nimport "./globals.css";\n\nconst sans = Geist({\n variable: "--font-sans",\n subsets: ["latin"],\n weight: "variable",\n display: "swap",\n});\n\nconst mono = Geist_Mono({\n variable: "--font-mono",\n subsets: ["latin"],\n weight: "variable",\n display: "swap",\n});\n\nexport const metadata: Metadata = {\n title: "__EVE_INIT_APP_NAME__",\n description: "A Next.js starter for eve agents with AI Elements.",\n};\n\nexport default function RootLayout({ children }: { readonly children: ReactNode }) {\n return (\n <html className={cn(sans.variable, mono.variable)} lang="en">\n <body>\n <TooltipProvider>{children}</TooltipProvider>\n </body>\n </html>\n );\n}\n';
readonly "app/page.tsx": 'import { AgentChat } from "@/app/_components/agent-chat";\n\nexport default function Page() {\n return <AgentChat />;\n}\n';
readonly "app/s/[sessionId]/page.tsx": 'import { AgentChat } from "@/app/_components/agent-chat";\n\nexport default async function SessionPage({\n params,\n}: {\n readonly params: Promise<{ readonly sessionId: string }>;\n}) {\n const { sessionId } = await params;\n return <AgentChat sessionId={sessionId} />;\n}\n';
readonly "app/s/page.tsx": 'import { AgentChat } from "@/app/_components/agent-chat";\n\nexport default function NewSessionPage() {\n return <AgentChat sessionless />;\n}\n';
readonly "components/ai-elements/chain-of-thought.tsx": '"use client";\n\nimport { useControllableState } from "@radix-ui/react-use-controllable-state";\nimport { Badge } from "@/components/ui/badge";\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";\nimport { cn } from "@/lib/utils";\nimport type { LucideIcon } from "lucide-react";\nimport { BrainIcon, ChevronDownIcon, DotIcon } from "lucide-react";\nimport type { ComponentProps, ReactNode } from "react";\nimport { createContext, memo, useContext, useMemo } from "react";\n\ninterface ChainOfThoughtContextValue {\n isOpen: boolean;\n setIsOpen: (open: boolean) => void;\n}\n\nconst ChainOfThoughtContext = createContext<ChainOfThoughtContextValue | null>(null);\n\nconst useChainOfThought = () => {\n const context = useContext(ChainOfThoughtContext);\n if (!context) {\n throw new Error("ChainOfThought components must be used within ChainOfThought");\n }\n return context;\n};\n\nexport type ChainOfThoughtProps = ComponentProps<"div"> & {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n};\n\nexport const ChainOfThought = memo(\n ({\n className,\n open,\n defaultOpen = false,\n onOpenChange,\n children,\n ...props\n }: ChainOfThoughtProps) => {\n const [isOpen, setIsOpen] = useControllableState({\n defaultProp: defaultOpen,\n onChange: onOpenChange,\n prop: open,\n });\n\n const chainOfThoughtContext = useMemo(() => ({ isOpen, setIsOpen }), [isOpen, setIsOpen]);\n\n return (\n <ChainOfThoughtContext.Provider value={chainOfThoughtContext}>\n <div className={cn("not-prose w-full space-y-4", className)} {...props}>\n {children}\n </div>\n </ChainOfThoughtContext.Provider>\n );\n },\n);\n\nexport type ChainOfThoughtHeaderProps = ComponentProps<typeof CollapsibleTrigger>;\n\nexport const ChainOfThoughtHeader = memo(\n ({ className, children, ...props }: ChainOfThoughtHeaderProps) => {\n const { isOpen, setIsOpen } = useChainOfThought();\n\n return (\n <Collapsible onOpenChange={setIsOpen} open={isOpen}>\n <CollapsibleTrigger\n className={cn(\n "flex w-full items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground",\n className,\n )}\n {...props}\n >\n <BrainIcon className="size-4" />\n <span className="flex-1 text-left">{children ?? "Chain of Thought"}</span>\n <ChevronDownIcon\n className={cn("size-4 transition-transform", isOpen ? "rotate-180" : "rotate-0")}\n />\n </CollapsibleTrigger>\n </Collapsible>\n );\n },\n);\n\nexport type ChainOfThoughtStepProps = ComponentProps<"div"> & {\n icon?: LucideIcon;\n label: ReactNode;\n description?: ReactNode;\n status?: "complete" | "active" | "pending";\n};\n\nconst stepStatusStyles = {\n active: "text-foreground",\n complete: "text-muted-foreground",\n pending: "text-muted-foreground/50",\n};\n\nexport const ChainOfThoughtStep = memo(\n ({\n className,\n icon: Icon = DotIcon,\n label,\n description,\n status = "complete",\n children,\n ...props\n }: ChainOfThoughtStepProps) => (\n <div\n className={cn(\n "flex gap-2 text-sm",\n stepStatusStyles[status],\n "fade-in-0 slide-in-from-top-2 animate-in",\n className,\n )}\n {...props}\n >\n <div className="relative mt-0.5">\n <Icon className="size-4" />\n <div className="absolute top-7 bottom-0 left-1/2 -mx-px w-px bg-border" />\n </div>\n <div className="flex-1 space-y-2 overflow-hidden">\n <div>{label}</div>\n {description && <div className="text-muted-foreground text-xs">{description}</div>}\n {children}\n </div>\n </div>\n ),\n);\n\nexport type ChainOfThoughtSearchResultsProps = ComponentProps<"div">;\n\nexport const ChainOfThoughtSearchResults = memo(\n ({ className, ...props }: ChainOfThoughtSearchResultsProps) => (\n <div className={cn("flex flex-wrap items-center gap-2", className)} {...props} />\n ),\n);\n\nexport type ChainOfThoughtSearchResultProps = ComponentProps<typeof Badge>;\n\nexport const ChainOfThoughtSearchResult = memo(\n ({ className, children, ...props }: ChainOfThoughtSearchResultProps) => (\n <Badge\n className={cn("gap-1 px-2 py-0.5 font-normal text-xs", className)}\n variant="secondary"\n {...props}\n >\n {children}\n </Badge>\n ),\n);\n\nexport type ChainOfThoughtContentProps = ComponentProps<typeof CollapsibleContent>;\n\nexport const ChainOfThoughtContent = memo(\n ({ className, children, ...props }: ChainOfThoughtContentProps) => {\n const { isOpen } = useChainOfThought();\n\n return (\n <Collapsible open={isOpen}>\n <CollapsibleContent\n className={cn(\n "mt-2 space-y-3",\n "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",\n className,\n )}\n {...props}\n >\n {children}\n </CollapsibleContent>\n </Collapsible>\n );\n },\n);\n\nexport type ChainOfThoughtImageProps = ComponentProps<"div"> & {\n caption?: string;\n};\n\nexport const ChainOfThoughtImage = memo(\n ({ className, children, caption, ...props }: ChainOfThoughtImageProps) => (\n <div className={cn("mt-2 space-y-2", className)} {...props}>\n <div className="relative flex max-h-[22rem] items-center justify-center overflow-hidden rounded-lg bg-muted p-3">\n {children}\n </div>\n {caption && <p className="text-muted-foreground text-xs">{caption}</p>}\n </div>\n ),\n);\n\nChainOfThought.displayName = "ChainOfThought";\nChainOfThoughtHeader.displayName = "ChainOfThoughtHeader";\nChainOfThoughtStep.displayName = "ChainOfThoughtStep";\nChainOfThoughtSearchResults.displayName = "ChainOfThoughtSearchResults";\nChainOfThoughtSearchResult.displayName = "ChainOfThoughtSearchResult";\nChainOfThoughtContent.displayName = "ChainOfThoughtContent";\nChainOfThoughtImage.displayName = "ChainOfThoughtImage";\n';
readonly "components/ai-elements/code-block.tsx": '"use client";\n\nimport { Button } from "@/components/ui/button";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from "@/components/ui/select";\nimport { cn } from "@/lib/utils";\nimport { CheckIcon, CopyIcon } from "lucide-react";\nimport type { ComponentProps, CSSProperties, HTMLAttributes } from "react";\nimport {\n createContext,\n memo,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from "react";\nimport type { BundledLanguage, BundledTheme, HighlighterGeneric, ThemedToken } from "shiki";\nimport { createHighlighter } from "shiki";\n\n// Shiki uses bitflags for font styles: 1=italic, 2=bold, 4=underline\n// oxlint-disable-next-line eslint(no-bitwise)\nconst isItalic = (fontStyle: number | undefined) => fontStyle && fontStyle & 1;\n// oxlint-disable-next-line eslint(no-bitwise)\nconst isBold = (fontStyle: number | undefined) => fontStyle && fontStyle & 2;\nconst isUnderline = (fontStyle: number | undefined) =>\n // oxlint-disable-next-line eslint(no-bitwise)\n fontStyle && fontStyle & 4;\n\n// Transform tokens to include pre-computed keys to avoid noArrayIndexKey lint\ninterface KeyedToken {\n token: ThemedToken;\n key: string;\n}\ninterface KeyedLine {\n tokens: KeyedToken[];\n key: string;\n}\n\nconst addKeysToTokens = (lines: ThemedToken[][]): KeyedLine[] =>\n lines.map((line, lineIdx) => ({\n key: `line-${lineIdx}`,\n tokens: line.map((token, tokenIdx) => ({\n key: `line-${lineIdx}-${tokenIdx}`,\n token,\n })),\n }));\n\n// Token rendering component\nconst TokenSpan = ({ token }: { token: ThemedToken }) => (\n <span\n className="dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)]"\n style={\n {\n backgroundColor: token.bgColor,\n color: token.color,\n fontStyle: isItalic(token.fontStyle) ? "italic" : undefined,\n fontWeight: isBold(token.fontStyle) ? "bold" : undefined,\n textDecoration: isUnderline(token.fontStyle) ? "underline" : undefined,\n ...token.htmlStyle,\n } as CSSProperties\n }\n >\n {token.content}\n </span>\n);\n\n// Line number styles using CSS counters\nconst LINE_NUMBER_CLASSES = cn(\n "block",\n "before:content-[counter(line)]",\n "before:inline-block",\n "before:[counter-increment:line]",\n "before:w-8",\n "before:mr-4",\n "before:text-right",\n "before:text-muted-foreground/50",\n "before:font-mono",\n "before:select-none",\n);\n\n// Line rendering component\nconst LineSpan = ({\n keyedLine,\n showLineNumbers,\n}: {\n keyedLine: KeyedLine;\n showLineNumbers: boolean;\n}) => (\n <span className={showLineNumbers ? LINE_NUMBER_CLASSES : "block"}>\n {keyedLine.tokens.length === 0\n ? "\\n"\n : keyedLine.tokens.map(({ token, key }) => <TokenSpan key={key} token={token} />)}\n </span>\n);\n\n// Types\ntype CodeBlockProps = HTMLAttributes<HTMLDivElement> & {\n code: string;\n language: BundledLanguage;\n showLineNumbers?: boolean;\n};\n\ninterface TokenizedCode {\n tokens: ThemedToken[][];\n fg: string;\n bg: string;\n}\n\ninterface CodeBlockContextType {\n code: string;\n}\n\n// Context\nconst CodeBlockContext = createContext<CodeBlockContextType>({\n code: "",\n});\n\n// Highlighter cache (singleton per language)\nconst highlighterCache = new Map<\n string,\n Promise<HighlighterGeneric<BundledLanguage, BundledTheme>>\n>();\n\n// Token cache\nconst tokensCache = new Map<string, TokenizedCode>();\n\n// Subscribers for async token updates\nconst subscribers = new Map<string, Set<(result: TokenizedCode) => void>>();\n\nconst getTokensCacheKey = (code: string, language: BundledLanguage) => {\n const start = code.slice(0, 100);\n const end = code.length > 100 ? code.slice(-100) : "";\n return `${language}:${code.length}:${start}:${end}`;\n};\n\nconst getHighlighter = (\n language: BundledLanguage,\n): Promise<HighlighterGeneric<BundledLanguage, BundledTheme>> => {\n const cached = highlighterCache.get(language);\n if (cached) {\n return cached;\n }\n\n const highlighterPromise = createHighlighter({\n langs: [language],\n themes: ["github-light", "github-dark"],\n });\n\n highlighterCache.set(language, highlighterPromise);\n return highlighterPromise;\n};\n\n// Create raw tokens for immediate display while highlighting loads\nconst createRawTokens = (code: string): TokenizedCode => ({\n bg: "transparent",\n fg: "inherit",\n tokens: code.split("\\n").map((line) =>\n line === ""\n ? []\n : [\n {\n color: "inherit",\n content: line,\n } as ThemedToken,\n ],\n ),\n});\n\n// Synchronous highlight with callback for async results\nexport const highlightCode = (\n code: string,\n language: BundledLanguage,\n // oxlint-disable-next-line eslint-plugin-promise(prefer-await-to-callbacks)\n callback?: (result: TokenizedCode) => void,\n): TokenizedCode | null => {\n const tokensCacheKey = getTokensCacheKey(code, language);\n\n // Return cached result if available\n const cached = tokensCache.get(tokensCacheKey);\n if (cached) {\n return cached;\n }\n\n // Subscribe callback if provided\n if (callback) {\n if (!subscribers.has(tokensCacheKey)) {\n subscribers.set(tokensCacheKey, new Set());\n }\n subscribers.get(tokensCacheKey)?.add(callback);\n }\n\n // Start highlighting in background - fire-and-forget async pattern\n getHighlighter(language)\n // oxlint-disable-next-line eslint-plugin-promise(prefer-await-to-then)\n .then((highlighter) => {\n const availableLangs = highlighter.getLoadedLanguages();\n const langToUse = availableLangs.includes(language) ? language : "text";\n\n const result = highlighter.codeToTokens(code, {\n lang: langToUse,\n themes: {\n dark: "github-dark",\n light: "github-light",\n },\n });\n\n const tokenized: TokenizedCode = {\n bg: result.bg ?? "transparent",\n fg: result.fg ?? "inherit",\n tokens: result.tokens,\n };\n\n // Cache the result\n tokensCache.set(tokensCacheKey, tokenized);\n\n // Notify all subscribers\n const subs = subscribers.get(tokensCacheKey);\n if (subs) {\n for (const sub of subs) {\n sub(tokenized);\n }\n subscribers.delete(tokensCacheKey);\n }\n })\n // oxlint-disable-next-line eslint-plugin-promise(prefer-await-to-then), eslint-plugin-promise(prefer-await-to-callbacks)\n .catch((error) => {\n console.error("Failed to highlight code:", error);\n subscribers.delete(tokensCacheKey);\n });\n\n return null;\n};\n\nconst CodeBlockBody = memo(\n ({\n tokenized,\n showLineNumbers,\n className,\n }: {\n tokenized: TokenizedCode;\n showLineNumbers: boolean;\n className?: string;\n }) => {\n const preStyle = useMemo(\n () => ({\n backgroundColor: tokenized.bg,\n color: tokenized.fg,\n }),\n [tokenized.bg, tokenized.fg],\n );\n\n const keyedLines = useMemo(() => addKeysToTokens(tokenized.tokens), [tokenized.tokens]);\n\n return (\n <pre\n className={cn(\n "dark:!bg-[var(--shiki-dark-bg)] dark:!text-[var(--shiki-dark)] m-0 p-4 text-sm",\n className,\n )}\n style={preStyle}\n >\n <code\n className={cn(\n "font-mono text-sm",\n showLineNumbers && "[counter-increment:line_0] [counter-reset:line]",\n )}\n >\n {keyedLines.map((keyedLine) => (\n <LineSpan key={keyedLine.key} keyedLine={keyedLine} showLineNumbers={showLineNumbers} />\n ))}\n </code>\n </pre>\n );\n },\n (prevProps, nextProps) =>\n prevProps.tokenized === nextProps.tokenized &&\n prevProps.showLineNumbers === nextProps.showLineNumbers &&\n prevProps.className === nextProps.className,\n);\n\nCodeBlockBody.displayName = "CodeBlockBody";\n\nexport const CodeBlockContainer = ({\n className,\n language,\n style,\n ...props\n}: HTMLAttributes<HTMLDivElement> & { language: string }) => (\n <div\n className={cn(\n "group relative w-full overflow-hidden rounded-md border bg-background text-foreground",\n className,\n )}\n data-language={language}\n style={{\n containIntrinsicSize: "auto 200px",\n contentVisibility: "auto",\n ...style,\n }}\n {...props}\n />\n);\n\nexport const CodeBlockHeader = ({\n children,\n className,\n ...props\n}: HTMLAttributes<HTMLDivElement>) => (\n <div\n className={cn(\n "flex items-center justify-between border-b bg-muted/80 px-3 py-2 text-muted-foreground text-xs",\n className,\n )}\n {...props}\n >\n {children}\n </div>\n);\n\nexport const CodeBlockTitle = ({\n children,\n className,\n ...props\n}: HTMLAttributes<HTMLDivElement>) => (\n <div className={cn("flex items-center gap-2", className)} {...props}>\n {children}\n </div>\n);\n\nexport const CodeBlockFilename = ({\n children,\n className,\n ...props\n}: HTMLAttributes<HTMLSpanElement>) => (\n <span className={cn("font-mono", className)} {...props}>\n {children}\n </span>\n);\n\nexport const CodeBlockActions = ({\n children,\n className,\n ...props\n}: HTMLAttributes<HTMLDivElement>) => (\n <div className={cn("-my-1 -mr-1 flex items-center gap-2", className)} {...props}>\n {children}\n </div>\n);\n\nexport const CodeBlockContent = ({\n code,\n language,\n showLineNumbers = false,\n}: {\n code: string;\n language: BundledLanguage;\n showLineNumbers?: boolean;\n}) => {\n // Memoized raw tokens for immediate display\n const rawTokens = useMemo(() => createRawTokens(code), [code]);\n\n // Synchronous cache lookup — avoids setState in effect for cached results\n const syncTokens = useMemo(\n () => highlightCode(code, language) ?? rawTokens,\n [code, language, rawTokens],\n );\n\n // Async highlighting result (populated after shiki loads)\n const [asyncTokens, setAsyncTokens] = useState<TokenizedCode | null>(null);\n const asyncKeyRef = useRef({ code, language });\n\n // Invalidate stale async tokens synchronously during render\n if (asyncKeyRef.current.code !== code || asyncKeyRef.current.language !== language) {\n asyncKeyRef.current = { code, language };\n setAsyncTokens(null);\n }\n\n useEffect(() => {\n let cancelled = false;\n\n highlightCode(code, language, (result) => {\n if (!cancelled) {\n setAsyncTokens(result);\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, [code, language]);\n\n const tokenized = asyncTokens ?? syncTokens;\n\n return (\n <div className="relative overflow-auto">\n <CodeBlockBody showLineNumbers={showLineNumbers} tokenized={tokenized} />\n </div>\n );\n};\n\nexport const CodeBlock = ({\n code,\n language,\n showLineNumbers = false,\n className,\n children,\n ...props\n}: CodeBlockProps) => {\n const contextValue = useMemo(() => ({ code }), [code]);\n\n return (\n <CodeBlockContext.Provider value={contextValue}>\n <CodeBlockContainer className={className} language={language} {...props}>\n {children}\n <CodeBlockContent code={code} language={language} showLineNumbers={showLineNumbers} />\n </CodeBlockContainer>\n </CodeBlockContext.Provider>\n );\n};\n\nexport type CodeBlockCopyButtonProps = ComponentProps<typeof Button> & {\n onCopy?: () => void;\n onError?: (error: Error) => void;\n timeout?: number;\n};\n\nexport const CodeBlockCopyButton = ({\n onCopy,\n onError,\n timeout = 2000,\n children,\n className,\n ...props\n}: CodeBlockCopyButtonProps) => {\n const [isCopied, setIsCopied] = useState(false);\n const timeoutRef = useRef<number>(0);\n const { code } = useContext(CodeBlockContext);\n\n const copyToClipboard = useCallback(async () => {\n if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {\n onError?.(new Error("Clipboard API not available"));\n return;\n }\n\n try {\n if (!isCopied) {\n await navigator.clipboard.writeText(code);\n setIsCopied(true);\n onCopy?.();\n timeoutRef.current = window.setTimeout(() => setIsCopied(false), timeout);\n }\n } catch (error) {\n onError?.(error as Error);\n }\n }, [code, onCopy, onError, timeout, isCopied]);\n\n useEffect(\n () => () => {\n window.clearTimeout(timeoutRef.current);\n },\n [],\n );\n\n const Icon = isCopied ? CheckIcon : CopyIcon;\n\n return (\n <Button\n className={cn("shrink-0", className)}\n onClick={copyToClipboard}\n size="icon"\n variant="ghost"\n {...props}\n >\n {children ?? <Icon size={14} />}\n </Button>\n );\n};\n\nexport type CodeBlockLanguageSelectorProps = ComponentProps<typeof Select>;\n\nexport const CodeBlockLanguageSelector = (props: CodeBlockLanguageSelectorProps) => (\n <Select {...props} />\n);\n\nexport type CodeBlockLanguageSelectorTriggerProps = ComponentProps<typeof SelectTrigger>;\n\nexport const CodeBlockLanguageSelectorTrigger = ({\n className,\n ...props\n}: CodeBlockLanguageSelectorTriggerProps) => (\n <SelectTrigger\n className={cn("h-7 border-none bg-transparent px-2 text-xs shadow-none", className)}\n size="sm"\n {...props}\n />\n);\n\nexport type CodeBlockLanguageSelectorValueProps = ComponentProps<typeof SelectValue>;\n\nexport const CodeBlockLanguageSelectorValue = (props: CodeBlockLanguageSelectorValueProps) => (\n <SelectValue {...props} />\n);\n\nexport type CodeBlockLanguageSelectorContentProps = ComponentProps<typeof SelectContent>;\n\nexport const CodeBlockLanguageSelectorContent = ({\n align = "end",\n ...props\n}: CodeBlockLanguageSelectorContentProps) => <SelectContent align={align} {...props} />;\n\nexport type CodeBlockLanguageSelectorItemProps = ComponentProps<typeof SelectItem>;\n\nexport const CodeBlockLanguageSelectorItem = (props: CodeBlockLanguageSelectorItemProps) => (\n <SelectItem {...props} />\n);\n';
readonly "components/ai-elements/conversation.tsx": '"use client";\n\nimport { Button } from "@/components/ui/button";\nimport { cn } from "@/lib/utils";\nimport type { UIMessage } from "ai";\nimport { ArrowDownIcon, DownloadIcon } from "lucide-react";\nimport type { ComponentProps } from "react";\nimport { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";\nimport { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";\n\nexport type ConversationProps = ComponentProps<typeof StickToBottom> & {\n scrollRestorationKey?: string;\n};\n\nexport const Conversation = ({\n children,\n className,\n initial,\n scrollRestorationKey,\n ...props\n}: ConversationProps) => (\n <StickToBottom\n className={cn("relative flex-1 overflow-y-hidden", className)}\n initial={initial ?? (scrollRestorationKey === undefined ? "smooth" : false)}\n resize="smooth"\n role="log"\n {...props}\n >\n {typeof children === "function" ? (\n (context) => (\n <>\n {children(context)}\n {scrollRestorationKey === undefined ? null : (\n <ConversationScrollRestoration storageKey={scrollRestorationKey} />\n )}\n </>\n )\n ) : (\n <>\n {children}\n {scrollRestorationKey === undefined ? null : (\n <ConversationScrollRestoration storageKey={scrollRestorationKey} />\n )}\n </>\n )}\n </StickToBottom>\n);\n\nfunction ConversationScrollRestoration({ storageKey }: { readonly storageKey: string }) {\n const { scrollRef, scrollToBottom, state } = useStickToBottomContext();\n const restoredKeyRef = useRef<string | undefined>(undefined);\n\n useLayoutEffect(() => {\n const scrollElement = scrollRef.current;\n if (scrollElement === null) return;\n\n if (restoredKeyRef.current !== storageKey) {\n const saved = readScrollPosition(sessionStorage.getItem(storageKey));\n if (saved?.atBottom === false) {\n scrollElement.scrollTop = saved.scrollTop;\n requestAnimationFrame(() => {\n scrollElement.scrollTop = saved.scrollTop;\n });\n } else {\n scrollElement.scrollTop = scrollElement.scrollHeight;\n scrollToBottom({ animation: "instant", ignoreEscapes: true });\n }\n restoredKeyRef.current = storageKey;\n }\n\n const saveNow = () => {\n sessionStorage.setItem(\n storageKey,\n JSON.stringify({\n atBottom: state.isAtBottom || state.isNearBottom,\n scrollTop: scrollElement.scrollTop,\n }),\n );\n };\n let frame: number | undefined;\n const scheduleSave = () => {\n if (frame !== undefined) return;\n frame = requestAnimationFrame(() => {\n frame = undefined;\n saveNow();\n });\n };\n scrollElement.addEventListener("scroll", scheduleSave, { passive: true });\n window.addEventListener("pagehide", saveNow);\n return () => {\n scrollElement.removeEventListener("scroll", scheduleSave);\n window.removeEventListener("pagehide", saveNow);\n if (frame !== undefined) cancelAnimationFrame(frame);\n saveNow();\n };\n }, [scrollRef, scrollToBottom, state, storageKey]);\n\n return null;\n}\n\nfunction readScrollPosition(value: string | null):\n | {\n readonly atBottom: boolean;\n readonly scrollTop: number;\n }\n | undefined {\n if (value === null) return undefined;\n try {\n const parsed = JSON.parse(value) as { atBottom?: unknown; scrollTop?: unknown };\n return typeof parsed.atBottom === "boolean" && typeof parsed.scrollTop === "number"\n ? { atBottom: parsed.atBottom, scrollTop: parsed.scrollTop }\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport type ConversationContentProps = ComponentProps<typeof StickToBottom.Content>;\n\nexport const ConversationContent = ({ className, ...props }: ConversationContentProps) => (\n <StickToBottom.Content className={cn("flex flex-col gap-8 p-4", className)} {...props} />\n);\n\nexport type ConversationTopFadeProps = ComponentProps<"div">;\n\nexport const ConversationTopFade = ({ className, ...props }: ConversationTopFadeProps) => {\n const { contentRef, scrollRef } = useStickToBottomContext();\n const [isVisible, setIsVisible] = useState(false);\n\n useEffect(() => {\n const scrollElement = scrollRef.current;\n if (scrollElement === null) return;\n\n const updateVisibility = () => {\n setIsVisible(scrollElement.scrollTop > 0);\n };\n\n updateVisibility();\n scrollElement.addEventListener("scroll", updateVisibility, { passive: true });\n\n const resizeObserver = new ResizeObserver(updateVisibility);\n resizeObserver.observe(scrollElement);\n if (contentRef.current !== null) {\n resizeObserver.observe(contentRef.current);\n }\n\n return () => {\n scrollElement.removeEventListener("scroll", updateVisibility);\n resizeObserver.disconnect();\n };\n }, [contentRef, scrollRef]);\n\n return (\n <div\n {...props}\n aria-hidden\n className={cn(\n "pointer-events-none absolute inset-x-0 top-0 z-10 h-6 bg-linear-to-b from-background via-background/80 to-transparent transition-opacity duration-150",\n isVisible ? "opacity-100" : "opacity-0",\n className,\n )}\n data-slot="conversation-top-fade"\n />\n );\n};\n\nexport type ConversationEmptyStateProps = ComponentProps<"div"> & {\n title?: string;\n description?: string;\n icon?: React.ReactNode;\n};\n\nexport const ConversationEmptyState = ({\n className,\n title = "No messages yet",\n description = "Start a conversation to see messages here",\n icon,\n children,\n ...props\n}: ConversationEmptyStateProps) => (\n <div\n className={cn(\n "flex size-full flex-col items-center justify-center gap-3 p-8 text-center",\n className,\n )}\n {...props}\n >\n {children ?? (\n <>\n {icon && <div className="text-muted-foreground">{icon}</div>}\n <div className="space-y-1">\n <h3 className="font-medium text-sm">{title}</h3>\n {description && <p className="text-muted-foreground text-sm">{description}</p>}\n </div>\n </>\n )}\n </div>\n);\n\nexport type ConversationScrollButtonProps = ComponentProps<typeof Button>;\n\nexport const ConversationScrollButton = ({\n className,\n ...props\n}: ConversationScrollButtonProps) => {\n const { isAtBottom, scrollToBottom } = useStickToBottomContext();\n const [isReady, setIsReady] = useState(false);\n\n useEffect(() => setIsReady(true), []);\n\n const handleScrollToBottom = useCallback(() => {\n scrollToBottom();\n }, [scrollToBottom]);\n\n return (\n isReady &&\n !isAtBottom && (\n <Button\n aria-label="Scroll to bottom"\n className={cn(\n "absolute bottom-32 left-[50%] translate-x-[-50%] rounded-full dark:bg-background dark:hover:bg-muted",\n className,\n )}\n onClick={handleScrollToBottom}\n size="icon"\n type="button"\n variant="outline"\n {...props}\n >\n <ArrowDownIcon className="size-4" />\n </Button>\n )\n );\n};\n\nconst getMessageText = (message: UIMessage): string =>\n message.parts\n .filter((part) => part.type === "text")\n .map((part) => part.text)\n .join("");\n\nexport type ConversationDownloadProps = Omit<ComponentProps<typeof Button>, "onClick"> & {\n messages: UIMessage[];\n filename?: string;\n formatMessage?: (message: UIMessage, index: number) => string;\n};\n\nconst defaultFormatMessage = (message: UIMessage): string => {\n const roleLabel = message.role.charAt(0).toUpperCase() + message.role.slice(1);\n return `**${roleLabel}:** ${getMessageText(message)}`;\n};\n\nexport const messagesToMarkdown = (\n messages: UIMessage[],\n formatMessage: (message: UIMessage, index: number) => string = defaultFormatMessage,\n): string => messages.map((msg, i) => formatMessage(msg, i)).join("\\n\\n");\n\nexport const ConversationDownload = ({\n messages,\n filename = "conversation.md",\n formatMessage = defaultFormatMessage,\n className,\n children,\n ...props\n}: ConversationDownloadProps) => {\n const handleDownload = useCallback(() => {\n const markdown = messagesToMarkdown(messages, formatMessage);\n const blob = new Blob([markdown], { type: "text/markdown" });\n const url = URL.createObjectURL(blob);\n const link = document.createElement("a");\n link.href = url;\n link.download = filename;\n document.body.append(link);\n link.click();\n link.remove();\n URL.revokeObjectURL(url);\n }, [messages, filename, formatMessage]);\n\n return (\n <Button\n className={cn(\n "absolute top-4 right-4 rounded-full dark:bg-background dark:hover:bg-muted",\n className,\n )}\n onClick={handleDownload}\n size="icon"\n type="button"\n variant="outline"\n {...props}\n >\n {children ?? <DownloadIcon className="size-4" />}\n </Button>\n );\n};\n';
readonly "components/ai-elements/message.tsx": '"use client";\n\nimport { Button } from "@/components/ui/button";\nimport { ButtonGroup, ButtonGroupText } from "@/components/ui/button-group";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";\nimport { cn } from "@/lib/utils";\nimport { cjk } from "@streamdown/cjk";\nimport { code } from "@streamdown/code";\nimport { math } from "@streamdown/math";\nimport { mermaid } from "@streamdown/mermaid";\nimport type { UIMessage } from "ai";\nimport { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";\nimport type { ComponentProps, HTMLAttributes, ReactElement } from "react";\nimport { createContext, memo, useCallback, useContext, useEffect, useMemo, useState } from "react";\nimport { Streamdown } from "streamdown";\n\nexport type MessageProps = HTMLAttributes<HTMLDivElement> & {\n from: UIMessage["role"];\n};\n\nexport const Message = ({ className, from, ...props }: MessageProps) => (\n <div\n className={cn(\n "group flex w-full max-w-[95%] flex-col gap-2",\n from === "user" ? "is-user ml-auto justify-end" : "is-assistant",\n className,\n )}\n {...props}\n />\n);\n\nexport type MessageContentProps = HTMLAttributes<HTMLDivElement>;\n\nexport const MessageContent = ({ children, className, ...props }: MessageContentProps) => (\n <div\n className={cn(\n "is-user:dark flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",\n "group-[.is-user]:ml-auto group-[.is-user]:rounded-2xl group-[.is-user]:bg-primary group-[.is-user]:px-4 group-[.is-user]:py-2.5 group-[.is-user]:text-primary-foreground",\n "group-[.is-assistant]:w-full group-[.is-assistant]:text-foreground",\n "group-data-[optimistic=true]:opacity-70",\n className,\n )}\n {...props}\n >\n {children}\n </div>\n);\n\nexport type MessageActionsProps = ComponentProps<"div">;\n\nexport const MessageActions = ({ className, children, ...props }: MessageActionsProps) => (\n <div className={cn("flex items-center gap-1", className)} {...props}>\n {children}\n </div>\n);\n\nexport type MessageActionProps = ComponentProps<typeof Button> & {\n tooltip?: string;\n label?: string;\n};\n\nexport const MessageAction = ({\n tooltip,\n children,\n label,\n variant = "ghost",\n size = "icon-sm",\n ...props\n}: MessageActionProps) => {\n const button = (\n <Button size={size} type="button" variant={variant} {...props}>\n {children}\n <span className="sr-only">{label || tooltip}</span>\n </Button>\n );\n\n if (tooltip) {\n return (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent>\n <p>{tooltip}</p>\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n );\n }\n\n return button;\n};\n\ninterface MessageBranchContextType {\n currentBranch: number;\n totalBranches: number;\n goToPrevious: () => void;\n goToNext: () => void;\n branches: ReactElement[];\n setBranches: (branches: ReactElement[]) => void;\n}\n\nconst MessageBranchContext = createContext<MessageBranchContextType | null>(null);\n\nconst useMessageBranch = () => {\n const context = useContext(MessageBranchContext);\n\n if (!context) {\n throw new Error("MessageBranch components must be used within MessageBranch");\n }\n\n return context;\n};\n\nexport type MessageBranchProps = HTMLAttributes<HTMLDivElement> & {\n defaultBranch?: number;\n onBranchChange?: (branchIndex: number) => void;\n};\n\nexport const MessageBranch = ({\n defaultBranch = 0,\n onBranchChange,\n className,\n ...props\n}: MessageBranchProps) => {\n const [currentBranch, setCurrentBranch] = useState(defaultBranch);\n const [branches, setBranches] = useState<ReactElement[]>([]);\n\n const handleBranchChange = useCallback(\n (newBranch: number) => {\n setCurrentBranch(newBranch);\n onBranchChange?.(newBranch);\n },\n [onBranchChange],\n );\n\n const goToPrevious = useCallback(() => {\n const newBranch = currentBranch > 0 ? currentBranch - 1 : branches.length - 1;\n handleBranchChange(newBranch);\n }, [currentBranch, branches.length, handleBranchChange]);\n\n const goToNext = useCallback(() => {\n const newBranch = currentBranch < branches.length - 1 ? currentBranch + 1 : 0;\n handleBranchChange(newBranch);\n }, [currentBranch, branches.length, handleBranchChange]);\n\n const contextValue = useMemo<MessageBranchContextType>(\n () => ({\n branches,\n currentBranch,\n goToNext,\n goToPrevious,\n setBranches,\n totalBranches: branches.length,\n }),\n [branches, currentBranch, goToNext, goToPrevious],\n );\n\n return (\n <MessageBranchContext.Provider value={contextValue}>\n <div className={cn("grid w-full gap-2 [&>div]:pb-0", className)} {...props} />\n </MessageBranchContext.Provider>\n );\n};\n\nexport type MessageBranchContentProps = HTMLAttributes<HTMLDivElement>;\n\nexport const MessageBranchContent = ({ children, ...props }: MessageBranchContentProps) => {\n const { currentBranch, setBranches, branches } = useMessageBranch();\n const childrenArray = useMemo(\n () => (Array.isArray(children) ? children : [children]),\n [children],\n );\n\n // Use useEffect to update branches when they change\n useEffect(() => {\n if (branches.length !== childrenArray.length) {\n setBranches(childrenArray);\n }\n }, [childrenArray, branches, setBranches]);\n\n return childrenArray.map((branch, index) => (\n <div\n className={cn(\n "grid gap-2 overflow-hidden [&>div]:pb-0",\n index === currentBranch ? "block" : "hidden",\n )}\n key={branch.key}\n {...props}\n >\n {branch}\n </div>\n ));\n};\n\nexport type MessageBranchSelectorProps = ComponentProps<typeof ButtonGroup>;\n\nexport const MessageBranchSelector = ({ className, ...props }: MessageBranchSelectorProps) => {\n const { totalBranches } = useMessageBranch();\n\n // Don\'t render if there\'s only one branch\n if (totalBranches <= 1) {\n return null;\n }\n\n return (\n <ButtonGroup\n className={cn(\n "[&>*:not(:first-child)]:rounded-l-md [&>*:not(:last-child)]:rounded-r-md",\n className,\n )}\n orientation="horizontal"\n {...props}\n />\n );\n};\n\nexport type MessageBranchPreviousProps = ComponentProps<typeof Button>;\n\nexport const MessageBranchPrevious = ({ children, ...props }: MessageBranchPreviousProps) => {\n const { goToPrevious, totalBranches } = useMessageBranch();\n\n return (\n <Button\n aria-label="Previous branch"\n disabled={totalBranches <= 1}\n onClick={goToPrevious}\n size="icon-sm"\n type="button"\n variant="ghost"\n {...props}\n >\n {children ?? <ChevronLeftIcon size={14} />}\n </Button>\n );\n};\n\nexport type MessageBranchNextProps = ComponentProps<typeof Button>;\n\nexport const MessageBranchNext = ({ children, ...props }: MessageBranchNextProps) => {\n const { goToNext, totalBranches } = useMessageBranch();\n\n return (\n <Button\n aria-label="Next branch"\n disabled={totalBranches <= 1}\n onClick={goToNext}\n size="icon-sm"\n type="button"\n variant="ghost"\n {...props}\n >\n {children ?? <ChevronRightIcon size={14} />}\n </Button>\n );\n};\n\nexport type MessageBranchPageProps = HTMLAttributes<HTMLSpanElement>;\n\nexport const MessageBranchPage = ({ className, ...props }: MessageBranchPageProps) => {\n const { currentBranch, totalBranches } = useMessageBranch();\n\n return (\n <ButtonGroupText\n className={cn("border-none bg-transparent text-muted-foreground shadow-none", className)}\n {...props}\n >\n {currentBranch + 1} of {totalBranches}\n </ButtonGroupText>\n );\n};\n\nexport type MessageResponseProps = ComponentProps<typeof Streamdown>;\n\nconst streamdownPlugins = { cjk, code, math, mermaid };\n\nexport const MessageResponse = memo(\n ({ className, ...props }: MessageResponseProps) => (\n <Streamdown\n className={cn("size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0", className)}\n plugins={streamdownPlugins}\n {...props}\n />\n ),\n (prevProps, nextProps) =>\n prevProps.children === nextProps.children && nextProps.isAnimating === prevProps.isAnimating,\n);\n\nMessageResponse.displayName = "MessageResponse";\n\nexport type MessageToolbarProps = ComponentProps<"div">;\n\nexport const MessageToolbar = ({ className, children, ...props }: MessageToolbarProps) => (\n <div className={cn("mt-4 flex w-full items-center justify-between gap-4", className)} {...props}>\n {children}\n </div>\n);\n';
readonly "components/ai-elements/prompt-input.tsx": '"use client";\n\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n} from "@/components/ui/command";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from "@/components/ui/dropdown-menu";\nimport { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";\nimport {\n InputGroup,\n InputGroupAddon,\n InputGroupButton,\n InputGroupTextarea,\n} from "@/components/ui/input-group";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from "@/components/ui/select";\nimport { Spinner } from "@/components/ui/spinner";\nimport { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";\nimport { cn } from "@/lib/utils";\nimport type { ChatStatus, FileUIPart, SourceDocumentUIPart } from "ai";\nimport { ArrowUpIcon, ImageIcon, Monitor, PlusIcon, XIcon } from "lucide-react";\nimport { nanoid } from "nanoid";\nimport type {\n ChangeEvent,\n ChangeEventHandler,\n ClipboardEventHandler,\n ComponentProps,\n FormEvent,\n FormEventHandler,\n HTMLAttributes,\n KeyboardEventHandler,\n PropsWithChildren,\n ReactNode,\n RefObject,\n} from "react";\nimport {\n Children,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from "react";\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nconst convertBlobUrlToDataUrl = async (url: string): Promise<string | null> => {\n try {\n const response = await fetch(url);\n const blob = await response.blob();\n // FileReader uses callback-based API, wrapping in Promise is necessary\n // oxlint-disable-next-line eslint-plugin-promise(avoid-new)\n return new Promise((resolve) => {\n const reader = new FileReader();\n // oxlint-disable-next-line eslint-plugin-unicorn(prefer-add-event-listener)\n reader.onloadend = () => resolve(reader.result as string);\n // oxlint-disable-next-line eslint-plugin-unicorn(prefer-add-event-listener)\n reader.onerror = () => resolve(null);\n reader.readAsDataURL(blob);\n });\n } catch {\n return null;\n }\n};\n\nconst captureScreenshot = async (): Promise<File | null> => {\n if (typeof navigator === "undefined" || !navigator.mediaDevices?.getDisplayMedia) {\n return null;\n }\n\n let stream: MediaStream | null = null;\n const video = document.createElement("video");\n video.muted = true;\n video.playsInline = true;\n\n try {\n stream = await navigator.mediaDevices.getDisplayMedia({\n audio: false,\n video: true,\n });\n\n video.srcObject = stream;\n\n // Video element uses callback-based API, wrapping in Promise is necessary\n // oxlint-disable-next-line eslint-plugin-promise(avoid-new)\n await new Promise<void>((resolve, reject) => {\n // oxlint-disable-next-line eslint-plugin-unicorn(prefer-add-event-listener)\n video.onloadedmetadata = () => resolve();\n // oxlint-disable-next-line eslint-plugin-unicorn(prefer-add-event-listener)\n video.onerror = () => reject(new Error("Failed to load screen stream"));\n });\n\n await video.play();\n\n const width = video.videoWidth;\n const height = video.videoHeight;\n if (!width || !height) {\n return null;\n }\n\n const canvas = document.createElement("canvas");\n canvas.width = width;\n canvas.height = height;\n const context = canvas.getContext("2d");\n if (!context) {\n return null;\n }\n\n context.drawImage(video, 0, 0, width, height);\n // canvas.toBlob uses callback-based API, wrapping in Promise is necessary\n // oxlint-disable-next-line eslint-plugin-promise(avoid-new)\n const blob = await new Promise<Blob | null>((resolve) => {\n canvas.toBlob(resolve, "image/png");\n });\n if (!blob) {\n return null;\n }\n\n const timestamp = new Date()\n .toISOString()\n .replaceAll(/[:.]/g, "-")\n .replace("T", "_")\n .replace("Z", "");\n\n return new File([blob], `screenshot-${timestamp}.png`, {\n lastModified: Date.now(),\n type: "image/png",\n });\n } finally {\n if (stream) {\n for (const track of stream.getTracks()) {\n track.stop();\n }\n }\n video.pause();\n video.srcObject = null;\n }\n};\n\n// ============================================================================\n// Provider Context & Types\n// ============================================================================\n\nexport interface AttachmentsContext {\n files: (FileUIPart & { id: string })[];\n add: (files: File[] | FileList) => void;\n remove: (id: string) => void;\n clear: () => void;\n openFileDialog: () => void;\n fileInputRef: RefObject<HTMLInputElement | null>;\n}\n\nexport interface TextInputContext {\n value: string;\n setInput: (v: string) => void;\n clear: () => void;\n}\n\nexport interface PromptInputControllerProps {\n textInput: TextInputContext;\n attachments: AttachmentsContext;\n /** INTERNAL: Allows PromptInput to register its file textInput + "open" callback */\n __registerFileInput: (ref: RefObject<HTMLInputElement | null>, open: () => void) => void;\n}\n\nconst PromptInputController = createContext<PromptInputControllerProps | null>(null);\nconst ProviderAttachmentsContext = createContext<AttachmentsContext | null>(null);\n\nexport const usePromptInputController = () => {\n const ctx = useContext(PromptInputController);\n if (!ctx) {\n throw new Error(\n "Wrap your component inside <PromptInputProvider> to use usePromptInputController().",\n );\n }\n return ctx;\n};\n\n// Optional variants (do NOT throw). Useful for dual-mode components.\nconst useOptionalPromptInputController = () => useContext(PromptInputController);\n\nexport const useProviderAttachments = () => {\n const ctx = useContext(ProviderAttachmentsContext);\n if (!ctx) {\n throw new Error(\n "Wrap your component inside <PromptInputProvider> to use useProviderAttachments().",\n );\n }\n return ctx;\n};\n\nconst useOptionalProviderAttachments = () => useContext(ProviderAttachmentsContext);\n\nexport type PromptInputProviderProps = PropsWithChildren<{\n initialInput?: string;\n}>;\n\n/**\n * Optional global provider that lifts PromptInput state outside of PromptInput.\n * If you don\'t use it, PromptInput stays fully self-managed.\n */\nexport const PromptInputProvider = ({\n initialInput: initialTextInput = "",\n children,\n}: PromptInputProviderProps) => {\n // ----- textInput state\n const [textInput, setTextInput] = useState(initialTextInput);\n const clearInput = useCallback(() => setTextInput(""), []);\n\n // ----- attachments state (global when wrapped)\n const [attachmentFiles, setAttachmentFiles] = useState<(FileUIPart & { id: string })[]>([]);\n const fileInputRef = useRef<HTMLInputElement | null>(null);\n // oxlint-disable-next-line eslint(no-empty-function)\n const openRef = useRef<() => void>(() => {});\n\n const add = useCallback((files: File[] | FileList) => {\n const incoming = [...files];\n if (incoming.length === 0) {\n return;\n }\n\n setAttachmentFiles((prev) => [\n ...prev,\n ...incoming.map((file) => ({\n filename: file.name,\n id: nanoid(),\n mediaType: file.type,\n type: "file" as const,\n url: URL.createObjectURL(file),\n })),\n ]);\n }, []);\n\n const remove = useCallback((id: string) => {\n setAttachmentFiles((prev) => {\n const found = prev.find((f) => f.id === id);\n if (found?.url) {\n URL.revokeObjectURL(found.url);\n }\n return prev.filter((f) => f.id !== id);\n });\n }, []);\n\n const clear = useCallback(() => {\n setAttachmentFiles((prev) => {\n for (const f of prev) {\n if (f.url) {\n URL.revokeObjectURL(f.url);\n }\n }\n return [];\n });\n }, []);\n\n // Keep a ref to attachments for cleanup on unmount (avoids stale closure)\n const attachmentsRef = useRef(attachmentFiles);\n\n useEffect(() => {\n attachmentsRef.current = attachmentFiles;\n }, [attachmentFiles]);\n\n // Cleanup blob URLs on unmount to prevent memory leaks\n useEffect(\n () => () => {\n for (const f of attachmentsRef.current) {\n if (f.url) {\n URL.revokeObjectURL(f.url);\n }\n }\n },\n [],\n );\n\n const openFileDialog = useCallback(() => {\n openRef.current?.();\n }, []);\n\n const attachments = useMemo<AttachmentsContext>(\n () => ({\n add,\n clear,\n fileInputRef,\n files: attachmentFiles,\n openFileDialog,\n remove,\n }),\n [attachmentFiles, add, remove, clear, openFileDialog],\n );\n\n const __registerFileInput = useCallback(\n (ref: RefObject<HTMLInputElement | null>, open: () => void) => {\n fileInputRef.current = ref.current;\n openRef.current = open;\n },\n [],\n );\n\n const controller = useMemo<PromptInputControllerProps>(\n () => ({\n __registerFileInput,\n attachments,\n textInput: {\n clear: clearInput,\n setInput: setTextInput,\n value: textInput,\n },\n }),\n [textInput, clearInput, attachments, __registerFileInput],\n );\n\n return (\n <PromptInputController.Provider value={controller}>\n <ProviderAttachmentsContext.Provider value={attachments}>\n {children}\n </ProviderAttachmentsContext.Provider>\n </PromptInputController.Provider>\n );\n};\n\n// ============================================================================\n// Component Context & Hooks\n// ============================================================================\n\nconst LocalAttachmentsContext = createContext<AttachmentsContext | null>(null);\n\nexport const usePromptInputAttachments = () => {\n // Prefer local context (inside PromptInput) as it has validation, fall back to provider\n const provider = useOptionalProviderAttachments();\n const local = useContext(LocalAttachmentsContext);\n const context = local ?? provider;\n if (!context) {\n throw new Error(\n "usePromptInputAttachments must be used within a PromptInput or PromptInputProvider",\n );\n }\n return context;\n};\n\n// ============================================================================\n// Referenced Sources (Local to PromptInput)\n// ============================================================================\n\nexport interface ReferencedSourcesContext {\n sources: (SourceDocumentUIPart & { id: string })[];\n add: (sources: SourceDocumentUIPart[] | SourceDocumentUIPart) => void;\n remove: (id: string) => void;\n clear: () => void;\n}\n\nexport const LocalReferencedSourcesContext = createContext<ReferencedSourcesContext | null>(null);\n\nexport const usePromptInputReferencedSources = () => {\n const ctx = useContext(LocalReferencedSourcesContext);\n if (!ctx) {\n throw new Error(\n "usePromptInputReferencedSources must be used within a LocalReferencedSourcesContext.Provider",\n );\n }\n return ctx;\n};\n\nexport type PromptInputActionAddAttachmentsProps = ComponentProps<typeof DropdownMenuItem> & {\n label?: string;\n};\n\nexport const PromptInputActionAddAttachments = ({\n label = "Add photos or files",\n ...props\n}: PromptInputActionAddAttachmentsProps) => {\n const attachments = usePromptInputAttachments();\n\n const handleSelect = useCallback(\n (e: Event) => {\n e.preventDefault();\n attachments.openFileDialog();\n },\n [attachments],\n );\n\n return (\n <DropdownMenuItem {...props} onSelect={handleSelect}>\n <ImageIcon className="mr-2 size-4" /> {label}\n </DropdownMenuItem>\n );\n};\n\nexport type PromptInputActionAddScreenshotProps = ComponentProps<typeof DropdownMenuItem> & {\n label?: string;\n};\n\nexport const PromptInputActionAddScreenshot = ({\n label = "Take screenshot",\n onSelect,\n ...props\n}: PromptInputActionAddScreenshotProps) => {\n const attachments = usePromptInputAttachments();\n\n const handleSelect = useCallback(\n async (event: Event) => {\n onSelect?.(event);\n if (event.defaultPrevented) {\n return;\n }\n\n try {\n const screenshot = await captureScreenshot();\n if (screenshot) {\n attachments.add([screenshot]);\n }\n } catch (error) {\n if (\n error instanceof DOMException &&\n (error.name === "NotAllowedError" || error.name === "AbortError")\n ) {\n return;\n }\n throw error;\n }\n },\n [onSelect, attachments],\n );\n\n return (\n <DropdownMenuItem {...props} onSelect={handleSelect}>\n <Monitor className="mr-2 size-4" />\n {label}\n </DropdownMenuItem>\n );\n};\n\nexport interface PromptInputMessage {\n text: string;\n files: FileUIPart[];\n}\n\nexport type PromptInputProps = Omit<HTMLAttributes<HTMLFormElement>, "onSubmit" | "onError"> & {\n // e.g., "image/*" or leave undefined for any\n accept?: string;\n multiple?: boolean;\n // When true, accepts drops anywhere on document. Default false (opt-in).\n globalDrop?: boolean;\n // Render a hidden input with given name and keep it in sync for native form posts. Default false.\n syncHiddenInput?: boolean;\n // Minimal constraints\n maxFiles?: number;\n // bytes\n maxFileSize?: number;\n onError?: (err: { code: "max_files" | "max_file_size" | "accept"; message: string }) => void;\n onSubmit: (\n message: PromptInputMessage,\n event: FormEvent<HTMLFormElement>,\n ) => void | Promise<void>;\n};\n\nexport const PromptInput = ({\n className,\n accept,\n multiple,\n globalDrop,\n syncHiddenInput,\n maxFiles,\n maxFileSize,\n onError,\n onSubmit,\n children,\n ...props\n}: PromptInputProps) => {\n // Try to use a provider controller if present\n const controller = useOptionalPromptInputController();\n const usingProvider = !!controller;\n\n // Refs\n const inputRef = useRef<HTMLInputElement | null>(null);\n const formRef = useRef<HTMLFormElement | null>(null);\n\n // ----- Local attachments (only used when no provider)\n const [items, setItems] = useState<(FileUIPart & { id: string })[]>([]);\n const files = usingProvider ? controller.attachments.files : items;\n\n // ----- Local referenced sources (always local to PromptInput)\n const [referencedSources, setReferencedSources] = useState<\n (SourceDocumentUIPart & { id: string })[]\n >([]);\n\n // Keep a ref to files for cleanup on unmount (avoids stale closure)\n const filesRef = useRef(files);\n\n useEffect(() => {\n filesRef.current = files;\n }, [files]);\n\n const openFileDialogLocal = useCallback(() => {\n inputRef.current?.click();\n }, []);\n\n const matchesAccept = useCallback(\n (f: File) => {\n if (!accept || accept.trim() === "") {\n return true;\n }\n\n const patterns = accept\n .split(",")\n .map((s) => s.trim())\n .filter(Boolean);\n\n return patterns.some((pattern) => {\n if (pattern.endsWith("/*")) {\n // e.g: image/* -> image/\n const prefix = pattern.slice(0, -1);\n return f.type.startsWith(prefix);\n }\n return f.type === pattern;\n });\n },\n [accept],\n );\n\n const addLocal = useCallback(\n (fileList: File[] | FileList) => {\n const incoming = [...fileList];\n const accepted = incoming.filter((f) => matchesAccept(f));\n if (incoming.length && accepted.length === 0) {\n onError?.({\n code: "accept",\n message: "No files match the accepted types.",\n });\n return;\n }\n const withinSize = (f: File) => (maxFileSize ? f.size <= maxFileSize : true);\n const sized = accepted.filter(withinSize);\n if (accepted.length > 0 && sized.length === 0) {\n onError?.({\n code: "max_file_size",\n message: "All files exceed the maximum size.",\n });\n return;\n }\n\n setItems((prev) => {\n const capacity =\n typeof maxFiles === "number" ? Math.max(0, maxFiles - prev.length) : undefined;\n const capped = typeof capacity === "number" ? sized.slice(0, capacity) : sized;\n if (typeof capacity === "number" && sized.length > capacity) {\n onError?.({\n code: "max_files",\n message: "Too many files. Some were not added.",\n });\n }\n const next: (FileUIPart & { id: string })[] = [];\n for (const file of capped) {\n next.push({\n filename: file.name,\n id: nanoid(),\n mediaType: file.type,\n type: "file",\n url: URL.createObjectURL(file),\n });\n }\n return [...prev, ...next];\n });\n },\n [matchesAccept, maxFiles, maxFileSize, onError],\n );\n\n const removeLocal = useCallback(\n (id: string) =>\n setItems((prev) => {\n const found = prev.find((file) => file.id === id);\n if (found?.url) {\n URL.revokeObjectURL(found.url);\n }\n return prev.filter((file) => file.id !== id);\n }),\n [],\n );\n\n // Wrapper that validates files before calling provider\'s add\n const addWithProviderValidation = useCallback(\n (fileList: File[] | FileList) => {\n const incoming = [...fileList];\n const accepted = incoming.filter((f) => matchesAccept(f));\n if (incoming.length && accepted.length === 0) {\n onError?.({\n code: "accept",\n message: "No files match the accepted types.",\n });\n return;\n }\n const withinSize = (f: File) => (maxFileSize ? f.size <= maxFileSize : true);\n const sized = accepted.filter(withinSize);\n if (accepted.length > 0 && sized.length === 0) {\n onError?.({\n code: "max_file_size",\n message: "All files exceed the maximum size.",\n });\n return;\n }\n\n const currentCount = files.length;\n const capacity =\n typeof maxFiles === "number" ? Math.max(0, maxFiles - currentCount) : undefined;\n const capped = typeof capacity === "number" ? sized.slice(0, capacity) : sized;\n if (typeof capacity === "number" && sized.length > capacity) {\n onError?.({\n code: "max_files",\n message: "Too many files. Some were not added.",\n });\n }\n\n if (capped.length > 0) {\n controller?.attachments.add(capped);\n }\n },\n [matchesAccept, maxFileSize, maxFiles, onError, files.length, controller],\n );\n\n const clearAttachments = useCallback(\n () =>\n usingProvider\n ? controller?.attachments.clear()\n : setItems((prev) => {\n for (const file of prev) {\n if (file.url) {\n URL.revokeObjectURL(file.url);\n }\n }\n return [];\n }),\n [usingProvider, controller],\n );\n\n const clearReferencedSources = useCallback(() => setReferencedSources([]), []);\n\n const add = usingProvider ? addWithProviderValidation : addLocal;\n const remove = usingProvider ? controller.attachments.remove : removeLocal;\n const openFileDialog = usingProvider\n ? controller.attachments.openFileDialog\n : openFileDialogLocal;\n\n const clear = useCallback(() => {\n clearAttachments();\n clearReferencedSources();\n }, [clearAttachments, clearReferencedSources]);\n\n // Let provider know about our hidden file input so external menus can call openFileDialog()\n useEffect(() => {\n if (!usingProvider) {\n return;\n }\n controller.__registerFileInput(inputRef, () => inputRef.current?.click());\n }, [usingProvider, controller]);\n\n // Note: File input cannot be programmatically set for security reasons\n // The syncHiddenInput prop is no longer functional\n useEffect(() => {\n if (syncHiddenInput && inputRef.current && files.length === 0) {\n inputRef.current.value = "";\n }\n }, [files, syncHiddenInput]);\n\n // Attach drop handlers on nearest form and document (opt-in)\n useEffect(() => {\n const form = formRef.current;\n if (!form) {\n return;\n }\n if (globalDrop) {\n // when global drop is on, let the document-level handler own drops\n return;\n }\n\n const onDragOver = (e: DragEvent) => {\n if (e.dataTransfer?.types?.includes("Files")) {\n e.preventDefault();\n }\n };\n const onDrop = (e: DragEvent) => {\n if (e.dataTransfer?.types?.includes("Files")) {\n e.preventDefault();\n }\n if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {\n add(e.dataTransfer.files);\n }\n };\n form.addEventListener("dragover", onDragOver);\n form.addEventListener("drop", onDrop);\n return () => {\n form.removeEventListener("dragover", onDragOver);\n form.removeEventListener("drop", onDrop);\n };\n }, [add, globalDrop]);\n\n useEffect(() => {\n if (!globalDrop) {\n return;\n }\n\n const onDragOver = (e: DragEvent) => {\n if (e.dataTransfer?.types?.includes("Files")) {\n e.preventDefault();\n }\n };\n const onDrop = (e: DragEvent) => {\n if (e.dataTransfer?.types?.includes("Files")) {\n e.preventDefault();\n }\n if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {\n add(e.dataTransfer.files);\n }\n };\n document.addEventListener("dragover", onDragOver);\n document.addEventListener("drop", onDrop);\n return () => {\n document.removeEventListener("dragover", onDragOver);\n document.removeEventListener("drop", onDrop);\n };\n }, [add, globalDrop]);\n\n useEffect(\n () => () => {\n if (!usingProvider) {\n for (const f of filesRef.current) {\n if (f.url) {\n URL.revokeObjectURL(f.url);\n }\n }\n }\n },\n [usingProvider],\n );\n\n const handleChange: ChangeEventHandler<HTMLInputElement> = useCallback(\n (event) => {\n if (event.currentTarget.files) {\n add(event.currentTarget.files);\n }\n // Reset input value to allow selecting files that were previously removed\n event.currentTarget.value = "";\n },\n [add],\n );\n\n const attachmentsCtx = useMemo<AttachmentsContext>(\n () => ({\n add,\n clear: clearAttachments,\n fileInputRef: inputRef,\n files: files.map((item) => ({ ...item, id: item.id })),\n openFileDialog,\n remove,\n }),\n [files, add, remove, clearAttachments, openFileDialog],\n );\n\n const refsCtx = useMemo<ReferencedSourcesContext>(\n () => ({\n add: (incoming: SourceDocumentUIPart[] | SourceDocumentUIPart) => {\n const array = Array.isArray(incoming) ? incoming : [incoming];\n setReferencedSources((prev) => [...prev, ...array.map((s) => ({ ...s, id: nanoid() }))]);\n },\n clear: clearReferencedSources,\n remove: (id: string) => {\n setReferencedSources((prev) => prev.filter((s) => s.id !== id));\n },\n sources: referencedSources,\n }),\n [referencedSources, clearReferencedSources],\n );\n\n const handleSubmit: FormEventHandler<HTMLFormElement> = useCallback(\n async (event) => {\n event.preventDefault();\n\n const form = event.currentTarget;\n const text = usingProvider\n ? controller.textInput.value\n : (() => {\n const formData = new FormData(form);\n return (formData.get("message") as string) || "";\n })();\n\n // Reset form immediately after capturing text to avoid race condition\n // where user input during async blob conversion would be lost\n if (!usingProvider) {\n form.reset();\n }\n\n try {\n // Convert blob URLs to data URLs asynchronously\n const convertedFiles: FileUIPart[] = await Promise.all(\n files.map(async ({ id: _id, ...item }) => {\n if (item.url?.startsWith("blob:")) {\n const dataUrl = await convertBlobUrlToDataUrl(item.url);\n // If conversion failed, keep the original blob URL\n return {\n ...item,\n url: dataUrl ?? item.url,\n };\n }\n return item;\n }),\n );\n\n const result = onSubmit({ files: convertedFiles, text }, event);\n\n // Handle both sync and async onSubmit\n if (result instanceof Promise) {\n try {\n await result;\n clear();\n if (usingProvider) {\n controller.textInput.clear();\n }\n } catch {\n // Don\'t clear on error - user may want to retry\n }\n } else {\n // Sync function completed without throwing, clear inputs\n clear();\n if (usingProvider) {\n controller.textInput.clear();\n }\n }\n } catch {\n // Don\'t clear on error - user may want to retry\n }\n },\n [usingProvider, controller, files, onSubmit, clear],\n );\n\n // Render with or without local provider\n const inner = (\n <>\n <input\n accept={accept}\n aria-label="Upload files"\n className="hidden"\n multiple={multiple}\n onChange={handleChange}\n ref={inputRef}\n title="Upload files"\n type="file"\n />\n <form className="w-full" onSubmit={handleSubmit} ref={formRef} {...props}>\n <InputGroup\n className={cn(\n "overflow-hidden rounded-2xl bg-card/80 shadow-sm backdrop-blur-md",\n "focus-within:border-foreground! has-[[data-slot=input-group-control]:focus-visible]:border-foreground!",\n className,\n )}\n >\n {children}\n </InputGroup>\n </form>\n </>\n );\n\n const withReferencedSources = (\n <LocalReferencedSourcesContext.Provider value={refsCtx}>\n {inner}\n </LocalReferencedSourcesContext.Provider>\n );\n\n // Always provide LocalAttachmentsContext so children get validated add function\n return (\n <LocalAttachmentsContext.Provider value={attachmentsCtx}>\n {withReferencedSources}\n </LocalAttachmentsContext.Provider>\n );\n};\n\nexport type PromptInputBodyProps = HTMLAttributes<HTMLDivElement>;\n\nexport const PromptInputBody = ({ className, ...props }: PromptInputBodyProps) => (\n <div className={cn("contents", className)} {...props} />\n);\n\nexport type PromptInputTextareaProps = ComponentProps<typeof InputGroupTextarea>;\n\nexport const PromptInputTextarea = ({\n onChange,\n onKeyDown,\n className,\n placeholder = "What would you like to know?",\n ...props\n}: PromptInputTextareaProps) => {\n const controller = useOptionalPromptInputController();\n const attachments = usePromptInputAttachments();\n const [isComposing, setIsComposing] = useState(false);\n\n const handleKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = useCallback(\n (e) => {\n // Call the external onKeyDown handler first\n onKeyDown?.(e);\n\n // If the external handler prevented default, don\'t run internal logic\n if (e.defaultPrevented) {\n return;\n }\n\n if (e.key === "Enter") {\n if (isComposing || e.nativeEvent.isComposing) {\n return;\n }\n if (e.shiftKey) {\n return;\n }\n e.preventDefault();\n\n // Check if the submit button is disabled before submitting\n const { form } = e.currentTarget;\n const submitButton = form?.querySelector(\n \'button[type="submit"]\',\n ) as HTMLButtonElement | null;\n if (submitButton?.disabled) {\n return;\n }\n\n form?.requestSubmit();\n }\n\n // Remove last attachment when Backspace is pressed and textarea is empty\n if (e.key === "Backspace" && e.currentTarget.value === "" && attachments.files.length > 0) {\n e.preventDefault();\n const lastAttachment = attachments.files.at(-1);\n if (lastAttachment) {\n attachments.remove(lastAttachment.id);\n }\n }\n },\n [onKeyDown, isComposing, attachments],\n );\n\n const handlePaste: ClipboardEventHandler<HTMLTextAreaElement> = useCallback(\n (event) => {\n const items = event.clipboardData?.items;\n\n if (!items) {\n return;\n }\n\n const files: File[] = [];\n\n for (const item of items) {\n if (item.kind === "file") {\n const file = item.getAsFile();\n if (file) {\n files.push(file);\n }\n }\n }\n\n if (files.length > 0) {\n event.preventDefault();\n attachments.add(files);\n }\n },\n [attachments],\n );\n\n const handleCompositionEnd = useCallback(() => setIsComposing(false), []);\n const handleCompositionStart = useCallback(() => setIsComposing(true), []);\n\n const controlledProps = controller\n ? {\n onChange: (e: ChangeEvent<HTMLTextAreaElement>) => {\n controller.textInput.setInput(e.currentTarget.value);\n onChange?.(e);\n },\n value: controller.textInput.value,\n }\n : {\n onChange,\n };\n\n return (\n <InputGroupTextarea\n className={cn("field-sizing-content max-h-48 min-h-18 text-sm!", className)}\n name="message"\n onCompositionEnd={handleCompositionEnd}\n onCompositionStart={handleCompositionStart}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n placeholder={placeholder}\n {...props}\n {...controlledProps}\n />\n );\n};\n\nexport type PromptInputHeaderProps = Omit<ComponentProps<typeof InputGroupAddon>, "align">;\n\nexport const PromptInputHeader = ({ className, ...props }: PromptInputHeaderProps) => (\n <InputGroupAddon\n align="block-end"\n className={cn("order-first flex-wrap gap-1", className)}\n {...props}\n />\n);\n\nexport type PromptInputFooterProps = Omit<ComponentProps<typeof InputGroupAddon>, "align">;\n\nexport const PromptInputFooter = ({ className, ...props }: PromptInputFooterProps) => (\n <InputGroupAddon\n align="block-end"\n className={cn("justify-between gap-1", className)}\n {...props}\n />\n);\n\nexport type PromptInputToolsProps = HTMLAttributes<HTMLDivElement>;\n\nexport const PromptInputTools = ({ className, ...props }: PromptInputToolsProps) => (\n <div className={cn("flex min-w-0 items-center gap-1", className)} {...props} />\n);\n\nexport type PromptInputButtonTooltip =\n | string\n | {\n content: ReactNode;\n shortcut?: string;\n side?: ComponentProps<typeof TooltipContent>["side"];\n };\n\nexport type PromptInputButtonProps = ComponentProps<typeof InputGroupButton> & {\n tooltip?: PromptInputButtonTooltip;\n};\n\nexport const PromptInputButton = ({\n variant = "ghost",\n className,\n size,\n tooltip,\n ...props\n}: PromptInputButtonProps) => {\n const newSize = size ?? (Children.count(props.children) > 1 ? "sm" : "icon-sm");\n\n const button = (\n <InputGroupButton\n className={cn(\n "rounded-full",\n variant === "outline" && "bg-card hover:bg-card dark:hover:bg-input/30",\n className,\n )}\n size={newSize}\n type="button"\n variant={variant}\n {...props}\n />\n );\n\n if (!tooltip) {\n return button;\n }\n\n const tooltipContent = typeof tooltip === "string" ? tooltip : tooltip.content;\n const shortcut = typeof tooltip === "string" ? undefined : tooltip.shortcut;\n const side = typeof tooltip === "string" ? "top" : (tooltip.side ?? "top");\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent side={side}>\n {tooltipContent}\n {shortcut && <span className="ml-2 text-muted-foreground">{shortcut}</span>}\n </TooltipContent>\n </Tooltip>\n );\n};\n\nexport type PromptInputActionMenuProps = ComponentProps<typeof DropdownMenu>;\nexport const PromptInputActionMenu = (props: PromptInputActionMenuProps) => (\n <DropdownMenu {...props} />\n);\n\nexport type PromptInputActionMenuTriggerProps = PromptInputButtonProps;\n\nexport const PromptInputActionMenuTrigger = ({\n className,\n children,\n ...props\n}: PromptInputActionMenuTriggerProps) => (\n <DropdownMenuTrigger asChild>\n <PromptInputButton className={className} {...props}>\n {children ?? <PlusIcon className="size-4" />}\n </PromptInputButton>\n </DropdownMenuTrigger>\n);\n\nexport type PromptInputActionMenuContentProps = ComponentProps<typeof DropdownMenuContent>;\nexport const PromptInputActionMenuContent = ({\n className,\n ...props\n}: PromptInputActionMenuContentProps) => (\n <DropdownMenuContent align="start" className={cn(className)} {...props} />\n);\n\nexport type PromptInputActionMenuItemProps = ComponentProps<typeof DropdownMenuItem>;\nexport const PromptInputActionMenuItem = ({\n className,\n ...props\n}: PromptInputActionMenuItemProps) => <DropdownMenuItem className={cn(className)} {...props} />;\n\n// Note: Actions that perform side-effects (like opening a file dialog)\n// are provided in opt-in modules (e.g., prompt-input-attachments).\n\nexport type PromptInputSubmitProps = ComponentProps<typeof InputGroupButton> & {\n status?: ChatStatus;\n onStop?: () => void;\n};\n\nexport const PromptInputSubmit = ({\n className,\n variant = "default",\n size = "icon-sm",\n status,\n onStop,\n onClick,\n children,\n ...props\n}: PromptInputSubmitProps) => {\n const isGenerating = status === "submitted" || status === "streaming";\n\n let Icon = <ArrowUpIcon className="size-4" />;\n\n if (status === "submitted") {\n Icon = <Spinner />;\n } else if (status === "streaming") {\n Icon = <span aria-hidden className="size-2.5 rounded-[2px] bg-current" />;\n } else if (status === "error") {\n Icon = <XIcon className="size-4" />;\n }\n\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLButtonElement>) => {\n if (isGenerating && onStop) {\n e.preventDefault();\n onStop();\n return;\n }\n onClick?.(e);\n },\n [isGenerating, onStop, onClick],\n );\n\n return (\n <InputGroupButton\n aria-label={isGenerating ? "Stop" : "Submit"}\n className={cn("absolute right-2.5 bottom-2.5 rounded-full", className)}\n onClick={handleClick}\n size={size}\n type={isGenerating && onStop ? "button" : "submit"}\n variant={variant}\n {...props}\n >\n {children ?? Icon}\n </InputGroupButton>\n );\n};\n\nexport type PromptInputSelectProps = ComponentProps<typeof Select>;\n\nexport const PromptInputSelect = (props: PromptInputSelectProps) => <Select {...props} />;\n\nexport type PromptInputSelectTriggerProps = ComponentProps<typeof SelectTrigger>;\n\nexport const PromptInputSelectTrigger = ({\n className,\n ...props\n}: PromptInputSelectTriggerProps) => (\n <SelectTrigger\n className={cn(\n "border-none bg-transparent font-medium text-muted-foreground shadow-none transition-colors",\n "hover:bg-accent hover:text-foreground aria-expanded:bg-accent aria-expanded:text-foreground",\n className,\n )}\n {...props}\n />\n);\n\nexport type PromptInputSelectContentProps = ComponentProps<typeof SelectContent>;\n\nexport const PromptInputSelectContent = ({\n className,\n ...props\n}: PromptInputSelectContentProps) => <SelectContent className={cn(className)} {...props} />;\n\nexport type PromptInputSelectItemProps = ComponentProps<typeof SelectItem>;\n\nexport const PromptInputSelectItem = ({ className, ...props }: PromptInputSelectItemProps) => (\n <SelectItem className={cn(className)} {...props} />\n);\n\nexport type PromptInputSelectValueProps = ComponentProps<typeof SelectValue>;\n\nexport const PromptInputSelectValue = ({ className, ...props }: PromptInputSelectValueProps) => (\n <SelectValue className={cn(className)} {...props} />\n);\n\nexport type PromptInputHoverCardProps = ComponentProps<typeof HoverCard>;\n\nexport const PromptInputHoverCard = ({\n openDelay = 0,\n closeDelay = 0,\n ...props\n}: PromptInputHoverCardProps) => (\n <HoverCard closeDelay={closeDelay} openDelay={openDelay} {...props} />\n);\n\nexport type PromptInputHoverCardTriggerProps = ComponentProps<typeof HoverCardTrigger>;\n\nexport const PromptInputHoverCardTrigger = (props: PromptInputHoverCardTriggerProps) => (\n <HoverCardTrigger {...props} />\n);\n\nexport type PromptInputHoverCardContentProps = ComponentProps<typeof HoverCardContent>;\n\nexport const PromptInputHoverCardContent = ({\n align = "start",\n ...props\n}: PromptInputHoverCardContentProps) => <HoverCardContent align={align} {...props} />;\n\nexport type PromptInputTabsListProps = HTMLAttributes<HTMLDivElement>;\n\nexport const PromptInputTabsList = ({ className, ...props }: PromptInputTabsListProps) => (\n <div className={cn(className)} {...props} />\n);\n\nexport type PromptInputTabProps = HTMLAttributes<HTMLDivElement>;\n\nexport const PromptInputTab = ({ className, ...props }: PromptInputTabProps) => (\n <div className={cn(className)} {...props} />\n);\n\nexport type PromptInputTabLabelProps = HTMLAttributes<HTMLHeadingElement>;\n\nexport const PromptInputTabLabel = ({ className, ...props }: PromptInputTabLabelProps) => (\n // Content provided via children in props\n // oxlint-disable-next-line eslint-plugin-jsx-a11y(heading-has-content)\n <h3 className={cn("mb-2 px-3 font-medium text-muted-foreground text-xs", className)} {...props} />\n);\n\nexport type PromptInputTabBodyProps = HTMLAttributes<HTMLDivElement>;\n\nexport const PromptInputTabBody = ({ className, ...props }: PromptInputTabBodyProps) => (\n <div className={cn("space-y-1", className)} {...props} />\n);\n\nexport type PromptInputTabItemProps = HTMLAttributes<HTMLDivElement>;\n\nexport const PromptInputTabItem = ({ className, ...props }: PromptInputTabItemProps) => (\n <div\n className={cn("flex items-center gap-2 px-3 py-2 text-xs hover:bg-accent", className)}\n {...props}\n />\n);\n\nexport type PromptInputCommandProps = ComponentProps<typeof Command>;\n\nexport const PromptInputCommand = ({ className, ...props }: PromptInputCommandProps) => (\n <Command className={cn(className)} {...props} />\n);\n\nexport type PromptInputCommandInputProps = ComponentProps<typeof CommandInput>;\n\nexport const PromptInputCommandInput = ({ className, ...props }: PromptInputCommandInputProps) => (\n <CommandInput className={cn(className)} {...props} />\n);\n\nexport type PromptInputCommandListProps = ComponentProps<typeof CommandList>;\n\nexport const PromptInputCommandList = ({ className, ...props }: PromptInputCommandListProps) => (\n <CommandList className={cn(className)} {...props} />\n);\n\nexport type PromptInputCommandEmptyProps = ComponentProps<typeof CommandEmpty>;\n\nexport const PromptInputCommandEmpty = ({ className, ...props }: PromptInputCommandEmptyProps) => (\n <CommandEmpty className={cn(className)} {...props} />\n);\n\nexport type PromptInputCommandGroupProps = ComponentProps<typeof CommandGroup>;\n\nexport const PromptInputCommandGroup = ({ className, ...props }: PromptInputCommandGroupProps) => (\n <CommandGroup className={cn(className)} {...props} />\n);\n\nexport type PromptInputCommandItemProps = ComponentProps<typeof CommandItem>;\n\nexport const PromptInputCommandItem = ({ className, ...props }: PromptInputCommandItemProps) => (\n <CommandItem className={cn(className)} {...props} />\n);\n\nexport type PromptInputCommandSeparatorProps = ComponentProps<typeof CommandSeparator>;\n\nexport const PromptInputCommandSeparator = ({\n className,\n ...props\n}: PromptInputCommandSeparatorProps) => <CommandSeparator className={cn(className)} {...props} />;\n';
readonly "components/ai-elements/question.tsx": '"use client";\n\nimport type {\n ChangeEvent,\n ComponentProps,\n FormEvent,\n HTMLAttributes,\n KeyboardEvent,\n MouseEvent,\n ReactNode,\n} from "react";\n\nimport { Button } from "@/components/ui/button";\nimport { Textarea } from "@/components/ui/textarea";\nimport { cn } from "@/lib/utils";\nimport { createContext, useCallback, useContext, useMemo, useState } from "react";\n\nexport interface QuestionValue {\n selectedValues: readonly string[];\n text: string;\n}\n\nexport interface QuestionResponse {\n selectedValues: readonly string[];\n text?: string;\n}\n\ntype SelectionMode = "multiple" | "single";\n\ninterface QuestionContextValue {\n disabled: boolean;\n selectedValues: readonly string[];\n selectionMode: SelectionMode;\n setText: (text: string) => void;\n text: string;\n toggleValue: (value: string) => void;\n}\n\nconst QuestionContext = createContext<QuestionContextValue | null>(null);\n\nconst useQuestion = () => {\n const context = useContext(QuestionContext);\n\n if (!context) {\n throw new Error("Question components must be used within Question");\n }\n\n return context;\n};\n\nexport type QuestionProps = Omit<ComponentProps<"form">, "defaultValue" | "onSubmit" | "value"> & {\n defaultValue?: QuestionValue;\n disabled?: boolean;\n onSubmit?: (\n response: QuestionResponse,\n event: FormEvent<HTMLFormElement>,\n ) => void | Promise<void>;\n onValueChange?: (value: QuestionValue) => void;\n selectionMode?: SelectionMode;\n value?: QuestionValue;\n};\n\nconst EMPTY_VALUE: QuestionValue = { selectedValues: [], text: "" };\n\nconst getSelectedValues = (\n currentValues: readonly string[],\n optionValue: string,\n selectionMode: SelectionMode,\n): readonly string[] => {\n const isSelected = currentValues.includes(optionValue);\n\n if (selectionMode === "single") {\n return isSelected ? [] : [optionValue];\n }\n\n if (isSelected) {\n return currentValues.filter((item) => item !== optionValue);\n }\n\n return [...currentValues, optionValue];\n};\n\nexport const Question = ({\n children,\n className,\n defaultValue = EMPTY_VALUE,\n disabled = false,\n onSubmit,\n onValueChange,\n selectionMode = "single",\n value: controlledValue,\n ...props\n}: QuestionProps) => {\n const [internalValue, setInternalValue] = useState(defaultValue);\n const value = controlledValue ?? internalValue;\n\n const setValue = useCallback(\n (nextValue: QuestionValue) => {\n if (controlledValue === undefined) {\n setInternalValue(nextValue);\n }\n onValueChange?.(nextValue);\n },\n [controlledValue, onValueChange],\n );\n\n const setText = useCallback(\n (text: string) => {\n setValue({ ...value, text });\n },\n [setValue, value],\n );\n\n const toggleValue = useCallback(\n (optionValue: string) => {\n const selectedValues = getSelectedValues(value.selectedValues, optionValue, selectionMode);\n setValue({ ...value, selectedValues });\n },\n [selectionMode, setValue, value],\n );\n\n const contextValue = useMemo(\n () => ({\n disabled,\n selectedValues: value.selectedValues,\n selectionMode,\n setText,\n text: value.text,\n toggleValue,\n }),\n [disabled, selectionMode, setText, toggleValue, value],\n );\n\n const handleSubmit = useCallback(\n async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (disabled) {\n return;\n }\n\n const text = value.text.trim();\n if (value.selectedValues.length === 0 && text.length === 0) {\n return;\n }\n\n await onSubmit?.(\n {\n selectedValues: value.selectedValues,\n text: text.length > 0 ? text : undefined,\n },\n event,\n );\n },\n [disabled, onSubmit, value],\n );\n\n return (\n <QuestionContext.Provider value={contextValue}>\n <form\n className={cn("space-y-3 rounded-xl border bg-card p-4", className)}\n onSubmit={handleSubmit}\n {...props}\n >\n {children}\n </form>\n </QuestionContext.Provider>\n );\n};\n\nexport type QuestionPromptProps = HTMLAttributes<HTMLParagraphElement>;\n\nexport const QuestionPrompt = ({ className, ...props }: QuestionPromptProps) => (\n <p className={cn("font-medium text-sm leading-snug", className)} {...props} />\n);\n\nexport type QuestionDescriptionProps = HTMLAttributes<HTMLParagraphElement>;\n\nexport const QuestionDescription = ({ className, ...props }: QuestionDescriptionProps) => (\n <p className={cn("text-muted-foreground text-sm", className)} {...props} />\n);\n\nexport type QuestionOptionsProps = HTMLAttributes<HTMLDivElement>;\n\nexport const QuestionOptions = ({ className, ...props }: QuestionOptionsProps) => {\n const { selectionMode } = useQuestion();\n\n return (\n <div\n className={cn("flex flex-wrap gap-1.5", className)}\n role={selectionMode === "single" ? "radiogroup" : "group"}\n {...props}\n />\n );\n};\n\nexport type QuestionOptionProps = Omit<ComponentProps<typeof Button>, "value"> & {\n value: string;\n};\n\nexport const QuestionOption = ({\n children,\n className,\n disabled,\n onClick,\n value,\n variant,\n ...props\n}: QuestionOptionProps) => {\n const question = useQuestion();\n const isSelected = question.selectedValues.includes(value);\n const role = question.selectionMode === "single" ? "radio" : "checkbox";\n const handleClick = useCallback(\n (event: MouseEvent<HTMLButtonElement>) => {\n question.toggleValue(value);\n onClick?.(event);\n },\n [onClick, question, value],\n );\n\n return (\n <Button\n aria-checked={isSelected}\n // Selection only changes colors: the border is always present so the\n // layout never shifts when an option is picked.\n className={cn(\n "group/option h-auto whitespace-normal border border-input font-normal shadow-none transition-colors",\n isSelected\n ? "border-foreground/20 bg-accent text-accent-foreground disabled:opacity-100"\n : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",\n className,\n )}\n data-state={isSelected ? "checked" : "unchecked"}\n disabled={question.disabled || disabled}\n onClick={handleClick}\n role={role}\n type="button"\n variant={variant ?? "ghost"}\n {...props}\n >\n {children ?? value}\n </Button>\n );\n};\n\nexport type QuestionInputProps = Omit<ComponentProps<typeof Textarea>, "defaultValue" | "value">;\n\nexport const QuestionInput = ({\n className,\n disabled,\n onChange,\n onKeyDown,\n ...props\n}: QuestionInputProps) => {\n const question = useQuestion();\n const handleChange = useCallback(\n (event: ChangeEvent<HTMLTextAreaElement>) => {\n question.setText(event.currentTarget.value);\n onChange?.(event);\n },\n [onChange, question],\n );\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLTextAreaElement>) => {\n onKeyDown?.(event);\n if (\n event.defaultPrevented ||\n event.key !== "Enter" ||\n event.shiftKey ||\n event.nativeEvent.isComposing\n ) {\n return;\n }\n\n event.preventDefault();\n event.currentTarget.form?.requestSubmit();\n },\n [onKeyDown],\n );\n\n return (\n <Textarea\n className={cn(\n "min-h-16 resize-none rounded-lg text-sm shadow-none focus-visible:border-foreground!",\n className,\n )}\n disabled={question.disabled || disabled}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n value={question.text}\n {...props}\n />\n );\n};\n\nexport type QuestionActionsProps = HTMLAttributes<HTMLDivElement>;\n\nexport const QuestionActions = ({ className, ...props }: QuestionActionsProps) => (\n <div className={cn("flex items-center justify-end gap-2", className)} {...props} />\n);\n\nexport type QuestionSubmitProps = ComponentProps<typeof Button> & {\n children?: ReactNode;\n};\n\nexport const QuestionSubmit = ({\n children = "Submit",\n className,\n disabled,\n size = "sm",\n ...props\n}: QuestionSubmitProps) => {\n const question = useQuestion();\n const hasResponse = question.selectedValues.length > 0 || question.text.trim().length > 0;\n\n return (\n <Button\n className={cn("text-sm! shadow-none", className)}\n disabled={question.disabled || disabled || !hasResponse}\n size={size}\n type="submit"\n {...props}\n >\n {children}\n </Button>\n );\n};\n';
readonly "components/ai-elements/reasoning.tsx": '"use client";\n\nimport { useControllableState } from "@radix-ui/react-use-controllable-state";\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";\nimport { cn } from "@/lib/utils";\nimport { cjk } from "@streamdown/cjk";\nimport { code } from "@streamdown/code";\nimport { math } from "@streamdown/math";\nimport { mermaid } from "@streamdown/mermaid";\nimport { BrainIcon, ChevronDownIcon } from "lucide-react";\nimport type { ComponentProps, ReactNode } from "react";\nimport {\n createContext,\n memo,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from "react";\nimport { Streamdown } from "streamdown";\n\nimport { Shimmer } from "./shimmer";\n\ninterface ReasoningContextValue {\n isStreaming: boolean;\n isOpen: boolean;\n setIsOpen: (open: boolean) => void;\n duration: number | undefined;\n}\n\nconst ReasoningContext = createContext<ReasoningContextValue | null>(null);\n\nexport const useReasoning = () => {\n const context = useContext(ReasoningContext);\n if (!context) {\n throw new Error("Reasoning components must be used within Reasoning");\n }\n return context;\n};\n\nexport type ReasoningProps = ComponentProps<typeof Collapsible> & {\n isStreaming?: boolean;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n duration?: number;\n};\n\nconst MS_IN_S = 1000;\n\nexport const Reasoning = memo(\n ({\n className,\n isStreaming = false,\n open,\n defaultOpen,\n onOpenChange,\n duration: durationProp,\n children,\n ...props\n }: ReasoningProps) => {\n const resolvedDefaultOpen = defaultOpen ?? isStreaming;\n // Track if defaultOpen was explicitly set to false (to prevent auto-open)\n const isExplicitlyClosed = defaultOpen === false;\n\n const [isOpen, setIsOpen] = useControllableState<boolean>({\n defaultProp: resolvedDefaultOpen,\n onChange: onOpenChange,\n prop: open,\n });\n const [duration, setDuration] = useControllableState<number | undefined>({\n defaultProp: undefined,\n prop: durationProp,\n });\n\n const hasEverStreamedRef = useRef(isStreaming);\n const [hasAutoClosed, setHasAutoClosed] = useState(false);\n const startTimeRef = useRef<number | null>(null);\n\n // Track when streaming starts and compute duration\n useEffect(() => {\n if (isStreaming) {\n hasEverStreamedRef.current = true;\n if (startTimeRef.current === null) {\n startTimeRef.current = Date.now();\n }\n } else if (startTimeRef.current !== null) {\n setDuration(Math.ceil((Date.now() - startTimeRef.current) / MS_IN_S));\n startTimeRef.current = null;\n }\n }, [isStreaming, setDuration]);\n\n // Auto-open when streaming starts (unless explicitly closed)\n useEffect(() => {\n if (isStreaming && !isOpen && !isExplicitlyClosed) {\n setIsOpen(true);\n }\n }, [isStreaming, isOpen, setIsOpen, isExplicitlyClosed]);\n\n // Auto-close when streaming ends (once only, and only if it ever streamed)\n useEffect(() => {\n if (hasEverStreamedRef.current && !isStreaming && isOpen && !hasAutoClosed) {\n setIsOpen(false);\n setHasAutoClosed(true);\n }\n }, [isStreaming, isOpen, setIsOpen, hasAutoClosed]);\n\n const handleOpenChange = useCallback(\n (newOpen: boolean) => {\n setIsOpen(newOpen);\n },\n [setIsOpen],\n );\n\n const contextValue = useMemo(\n () => ({ duration, isOpen, isStreaming, setIsOpen }),\n [duration, isOpen, isStreaming, setIsOpen],\n );\n\n return (\n <ReasoningContext.Provider value={contextValue}>\n <Collapsible\n className={cn("not-prose mb-4 w-full", className)}\n onOpenChange={handleOpenChange}\n open={isOpen}\n {...props}\n >\n {children}\n </Collapsible>\n </ReasoningContext.Provider>\n );\n },\n);\n\nexport type ReasoningTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {\n getThinkingMessage?: (isStreaming: boolean, duration?: number) => ReactNode;\n};\n\nconst defaultGetThinkingMessage = (isStreaming: boolean, duration?: number) => {\n if (isStreaming || duration === 0) {\n return <Shimmer duration={1}>Thinking...</Shimmer>;\n }\n if (duration === undefined) {\n return <p>Thought for a few seconds</p>;\n }\n return <p>Thought for {duration} seconds</p>;\n};\n\nexport const ReasoningTrigger = memo(\n ({\n className,\n children,\n getThinkingMessage = defaultGetThinkingMessage,\n ...props\n }: ReasoningTriggerProps) => {\n const { isStreaming, isOpen, duration } = useReasoning();\n\n return (\n <CollapsibleTrigger\n className={cn(\n "flex w-full items-center gap-2 text-muted-foreground text-sm transition-colors hover:text-foreground",\n className,\n )}\n {...props}\n >\n {children ?? (\n <>\n <BrainIcon className="size-4" />\n {getThinkingMessage(isStreaming, duration)}\n <ChevronDownIcon\n className={cn("size-4 transition-transform", isOpen ? "rotate-180" : "rotate-0")}\n />\n </>\n )}\n </CollapsibleTrigger>\n );\n },\n);\n\nexport type ReasoningContentProps = ComponentProps<typeof CollapsibleContent> & {\n children: string;\n};\n\nconst streamdownPlugins = { cjk, code, math, mermaid };\n\nexport const ReasoningContent = memo(({ className, children, ...props }: ReasoningContentProps) => (\n <CollapsibleContent\n className={cn(\n "mt-4 text-sm",\n "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-muted-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",\n className,\n )}\n {...props}\n >\n <Streamdown plugins={streamdownPlugins}>{children}</Streamdown>\n </CollapsibleContent>\n));\n\nReasoning.displayName = "Reasoning";\nReasoningTrigger.displayName = "ReasoningTrigger";\nReasoningContent.displayName = "ReasoningContent";\n';
readonly "components/ai-elements/shimmer.tsx": '"use client";\n\nimport { cn } from "@/lib/utils";\nimport type { MotionProps } from "motion/react";\nimport { motion } from "motion/react";\nimport type { CSSProperties, ElementType, JSX } from "react";\nimport { memo, useMemo } from "react";\n\ntype MotionHTMLProps = MotionProps & Record<string, unknown>;\n\n// Cache motion components at module level to avoid creating during render\nconst motionComponentCache = new Map<\n keyof JSX.IntrinsicElements,\n React.ComponentType<MotionHTMLProps>\n>();\n\nconst getMotionComponent = (element: keyof JSX.IntrinsicElements) => {\n let component = motionComponentCache.get(element);\n if (!component) {\n component = motion.create(element);\n motionComponentCache.set(element, component);\n }\n return component;\n};\n\nexport interface TextShimmerProps {\n children: string;\n as?: ElementType;\n className?: string;\n duration?: number;\n spread?: number;\n}\n\nconst ShimmerComponent = ({\n children,\n as: Component = "p",\n className,\n duration = 2,\n spread = 2,\n}: TextShimmerProps) => {\n const MotionComponent = getMotionComponent(Component as keyof JSX.IntrinsicElements);\n\n const dynamicSpread = useMemo(() => (children?.length ?? 0) * spread, [children, spread]);\n\n return (\n <MotionComponent\n animate={{ backgroundPosition: "0% center" }}\n className={cn(\n "relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",\n "[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]",\n className,\n )}\n initial={{ backgroundPosition: "100% center" }}\n style={\n {\n "--spread": `${dynamicSpread}px`,\n backgroundImage:\n "var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))",\n } as CSSProperties\n }\n transition={{\n duration,\n ease: "linear",\n repeat: Number.POSITIVE_INFINITY,\n }}\n >\n {children}\n </MotionComponent>\n );\n};\n\nexport const Shimmer = memo(ShimmerComponent);\n';
readonly "components/ai-elements/tool.tsx": '"use client";\n\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";\nimport { cn } from "@/lib/utils";\nimport type { DynamicToolUIPart, ToolUIPart } from "ai";\nimport { ChevronRightIcon, TerminalIcon, WrenchIcon } from "lucide-react";\nimport type { ComponentProps, ReactNode } from "react";\nimport { isValidElement } from "react";\n\nimport { CodeBlock } from "./code-block";\n\nconst compactCodeBlockClassName =\n "rounded-none border-0 bg-transparent [&_pre]:!bg-transparent [&_pre]:px-3 [&_pre]:pt-2 [&_pre]:pb-3 [&_pre]:text-xs [&_code]:text-xs";\n\nexport type ToolProps = ComponentProps<typeof Collapsible>;\n\nexport const Tool = ({ className, ...props }: ToolProps) => (\n <Collapsible className={cn("group not-prose w-full", className)} {...props} />\n);\n\nexport type ToolPart = ToolUIPart | DynamicToolUIPart;\n\nexport type ToolHeaderProps = {\n title?: string;\n className?: string;\n} & (\n | { type: ToolUIPart["type"]; state: ToolUIPart["state"]; toolName?: never }\n | {\n type: DynamicToolUIPart["type"];\n state: DynamicToolUIPart["state"];\n toolName: string;\n }\n);\n\nconst statusLabels: Record<ToolPart["state"], string> = {\n "approval-requested": "Awaiting Approval",\n "approval-responded": "Responded",\n "input-available": "Running",\n "input-streaming": "Pending",\n "output-available": "Completed",\n "output-denied": "Denied",\n "output-error": "Error",\n};\n\nexport const getStatusIndicator = (status: ToolPart["state"]) =>\n status === "output-available" ? null : (\n <span className={cn("text-sm", status === "output-error" && "text-destructive")}>\n {statusLabels[status]}\n </span>\n );\n\nexport const ToolHeader = ({\n className,\n title,\n type,\n state,\n toolName,\n ...props\n}: ToolHeaderProps) => {\n const derivedName = type === "dynamic-tool" ? toolName : type.split("-").slice(1).join("-");\n const displayName = title ?? derivedName;\n\n return (\n <CollapsibleTrigger\n className={cn(\n "flex w-full items-center gap-2 py-0.5 text-left text-muted-foreground transition-colors hover:text-foreground",\n className,\n )}\n {...props}\n >\n {displayName === "bash" ? (\n <TerminalIcon className="size-4 shrink-0" />\n ) : (\n <WrenchIcon className="size-4 shrink-0" />\n )}\n <span className="text-sm">{displayName}</span>\n {getStatusIndicator(state)}\n <ChevronRightIcon className="size-3.5 shrink-0 transition-transform group-data-[state=open]:rotate-90" />\n </CollapsibleTrigger>\n );\n};\n\nexport type ToolContentProps = ComponentProps<typeof CollapsibleContent>;\n\nexport const ToolContent = ({ className, ...props }: ToolContentProps) => (\n <CollapsibleContent\n className={cn(\n "data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 space-y-4 py-2 text-popover-foreground outline-none data-[state=closed]:animate-out data-[state=open]:animate-in",\n className,\n )}\n {...props}\n />\n);\n\nexport type BashToolContentProps = ComponentProps<"div"> & {\n input: ToolPart["input"];\n output: ToolPart["output"];\n errorText: ToolPart["errorText"];\n};\n\nexport const BashToolContent = ({\n className,\n input,\n output,\n errorText,\n ...props\n}: BashToolContentProps) => {\n const command = getRecordValue(input, "command");\n const stdout = getRecordValue(output, "stdout") ?? (typeof output === "string" ? output : "");\n const stderr = getRecordValue(output, "stderr") ?? errorText ?? "";\n const exitCode = getRecordValue(output, "exitCode");\n const hasResult = Boolean(stdout || stderr || (typeof exitCode === "number" && exitCode !== 0));\n\n return (\n <div className={cn("space-y-2", className)} {...props}>\n <pre className="overflow-x-auto whitespace-pre-wrap rounded-md bg-muted/50 p-3 font-mono text-xs leading-relaxed">\n <code>\n <span className="text-muted-foreground">$ </span>\n {command ?? "…"}\n </code>\n </pre>\n {hasResult ? (\n <pre className="overflow-x-auto whitespace-pre-wrap rounded-md bg-muted/50 p-3 font-mono text-xs leading-relaxed">\n <code>\n <span className="mb-2 block font-sans text-[10px] text-muted-foreground uppercase tracking-wide">\n Output\n </span>\n {stdout ? <span className="block">{String(stdout).trimEnd()}</span> : null}\n {stderr ? (\n <span className="block text-destructive">{String(stderr).trimEnd()}</span>\n ) : null}\n {typeof exitCode === "number" && exitCode !== 0 ? (\n <span className="block text-muted-foreground">Exited with code {exitCode}</span>\n ) : null}\n </code>\n </pre>\n ) : null}\n </div>\n );\n};\n\nconst getRecordValue = (value: unknown, key: string): string | number | undefined => {\n if (typeof value !== "object" || value === null || !(key in value)) {\n return undefined;\n }\n\n const property = value[key as keyof typeof value];\n return typeof property === "string" || typeof property === "number" ? property : undefined;\n};\n\nexport type ToolInputProps = ComponentProps<"div"> & {\n input: ToolPart["input"];\n};\n\nexport const ToolInput = ({ className, input, ...props }: ToolInputProps) => (\n <div className={cn("overflow-hidden rounded-md bg-muted/50", className)} {...props}>\n <span className="block px-3 pt-3 font-sans text-[10px] text-muted-foreground uppercase tracking-wide">\n Parameters\n </span>\n <div>\n <CodeBlock\n className={compactCodeBlockClassName}\n code={JSON.stringify(input, null, 2)}\n language="json"\n />\n </div>\n </div>\n);\n\nexport type ToolOutputProps = ComponentProps<"div"> & {\n output: ToolPart["output"];\n errorText: ToolPart["errorText"];\n};\n\nexport const ToolOutput = ({ className, output, errorText, ...props }: ToolOutputProps) => {\n if (!(output || errorText)) {\n return null;\n }\n\n let Output = <div>{output as ReactNode}</div>;\n\n if (typeof output === "object" && !isValidElement(output)) {\n Output = (\n <CodeBlock\n className={compactCodeBlockClassName}\n code={JSON.stringify(output, null, 2)}\n language="json"\n />\n );\n } else if (typeof output === "string") {\n Output = <CodeBlock className={compactCodeBlockClassName} code={output} language="json" />;\n }\n\n return (\n <div\n className={cn(\n "overflow-x-auto rounded-md text-xs [&_table]:w-full",\n errorText ? "bg-destructive/10 text-destructive" : "bg-muted/50 text-foreground",\n className,\n )}\n {...props}\n >\n <span className="block px-3 pt-3 font-sans text-[10px] text-muted-foreground uppercase tracking-wide">\n {errorText ? "Error" : "Result"}\n </span>\n {errorText && <div className="px-3 pt-2 pb-3">{errorText}</div>}\n {Output}\n </div>\n );\n};\n';
readonly "components/ui/badge.tsx": 'import * as React from "react";\nimport { cva, type VariantProps } from "class-variance-authority";\nimport { Slot } from "radix-ui";\n\nimport { cn } from "@/lib/utils";\n\nconst badgeVariants = cva(\n "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",\n {\n variants: {\n variant: {\n default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",\n secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",\n destructive:\n "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90",\n outline:\n "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",\n ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",\n link: "text-primary underline-offset-4 [a&]:hover:underline",\n },\n },\n defaultVariants: {\n variant: "default",\n },\n },\n);\n\nfunction Badge({\n className,\n variant = "default",\n asChild = false,\n ...props\n}: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) {\n const Comp = asChild ? Slot.Root : "span";\n\n return (\n <Comp\n data-slot="badge"\n data-variant={variant}\n className={cn(badgeVariants({ variant }), className)}\n {...props}\n />\n );\n}\n\nexport { Badge, badgeVariants };\n';
readonly "components/ui/button-group.tsx": 'import { cva, type VariantProps } from "class-variance-authority";\nimport { Slot } from "radix-ui";\n\nimport { cn } from "@/lib/utils";\nimport { Separator } from "@/components/ui/separator";\n\nconst buttonGroupVariants = cva(\n "flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*=\'w-\'])]:w-fit [&>input]:flex-1",\n {\n variants: {\n orientation: {\n horizontal:\n "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",\n vertical:\n "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",\n },\n },\n defaultVariants: {\n orientation: "horizontal",\n },\n },\n);\n\nfunction ButtonGroup({\n className,\n orientation,\n ...props\n}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {\n return (\n <div\n role="group"\n data-slot="button-group"\n data-orientation={orientation}\n className={cn(buttonGroupVariants({ orientation }), className)}\n {...props}\n />\n );\n}\n\nfunction ButtonGroupText({\n className,\n asChild = false,\n ...props\n}: React.ComponentProps<"div"> & {\n asChild?: boolean;\n}) {\n const Comp = asChild ? Slot.Root : "div";\n\n return (\n <Comp\n className={cn(\n "flex items-center gap-2 rounded-md border bg-muted px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction ButtonGroupSeparator({\n className,\n orientation = "vertical",\n ...props\n}: React.ComponentProps<typeof Separator>) {\n return (\n <Separator\n data-slot="button-group-separator"\n orientation={orientation}\n className={cn(\n "relative m-0! self-stretch bg-input data-[orientation=vertical]:h-auto",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, buttonGroupVariants };\n';
readonly "components/ui/button.tsx": 'import * as React from "react";\nimport { cva, type VariantProps } from "class-variance-authority";\nimport { Slot } from "radix-ui";\n\nimport { cn } from "@/lib/utils";\n\nconst buttonVariants = cva(\n "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4",\n {\n variants: {\n variant: {\n default: "bg-primary text-primary-foreground hover:bg-primary/90",\n destructive:\n "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",\n outline:\n "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",\n secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",\n ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",\n link: "text-primary underline-offset-4 hover:underline",\n },\n size: {\n default: "h-9 px-4 py-2 has-[>svg]:px-3",\n xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*=\'size-\'])]:size-3",\n sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",\n lg: "h-10 rounded-md px-6 has-[>svg]:px-4",\n icon: "size-9",\n "icon-xs": "size-6 rounded-md [&_svg:not([class*=\'size-\'])]:size-3",\n "icon-sm": "size-8",\n "icon-lg": "size-10",\n },\n },\n defaultVariants: {\n variant: "default",\n size: "default",\n },\n },\n);\n\nfunction Button({\n className,\n variant = "default",\n size = "default",\n asChild = false,\n ...props\n}: React.ComponentProps<"button"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean;\n }) {\n const Comp = asChild ? Slot.Root : "button";\n\n return (\n <Comp\n data-slot="button"\n data-variant={variant}\n data-size={size}\n className={cn(buttonVariants({ variant, size, className }))}\n {...props}\n />\n );\n}\n\nexport { Button, buttonVariants };\n';
readonly "components/ui/collapsible.tsx": '"use client";\n\nimport { Collapsible as CollapsiblePrimitive } from "radix-ui";\n\nfunction Collapsible({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {\n return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;\n}\n\nfunction CollapsibleTrigger({\n ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {\n return <CollapsiblePrimitive.CollapsibleTrigger data-slot="collapsible-trigger" {...props} />;\n}\n\nfunction CollapsibleContent({\n ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {\n return <CollapsiblePrimitive.CollapsibleContent data-slot="collapsible-content" {...props} />;\n}\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent };\n';
readonly "components/ui/command.tsx": '"use client";\n\nimport * as React from "react";\nimport { Command as CommandPrimitive } from "cmdk";\nimport { SearchIcon } from "lucide-react";\n\nimport { cn } from "@/lib/utils";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from "@/components/ui/dialog";\n\nfunction Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {\n return (\n <CommandPrimitive\n data-slot="command"\n className={cn(\n "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction CommandDialog({\n title = "Command Palette",\n description = "Search for a command to run...",\n children,\n className,\n showCloseButton = true,\n ...props\n}: React.ComponentProps<typeof Dialog> & {\n title?: string;\n description?: string;\n className?: string;\n showCloseButton?: boolean;\n}) {\n return (\n <Dialog {...props}>\n <DialogHeader className="sr-only">\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n <DialogContent\n className={cn("overflow-hidden p-0", className)}\n showCloseButton={showCloseButton}\n >\n <Command className="**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n );\n}\n\nfunction CommandInput({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n return (\n <div data-slot="command-input-wrapper" className="flex h-9 items-center gap-2 border-b px-3">\n <SearchIcon className="size-4 shrink-0 opacity-50" />\n <CommandPrimitive.Input\n data-slot="command-input"\n className={cn(\n "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",\n className,\n )}\n {...props}\n />\n </div>\n );\n}\n\nfunction CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {\n return (\n <CommandPrimitive.List\n data-slot="command-list"\n className={cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className)}\n {...props}\n />\n );\n}\n\nfunction CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n return (\n <CommandPrimitive.Empty\n data-slot="command-empty"\n className="py-6 text-center text-sm"\n {...props}\n />\n );\n}\n\nfunction CommandGroup({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n return (\n <CommandPrimitive.Group\n data-slot="command-group"\n className={cn(\n "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction CommandSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n return (\n <CommandPrimitive.Separator\n data-slot="command-separator"\n className={cn("-mx-1 h-px bg-border", className)}\n {...props}\n />\n );\n}\n\nfunction CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {\n return (\n <CommandPrimitive.Item\n data-slot="command-item"\n className={cn(\n "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 [&_svg:not([class*=\'text-\'])]:text-muted-foreground",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction CommandShortcut({ className, ...props }: React.ComponentProps<"span">) {\n return (\n <span\n data-slot="command-shortcut"\n className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}\n {...props}\n />\n );\n}\n\nexport {\n Command,\n CommandDialog,\n CommandInput,\n CommandList,\n CommandEmpty,\n CommandGroup,\n CommandItem,\n CommandShortcut,\n CommandSeparator,\n};\n';
readonly "components/ui/dialog.tsx": '"use client";\n\nimport * as React from "react";\nimport { XIcon } from "lucide-react";\nimport { Dialog as DialogPrimitive } from "radix-ui";\n\nimport { cn } from "@/lib/utils";\nimport { Button } from "@/components/ui/button";\n\nfunction Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {\n return <DialogPrimitive.Root data-slot="dialog" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;\n}\n\nfunction DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {\n return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;\n}\n\nfunction DialogOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n return (\n <DialogPrimitive.Overlay\n data-slot="dialog-overlay"\n className={cn(\n "fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DialogContent({\n className,\n children,\n showCloseButton = true,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n showCloseButton?: boolean;\n}) {\n return (\n <DialogPortal data-slot="dialog-portal">\n <DialogOverlay />\n <DialogPrimitive.Content\n data-slot="dialog-content"\n className={cn(\n "fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",\n className,\n )}\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close\n data-slot="dialog-close"\n className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4"\n >\n <XIcon />\n <span className="sr-only">Close</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Content>\n </DialogPortal>\n );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<"div">) {\n return (\n <div\n data-slot="dialog-header"\n className={cn("flex flex-col gap-2 text-center sm:text-left", className)}\n {...props}\n />\n );\n}\n\nfunction DialogFooter({\n className,\n showCloseButton = false,\n children,\n ...props\n}: React.ComponentProps<"div"> & {\n showCloseButton?: boolean;\n}) {\n return (\n <div\n data-slot="dialog-footer"\n className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close asChild>\n <Button variant="outline">Close</Button>\n </DialogPrimitive.Close>\n )}\n </div>\n );\n}\n\nfunction DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return (\n <DialogPrimitive.Title\n data-slot="dialog-title"\n className={cn("text-lg leading-none font-semibold", className)}\n {...props}\n />\n );\n}\n\nfunction DialogDescription({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return (\n <DialogPrimitive.Description\n data-slot="dialog-description"\n className={cn("text-sm text-muted-foreground", className)}\n {...props}\n />\n );\n}\n\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n};\n';
readonly "components/ui/dropdown-menu.tsx": '"use client";\n\nimport * as React from "react";\nimport { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";\nimport { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";\n\nimport { cn } from "@/lib/utils";\n\nfunction DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {\n return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;\n}\n\nfunction DropdownMenuPortal({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {\n return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;\n}\n\nfunction DropdownMenuTrigger({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {\n return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;\n}\n\nfunction DropdownMenuContent({\n className,\n sideOffset = 4,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n data-slot="dropdown-menu-content"\n sideOffset={sideOffset}\n className={cn(\n "z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",\n className,\n )}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n );\n}\n\nfunction DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {\n return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;\n}\n\nfunction DropdownMenuItem({\n className,\n inset,\n variant = "default",\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean;\n variant?: "default" | "destructive";\n}) {\n return (\n <DropdownMenuPrimitive.Item\n data-slot="dropdown-menu-item"\n data-inset={inset}\n data-variant={variant}\n className={cn(\n "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 [&_svg:not([class*=\'text-\'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuCheckboxItem({\n className,\n children,\n checked,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {\n return (\n <DropdownMenuPrimitive.CheckboxItem\n data-slot="dropdown-menu-checkbox-item"\n className={cn(\n "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4",\n className,\n )}\n checked={checked}\n {...props}\n >\n <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">\n <DropdownMenuPrimitive.ItemIndicator>\n <CheckIcon className="size-4" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n );\n}\n\nfunction DropdownMenuRadioGroup({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {\n return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;\n}\n\nfunction DropdownMenuRadioItem({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {\n return (\n <DropdownMenuPrimitive.RadioItem\n data-slot="dropdown-menu-radio-item"\n className={cn(\n "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4",\n className,\n )}\n {...props}\n >\n <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">\n <DropdownMenuPrimitive.ItemIndicator>\n <CircleIcon className="size-2 fill-current" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.RadioItem>\n );\n}\n\nfunction DropdownMenuLabel({\n className,\n inset,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean;\n}) {\n return (\n <DropdownMenuPrimitive.Label\n data-slot="dropdown-menu-label"\n data-inset={inset}\n className={cn("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n return (\n <DropdownMenuPrimitive.Separator\n data-slot="dropdown-menu-separator"\n className={cn("-mx-1 my-1 h-px bg-border", className)}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {\n return (\n <span\n data-slot="dropdown-menu-shortcut"\n className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {\n return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n className,\n inset,\n children,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {\n inset?: boolean;\n}) {\n return (\n <DropdownMenuPrimitive.SubTrigger\n data-slot="dropdown-menu-sub-trigger"\n data-inset={inset}\n className={cn(\n "flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 [&_svg:not([class*=\'text-\'])]:text-muted-foreground",\n className,\n )}\n {...props}\n >\n {children}\n <ChevronRightIcon className="ml-auto size-4" />\n </DropdownMenuPrimitive.SubTrigger>\n );\n}\n\nfunction DropdownMenuSubContent({\n className,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {\n return (\n <DropdownMenuPrimitive.SubContent\n data-slot="dropdown-menu-sub-content"\n className={cn(\n "z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport {\n DropdownMenu,\n DropdownMenuPortal,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuLabel,\n DropdownMenuItem,\n DropdownMenuCheckboxItem,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuSub,\n DropdownMenuSubTrigger,\n DropdownMenuSubContent,\n};\n';
readonly "components/ui/hover-card.tsx": '"use client";\n\nimport * as React from "react";\nimport { HoverCard as HoverCardPrimitive } from "radix-ui";\n\nimport { cn } from "@/lib/utils";\n\nfunction HoverCard({ ...props }: React.ComponentProps<typeof HoverCardPrimitive.Root>) {\n return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />;\n}\n\nfunction HoverCardTrigger({ ...props }: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {\n return <HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />;\n}\n\nfunction HoverCardContent({\n className,\n align = "center",\n sideOffset = 4,\n ...props\n}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {\n return (\n <HoverCardPrimitive.Portal data-slot="hover-card-portal">\n <HoverCardPrimitive.Content\n data-slot="hover-card-content"\n align={align}\n sideOffset={sideOffset}\n className={cn(\n "z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",\n className,\n )}\n {...props}\n />\n </HoverCardPrimitive.Portal>\n );\n}\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent };\n';
readonly "components/ui/input-group.tsx": '"use client";\n\nimport * as React from "react";\nimport { cva, type VariantProps } from "class-variance-authority";\n\nimport { cn } from "@/lib/utils";\nimport { Button } from "@/components/ui/button";\nimport { Input } from "@/components/ui/input";\nimport { Textarea } from "@/components/ui/textarea";\n\nfunction InputGroup({ className, ...props }: React.ComponentProps<"div">) {\n return (\n <div\n data-slot="input-group"\n role="group"\n className={cn(\n "group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none dark:bg-input/30",\n "h-9 min-w-0 has-[>textarea]:h-auto",\n\n // Variants based on alignment.\n "has-[>[data-align=inline-start]]:[&>input]:pl-2",\n "has-[>[data-align=inline-end]]:[&>input]:pr-2",\n "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",\n "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",\n\n // Focus state.\n "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50",\n\n // Error state.\n "has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",\n\n className,\n )}\n {...props}\n />\n );\n}\n\nconst inputGroupAddonVariants = cva(\n "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*=\'size-\'])]:size-4",\n {\n variants: {\n align: {\n "inline-start": "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",\n "inline-end": "order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",\n "block-start":\n "order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3",\n "block-end":\n "order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3",\n },\n },\n defaultVariants: {\n align: "inline-start",\n },\n },\n);\n\nfunction InputGroupAddon({\n className,\n align = "inline-start",\n ...props\n}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {\n return (\n <div\n role="group"\n data-slot="input-group-addon"\n data-align={align}\n className={cn(inputGroupAddonVariants({ align }), className)}\n onClick={(e) => {\n if ((e.target as HTMLElement).closest("button")) {\n return;\n }\n e.currentTarget.parentElement?.querySelector("input")?.focus();\n }}\n {...props}\n />\n );\n}\n\nconst inputGroupButtonVariants = cva("flex items-center gap-2 text-sm shadow-none", {\n variants: {\n size: {\n xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*=\'size-\'])]:size-3.5",\n sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",\n "icon-xs": "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",\n "icon-sm": "size-8 p-0 has-[>svg]:p-0",\n },\n },\n defaultVariants: {\n size: "xs",\n },\n});\n\nfunction InputGroupButton({\n className,\n type = "button",\n variant = "ghost",\n size = "xs",\n ...props\n}: Omit<React.ComponentProps<typeof Button>, "size"> &\n VariantProps<typeof inputGroupButtonVariants>) {\n return (\n <Button\n type={type}\n data-size={size}\n variant={variant}\n className={cn(inputGroupButtonVariants({ size }), className)}\n {...props}\n />\n );\n}\n\nfunction InputGroupText({ className, ...props }: React.ComponentProps<"span">) {\n return (\n <span\n className={cn(\n "flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction InputGroupInput({ className, ...props }: React.ComponentProps<"input">) {\n return (\n <Input\n data-slot="input-group-control"\n className={cn(\n "flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction InputGroupTextarea({ className, ...props }: React.ComponentProps<"textarea">) {\n return (\n <Textarea\n data-slot="input-group-control"\n className={cn(\n "flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport {\n InputGroup,\n InputGroupAddon,\n InputGroupButton,\n InputGroupText,\n InputGroupInput,\n InputGroupTextarea,\n};\n';
readonly "components/ui/input.tsx": 'import * as React from "react";\n\nimport { cn } from "@/lib/utils";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<"input">) {\n return (\n <input\n type={type}\n data-slot="input"\n className={cn(\n "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",\n "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",\n "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Input };\n';
readonly "components/ui/select.tsx": '"use client";\n\nimport * as React from "react";\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";\nimport { Select as SelectPrimitive } from "radix-ui";\n\nimport { cn } from "@/lib/utils";\n\nfunction Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) {\n return <SelectPrimitive.Root data-slot="select" {...props} />;\n}\n\nfunction SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) {\n return <SelectPrimitive.Group data-slot="select-group" {...props} />;\n}\n\nfunction SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) {\n return <SelectPrimitive.Value data-slot="select-value" {...props} />;\n}\n\nfunction SelectTrigger({\n className,\n size = "default",\n children,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {\n size?: "sm" | "default";\n}) {\n return (\n <SelectPrimitive.Trigger\n data-slot="select-trigger"\n data-size={size}\n className={cn(\n "flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 [&_svg:not([class*=\'text-\'])]:text-muted-foreground",\n className,\n )}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon asChild>\n <ChevronDownIcon className="size-4 opacity-50" />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n );\n}\n\nfunction SelectContent({\n className,\n children,\n position = "item-aligned",\n align = "center",\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Content>) {\n return (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n data-slot="select-content"\n className={cn(\n "relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",\n position === "popper" &&\n "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",\n className,\n )}\n position={position}\n align={align}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.Viewport\n className={cn(\n "p-1",\n position === "popper" &&\n "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n <SelectScrollDownButton />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n );\n}\n\nfunction SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {\n return (\n <SelectPrimitive.Label\n data-slot="select-label"\n className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}\n {...props}\n />\n );\n}\n\nfunction SelectItem({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Item>) {\n return (\n <SelectPrimitive.Item\n data-slot="select-item"\n className={cn(\n "relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 [&_svg:not([class*=\'text-\'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",\n className,\n )}\n {...props}\n >\n <span\n data-slot="select-item-indicator"\n className="absolute right-2 flex size-3.5 items-center justify-center"\n >\n <SelectPrimitive.ItemIndicator>\n <CheckIcon className="size-4" />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n );\n}\n\nfunction SelectSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Separator>) {\n return (\n <SelectPrimitive.Separator\n data-slot="select-separator"\n className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}\n {...props}\n />\n );\n}\n\nfunction SelectScrollUpButton({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {\n return (\n <SelectPrimitive.ScrollUpButton\n data-slot="select-scroll-up-button"\n className={cn("flex cursor-default items-center justify-center py-1", className)}\n {...props}\n >\n <ChevronUpIcon className="size-4" />\n </SelectPrimitive.ScrollUpButton>\n );\n}\n\nfunction SelectScrollDownButton({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {\n return (\n <SelectPrimitive.ScrollDownButton\n data-slot="select-scroll-down-button"\n className={cn("flex cursor-default items-center justify-center py-1", className)}\n {...props}\n >\n <ChevronDownIcon className="size-4" />\n </SelectPrimitive.ScrollDownButton>\n );\n}\n\nexport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n};\n';
readonly "components/ui/separator.tsx": '"use client";\n\nimport * as React from "react";\nimport { Separator as SeparatorPrimitive } from "radix-ui";\n\nimport { cn } from "@/lib/utils";\n\nfunction Separator({\n className,\n orientation = "horizontal",\n decorative = true,\n ...props\n}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {\n return (\n <SeparatorPrimitive.Root\n data-slot="separator"\n decorative={decorative}\n orientation={orientation}\n className={cn(\n "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Separator };\n';
readonly "components/ui/spinner.tsx": 'import { Loader2Icon } from "lucide-react";\n\nimport { cn } from "@/lib/utils";\n\nfunction Spinner({ className, ...props }: React.ComponentProps<"svg">) {\n return (\n <Loader2Icon\n role="status"\n aria-label="Loading"\n className={cn("size-4 animate-spin", className)}\n {...props}\n />\n );\n}\n\nexport { Spinner };\n';
readonly "components/ui/textarea.tsx": 'import * as React from "react";\n\nimport { cn } from "@/lib/utils";\n\nfunction Textarea({ className, ...props }: React.ComponentProps<"textarea">) {\n return (\n <textarea\n data-slot="textarea"\n className={cn(\n "flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Textarea };\n';
readonly "components/ui/tooltip.tsx": '"use client";\n\nimport * as React from "react";\nimport { Tooltip as TooltipPrimitive } from "radix-ui";\n\nimport { cn } from "@/lib/utils";\n\nfunction TooltipProvider({\n delayDuration = 0,\n ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {\n return (\n <TooltipPrimitive.Provider\n data-slot="tooltip-provider"\n delayDuration={delayDuration}\n {...props}\n />\n );\n}\n\nfunction Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) {\n return <TooltipPrimitive.Root data-slot="tooltip" {...props} />;\n}\n\nfunction TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {\n return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;\n}\n\nfunction TooltipContent({\n className,\n sideOffset = 0,\n children,\n ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Content>) {\n return (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n data-slot="tooltip-content"\n sideOffset={sideOffset}\n className={cn(\n "z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",\n className,\n )}\n {...props}\n >\n {children}\n <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />\n </TooltipPrimitive.Content>\n </TooltipPrimitive.Portal>\n );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n';
readonly "components.json": '{\n "$schema": "https://ui.shadcn.com/schema.json",\n "style": "new-york",\n "rsc": true,\n "tsx": true,\n "tailwind": {\n "config": "",\n "css": "app/globals.css",\n "baseColor": "neutral",\n "cssVariables": true,\n "prefix": ""\n },\n "iconLibrary": "lucide",\n "aliases": {\n "components": "@/components",\n "utils": "@/lib/utils",\n "ui": "@/components/ui",\n "lib": "@/lib",\n "hooks": "@/hooks"\n },\n "registries": {}\n}\n';
readonly "css.d.ts": 'declare module "*.css";\n';
readonly "lib/utils.ts": 'import { clsx, type ClassValue } from "clsx";\nimport { twMerge } from "tailwind-merge";\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n';
readonly "next-env.d.ts": '/// <reference types="next" />\n/// <reference types="next/image-types/global" />\nimport "./.next/types/routes.d.ts";\nimport "./.next/types/root-params.d.ts";\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.\n';
readonly "next.config.ts": 'import type { NextConfig } from "next";\nimport { withEve } from "eve/next";\n\nconst nextConfig: NextConfig = {};\n\nexport default withEve(nextConfig__EVE_INIT_WITH_EVE_OPTIONS__);\n';
readonly "postcss.config.mjs": 'const config = {\n plugins: {\n "@tailwindcss/postcss": {},\n },\n};\n\nexport default config;\n';
readonly "tsconfig.json": '{\n "$schema": "https://json.schemastore.org/tsconfig",\n "compilerOptions": {\n "target": "ES2017",\n "lib": ["dom", "dom.iterable", "esnext"],\n "allowJs": true,\n "skipLibCheck": true,\n "strict": true,\n "noEmit": true,\n "esModuleInterop": true,\n "module": "esnext",\n "moduleResolution": "Bundler",\n "resolveJsonModule": true,\n "isolatedModules": true,\n "jsx": "react-jsx",\n "incremental": true,\n "plugins": [\n {\n "name": "next"\n }\n ],\n "paths": {\n "@/*": ["./*"]\n }\n },\n "include": [\n "next-env.d.ts",\n "**/*.ts",\n "**/*.tsx",\n ".next/types/**/*.ts",\n ".next/dev/types/**/*.ts"\n ],\n "exclude": ["node_modules"]\n}\n';
};
export declare const WEB_APP_SIGN_IN_WITH_VERCEL_TEMPLATE_FILES: {
readonly "agent/channels/eve.ts": 'import { eveChannel } from "eve/channels/eve";\nimport { localDev, type AuthFn, vercelOidc } from "eve/channels/auth";\nimport { auth } from "@/lib/auth";\n\nconst betterAuthSession: AuthFn<Request> = async (request) => {\n const session = await auth.api.getSession({ headers: request.headers });\n if (!session) return null;\n\n const attributes: Record<string, string> = {\n email: session.user.email,\n name: session.user.name,\n };\n if (session.user.image) {\n attributes.picture = session.user.image;\n }\n\n return {\n attributes,\n authenticator: "better-auth:vercel",\n principalId: session.user.id,\n principalType: "user",\n };\n};\n\nexport default eveChannel({\n auth: [betterAuthSession, vercelOidc(), localDev()],\n});\n';
readonly "app/_components/authenticated-agent-chat.tsx": 'import { headers } from "next/headers";\nimport { auth } from "@/lib/auth";\nimport { AgentChat } from "./agent-chat";\nimport { AccountControl, SignIn } from "./web-chat-auth";\n\nexport async function AuthenticatedAgentChat({\n sessionId,\n sessionless,\n}: {\n readonly sessionId?: string;\n readonly sessionless?: boolean;\n}) {\n if (process.env.NODE_ENV === "development") {\n return <AgentChat sessionId={sessionId} sessionless={sessionless} />;\n }\n\n const session = await auth.api.getSession({ headers: await headers() });\n if (!session) return <SignIn />;\n\n return (\n <>\n <AgentChat sessionId={sessionId} sessionless={sessionless} />\n <AccountControl\n email={session.user.email}\n image={session.user.image}\n name={session.user.name}\n />\n </>\n );\n}\n';
readonly "app/_components/web-chat-auth.tsx": '"use client";\n\nimport { LogOutIcon } from "lucide-react";\nimport { useState } from "react";\nimport { Button } from "@/components/ui/button";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from "@/components/ui/dropdown-menu";\nimport { authClient } from "@/lib/auth-client";\n\nconst AGENT_NAME = "__EVE_INIT_APP_NAME__";\n\nexport function SignIn() {\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<string>();\n\n async function signIn() {\n setPending(true);\n setError(undefined);\n try {\n const result = await authClient.signIn.social({\n callbackURL: "/",\n provider: "vercel",\n });\n if (!result.error) return;\n setPending(false);\n setError("Sign-in failed. Try again.");\n } catch {\n setPending(false);\n setError("Sign-in failed. Try again.");\n }\n }\n\n return (\n <main className="flex min-h-dvh items-center justify-center bg-background px-8 text-foreground">\n <div className="flex w-full max-w-[22rem] flex-col gap-5">\n <div className="text-foreground opacity-[0.08] dark:opacity-[0.12]">\n <EveWordmark className="h-auto w-[4.875rem]" />\n </div>\n <section aria-label="Sign in" className="flex flex-col gap-2">\n <h1 className="max-w-full break-words font-medium text-sm leading-6">{AGENT_NAME}</h1>\n <p className="flex flex-wrap items-center gap-2 text-muted-foreground text-sm leading-6">\n <span className="inline-flex items-center gap-2 text-emerald-600 dark:text-emerald-400">\n <span aria-hidden="true" className="size-1.5 rounded-full bg-current" />\n Ready\n </span>\n <span aria-hidden="true" className="text-border">\n /\n </span>\n <span>Sign in to start a session</span>\n </p>\n <Button className="mt-3 w-full gap-2 text-sm" disabled={pending} onClick={signIn}>\n <svg aria-hidden="true" className="size-3 fill-current" viewBox="0 0 24 20">\n <path d="M12 0 24 20H0L12 0Z" />\n </svg>\n <span className="leading-5">{pending ? "Redirecting…" : "Continue with Vercel"}</span>\n </Button>\n {error ? (\n <p className="text-destructive text-sm" role="alert">\n {error}\n </p>\n ) : null}\n </section>\n </div>\n </main>\n );\n}\n\nfunction EveWordmark({ className }: { readonly className?: string }) {\n return (\n <svg\n aria-hidden="true"\n className={className}\n fill="none"\n viewBox="0 0 169 53"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M169 8.47h-51.39L81.73 53H70.36L113 0H169zM169 44.51v8.47h-45.87V44.5zM45.87 52.98H0V44.5h45.87zM38.66 30.55H0v-8.47h38.66z"\n fill="currentColor"\n />\n <path d="M169 30.55h-38.66v-8.47H169zM75.52 8.47H0V0h75.52z" fill="currentColor" />\n </svg>\n );\n}\n\nexport function AccountControl({\n email,\n image,\n name,\n}: {\n readonly email: string;\n readonly image?: string | null;\n readonly name: string;\n}) {\n const [imageFailed, setImageFailed] = useState(false);\n const [pending, setPending] = useState(false);\n const initials = getInitials(name, email);\n\n async function signOut() {\n setPending(true);\n try {\n await authClient.signOut({\n fetchOptions: {\n onError: () => setPending(false),\n onSuccess: () => window.location.assign("/"),\n },\n });\n } catch {\n setPending(false);\n }\n }\n\n return (\n <div className="fixed top-3 left-4 z-30 flex h-8 items-center">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n aria-label={`Open account menu for ${name}`}\n className="relative size-7 cursor-pointer overflow-hidden rounded-full p-0"\n size="icon-sm"\n variant="ghost"\n >\n {image && !imageFailed ? (\n <img\n alt=""\n className="size-full object-cover"\n onError={() => setImageFailed(true)}\n src={image}\n />\n ) : (\n <span aria-hidden="true" className="font-medium text-xs">\n {initials}\n </span>\n )}\n <span\n aria-hidden="true"\n className="pointer-events-none absolute inset-0 rounded-full border border-black/20 dark:border-white/25"\n />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align="start" className="w-64">\n <div className="min-w-0 px-2 py-1.5 text-sm">\n <span className="block truncate font-medium leading-5" title={name}>\n {name}\n </span>\n <span className="block truncate text-muted-foreground leading-5" title={email}>\n {email}\n </span>\n </div>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className="cursor-pointer justify-between"\n disabled={pending}\n onSelect={signOut}\n >\n {pending ? "Logging out…" : "Log out"}\n <LogOutIcon aria-hidden="true" />\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n );\n}\n\nfunction getInitials(name: string, email: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length >= 2) {\n return `${parts[0]?.[0] ?? ""}${parts.at(-1)?.[0] ?? ""}`.toUpperCase();\n }\n return (parts[0]?.[0] ?? email[0] ?? "?").toUpperCase();\n}\n';
readonly "app/api/auth/[...all]/route.ts": 'import { toNextJsHandler } from "better-auth/next-js";\nimport { auth } from "@/lib/auth";\n\nexport const { GET, POST } = toNextJsHandler(auth);\n';
readonly "app/layout.tsx": 'import type { Metadata } from "next";\nimport { Geist, Geist_Mono } from "next/font/google";\nimport type { ReactNode } from "react";\nimport { TooltipProvider } from "@/components/ui/tooltip";\nimport { cn } from "@/lib/utils";\nimport "./globals.css";\n\nconst sans = Geist({\n variable: "--font-sans",\n subsets: ["latin"],\n weight: "variable",\n display: "swap",\n});\n\nconst mono = Geist_Mono({\n variable: "--font-mono",\n subsets: ["latin"],\n weight: "variable",\n display: "swap",\n});\n\nexport const metadata: Metadata = {\n title: "__EVE_INIT_APP_NAME__",\n description: "A Next.js starter for eve agents with AI Elements.",\n};\n\n// The page and Eve routes validate the generated app\'s Better Auth session.\nexport default function RootLayout({ children }: { readonly children: ReactNode }) {\n return (\n <html className={cn(sans.variable, mono.variable)} lang="en">\n <body>\n <TooltipProvider>{children}</TooltipProvider>\n </body>\n </html>\n );\n}\n';
readonly "app/page.tsx": 'import { AuthenticatedAgentChat } from "./_components/authenticated-agent-chat";\n\nexport default function Page() {\n return <AuthenticatedAgentChat />;\n}\n';
readonly "app/s/[sessionId]/page.tsx": 'import { AuthenticatedAgentChat } from "@/app/_components/authenticated-agent-chat";\n\nexport default async function SessionPage({\n params,\n}: {\n readonly params: Promise<{ readonly sessionId: string }>;\n}) {\n const { sessionId } = await params;\n return <AuthenticatedAgentChat sessionId={sessionId} />;\n}\n';
readonly "app/s/page.tsx": 'import { AuthenticatedAgentChat } from "@/app/_components/authenticated-agent-chat";\n\nexport default function NewSessionPage() {\n return <AuthenticatedAgentChat sessionless />;\n}\n';
readonly "lib/auth-client.ts": '"use client";\n\nimport { createAuthClient } from "better-auth/react";\n\nexport const authClient = createAuthClient();\n';
readonly "lib/auth.ts": 'import { betterAuth } from "better-auth";\n\nconst SESSION_MAX_AGE_SECONDS = 8 * 60 * 60;\nconst DEVELOPMENT_ALLOWED_HOSTS = ["localhost:*", "127.0.0.1:*"];\n\nfunction getAllowedHosts(): string[] {\n if (process.env.NODE_ENV === "development") {\n return DEVELOPMENT_ALLOWED_HOSTS;\n }\n const deploymentHosts = [\n process.env.VERCEL_URL,\n process.env.VERCEL_BRANCH_URL,\n process.env.VERCEL_PROJECT_PRODUCTION_URL,\n ].filter((host): host is string => Boolean(host));\n if (deploymentHosts.length === 0) {\n throw new Error("No trusted deployment hosts are configured");\n }\n return Array.from(new Set(deploymentHosts));\n}\n\nfunction requireEnvironmentVariable(name: string): string {\n const value = process.env[name];\n if (value) return value;\n if (process.env.NODE_ENV === "development") return `development-${name}`;\n throw new Error(`Missing required environment variable: ${name}`);\n}\n\nexport const auth = betterAuth({\n baseURL: {\n allowedHosts: getAllowedHosts(),\n protocol: process.env.NODE_ENV === "development" ? "auto" : "https",\n },\n secret: requireEnvironmentVariable("BETTER_AUTH_SECRET"),\n session: {\n expiresIn: SESSION_MAX_AGE_SECONDS,\n disableSessionRefresh: true,\n cookieCache: {\n enabled: true,\n maxAge: SESSION_MAX_AGE_SECONDS,\n refreshCache: false,\n strategy: "jwe",\n },\n },\n socialProviders: {\n vercel: {\n clientId: requireEnvironmentVariable("VERCEL_APP_CLIENT_ID"),\n clientSecret: requireEnvironmentVariable("VERCEL_APP_CLIENT_SECRET"),\n },\n },\n});\n';
};
export declare const WEB_APP_TEMPLATE_PACKAGE_JSON: {
readonly scripts: {
readonly build: "next build";
readonly "build:eve": "eve build";
readonly dev: "next dev";
readonly "dev:eve": "eve dev";
readonly start: "next start";
readonly "start:eve": "eve start";
readonly typecheck: "tsc --noEmit -p tsconfig.json";
};
readonly dependencies: {
readonly "@radix-ui/react-use-controllable-state": "1.2.2";
readonly "@shikijs/core": "3.23.0";
readonly "@shikijs/engine-javascript": "3.23.0";
readonly "@shikijs/engine-oniguruma": "3.23.0";
readonly "@streamdown/cjk": "1.0.3";
readonly "@streamdown/code": "1.1.1";
readonly "@streamdown/math": "1.0.2";
readonly "@streamdown/mermaid": "1.0.2";
readonly "@tailwindcss/postcss": "4.3.0";
readonly "class-variance-authority": "0.7.1";
readonly clsx: "2.1.1";
readonly cmdk: "1.1.1";
readonly "lucide-react": "1.16.0";
readonly motion: "12.40.0";
readonly nanoid: "5.1.11";
readonly next: "16.3.0-preview.6";
readonly "radix-ui": "1.4.3";
readonly react: "19.2.6";
readonly "react-dom": "19.2.6";
readonly shiki: "3.23.0";
readonly streamdown: "2.5.0";
readonly "tailwind-merge": "3.6.0";
readonly tailwindcss: "4.3.0";
readonly "use-stick-to-bottom": "1.1.4";
readonly zod: "4.5.4";
};
readonly devDependencies: {
readonly "@types/node": "26";
readonly "@types/react": "19.2.15";
readonly "@types/react-dom": "19.2.3";
readonly typescript: "6.0.3";
};
};