diff --git a/README.md b/README.md index 9c5f97b..c56e790 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ pnpm dev ├── 📂 app/ # Next.js App Router │ ├── 📂 components/ # React 组件 │ ├── 📂 docs/ # 文档内容 -│ │ └── 📂 computer-science/ # 计算机科学知识库 +│ │ └── 📂 ai/ # ai知识库 │ ├── 📄 layout.tsx # 根布局 │ └── 📄 page.tsx # 主页 ├── 📂 source.config.ts # Fumadocs 配置 diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts new file mode 100644 index 0000000..820c88f --- /dev/null +++ b/app/api/chat/route.ts @@ -0,0 +1,93 @@ +import { createOpenAI } from "@ai-sdk/openai"; +import { createGoogleGenerativeAI } from "@ai-sdk/google"; +import { streamText, UIMessage, convertToModelMessages } from "ai"; + +// Allow streaming responses up to 30 seconds +export const maxDuration = 30; + +export async function POST(req: Request) { + const { + messages, + system, + pageContext, + provider, + apiKey, + }: { + messages: UIMessage[]; + system?: string; // System message forwarded from AssistantChatTransport + tools?: unknown; // Frontend tools forwarded from AssistantChatTransport + pageContext?: { + title?: string; + description?: string; + content?: string; + slug?: string; + }; + provider?: "openai" | "gemini"; + apiKey?: string; + } = await req.json(); + + // Check if API key is provided + if (!apiKey || apiKey.trim() === "") { + return Response.json( + { + error: + "API key is required. Please configure your API key in the settings.", + }, + { status: 400 }, + ); + } + + try { + // Build system message with page context + let systemMessage = + system || + `You are a helpful AI assistant for a documentation website. + You can help users understand the documentation, answer questions about the content, + and provide guidance on the topics covered in the docs. Be concise and helpful.`; + + // Add current page context if available + if (pageContext?.content) { + systemMessage += `\n\n--- CURRENT PAGE CONTEXT ---\n`; + if (pageContext.title) { + systemMessage += `Page Title: ${pageContext.title}\n`; + } + if (pageContext.description) { + systemMessage += `Page Description: ${pageContext.description}\n`; + } + if (pageContext.slug) { + systemMessage += `Page URL: /docs/${pageContext.slug}\n`; + } + systemMessage += `Page Content:\n${pageContext.content}`; + systemMessage += `\n--- END OF CONTEXT ---\n\nWhen users ask about "this page", "current page", or refer to the content they're reading, use the above context to provide accurate answers. You can summarize, explain, or answer specific questions about the current page content.`; + } + + // Select model based on provider + let model; + if (provider === "gemini") { + const customGoogle = createGoogleGenerativeAI({ + apiKey: apiKey, + }); + model = customGoogle("models/gemini-2.0-flash"); + } else { + // Default to OpenAI + const customOpenAI = createOpenAI({ + apiKey: apiKey, + }); + model = customOpenAI("gpt-4.1-nano"); + } + + const result = streamText({ + model: model, + system: systemMessage, + messages: convertToModelMessages(messages), + }); + + return result.toUIMessageStreamResponse(); + } catch (error) { + console.error("Chat API error:", error); + return Response.json( + { error: "Failed to process chat request" }, + { status: 500 }, + ); + } +} diff --git a/app/components/Contribute.tsx b/app/components/Contribute.tsx index 35f3ac0..c863f76 100644 --- a/app/components/Contribute.tsx +++ b/app/components/Contribute.tsx @@ -27,10 +27,11 @@ type DirNode = { name: string; path: string; children?: DirNode[] }; function buildGithubNewUrl(dirPath: string, filename: string, title: string) { const file = filename.endsWith(".mdx") ? filename : `${filename}.mdx`; const frontMatter = `--- -title: ${title || "New Article"} -description: -date: ${new Date().toISOString().slice(0, 10)} -tags: [] +title: '${title || "New Article"}' +description: "" +date: "${new Date().toISOString().slice(0, 10)}" +tags: + - tag-one --- # ${title || "New Article"} diff --git a/app/components/DocsAssistant.tsx b/app/components/DocsAssistant.tsx new file mode 100644 index 0000000..8e25a62 --- /dev/null +++ b/app/components/DocsAssistant.tsx @@ -0,0 +1,235 @@ +"use client"; + +import { useCallback, useEffect, useState, useRef } from "react"; + +import { AssistantRuntimeProvider } from "@assistant-ui/react"; +import { useAISDKRuntime } from "@assistant-ui/react-ai-sdk"; +import { useChat } from "@ai-sdk/react"; +import { DefaultChatTransport } from "ai"; +import { AssistantModal } from "@/app/components/assistant-ui/assistant-modal"; +import { + AssistantSettingsProvider, + useAssistantSettings, +} from "@/app/hooks/useAssistantSettings"; + +interface PageContext { + title?: string; + description?: string; + content?: string; + slug?: string; +} + +interface DocsAssistantProps { + pageContext: PageContext; +} + +export function DocsAssistant({ pageContext }: DocsAssistantProps) { + return ( + + + + ); +} + +function DocsAssistantInner({ pageContext }: DocsAssistantProps) { + const { provider, openaiApiKey, geminiApiKey } = useAssistantSettings(); + + // Use refs to ensure we always get the latest values + const providerRef = useRef(provider); + const openaiApiKeyRef = useRef(openaiApiKey); + const geminiApiKeyRef = useRef(geminiApiKey); + + // Update refs whenever the values change + providerRef.current = provider; + openaiApiKeyRef.current = openaiApiKey; + geminiApiKeyRef.current = geminiApiKey; + + const chat = useChat({ + transport: new DefaultChatTransport({ + api: "/api/chat", + body: () => { + // Use refs to get the current values at request time + const currentProvider = providerRef.current; + const currentApiKey = + currentProvider === "openai" + ? openaiApiKeyRef.current + : geminiApiKeyRef.current; + + console.log("[DocsAssistant] useChat body function called with:", { + provider: currentProvider, + apiKeyLength: currentApiKey.length, + hasApiKey: currentApiKey.trim().length > 0, + }); + + return { + pageContext, + provider: currentProvider, + apiKey: currentApiKey, + }; + }, + }), + }); + + const { + error: chatError, + status: chatStatus, + clearError: clearChatError, + } = chat; + const [assistantError, setAssistantError] = + useState(null); + + useEffect(() => { + if (!chatError) { + return; + } + + setAssistantError(deriveAssistantError(chatError, provider)); + clearChatError(); + }, [chatError, clearChatError, provider]); + + useEffect(() => { + if (chatStatus === "submitted" || chatStatus === "streaming") { + setAssistantError(null); + } + }, [chatStatus]); + + const handleClearError = useCallback(() => { + setAssistantError(null); + clearChatError(); + }, [clearChatError]); + + const runtime = useAISDKRuntime(chat); + + return ( + + + + ); +} + +interface AssistantErrorState { + message: string; + showSettingsCTA: boolean; +} + +function deriveAssistantError( + err: unknown, + provider: "openai" | "gemini", +): AssistantErrorState { + const providerLabel = provider === "gemini" ? "Google Gemini" : "OpenAI"; + const fallback: AssistantErrorState = { + message: + "The assistant couldn't complete that request. Please try again later.", + showSettingsCTA: false, + }; + + if (!err) { + return fallback; + } + + const maybeError = err as Partial<{ + message?: string; + statusCode?: number; + responseBody?: string; + data?: unknown; + }>; + + let message = ""; + + if ( + typeof maybeError.message === "string" && + maybeError.message.trim().length > 0 + ) { + message = maybeError.message.trim(); + } + + if ( + typeof maybeError.responseBody === "string" && + maybeError.responseBody.trim().length > 0 + ) { + const extracted = extractErrorFromResponseBody(maybeError.responseBody); + if (extracted) { + message = extracted; + } + } + + if (!message && err instanceof Error && typeof err.message === "string") { + message = err.message.trim(); + } + + if (!message && maybeError.data && typeof maybeError.data === "object") { + const dataError = (maybeError.data as { error?: unknown }).error; + if (typeof dataError === "string" && dataError.trim().length > 0) { + message = dataError.trim(); + } + } + + const statusCode = + typeof maybeError.statusCode === "number" + ? maybeError.statusCode + : undefined; + const normalized = message.toLowerCase(); + + let showSettingsCTA = false; + + if ( + statusCode === 400 || + statusCode === 401 || + statusCode === 403 || + normalized.includes("api key") || + normalized.includes("apikey") || + normalized.includes("missing key") || + normalized.includes("unauthorized") + ) { + showSettingsCTA = true; + } + + let friendlyMessage = message || fallback.message; + + if (showSettingsCTA) { + friendlyMessage = + message && message.length > 0 + ? message + : `The ${providerLabel} API key looks incorrect. Update it in settings and try again.`; + } else if (statusCode === 429) { + friendlyMessage = + "The provider is rate limiting requests. Please wait and try again."; + } else if (statusCode && statusCode >= 500) { + friendlyMessage = + "The AI provider is currently unavailable. Please try again soon."; + } + + return { + message: friendlyMessage, + showSettingsCTA, + }; +} + +function extractErrorFromResponseBody(body: string): string | undefined { + const trimmed = body.trim(); + if (!trimmed) { + return undefined; + } + + try { + const parsed = JSON.parse(trimmed); + if (typeof parsed === "string") { + return parsed.trim(); + } + if ( + parsed && + typeof parsed === "object" && + typeof (parsed as { error?: unknown }).error === "string" + ) { + return (parsed as { error: string }).error.trim(); + } + } catch { + // Ignore JSON parsing issues and fall back to the raw body text. + } + + return trimmed; +} diff --git a/app/components/assistant-ui/SettingsButton.tsx b/app/components/assistant-ui/SettingsButton.tsx new file mode 100644 index 0000000..6abc588 --- /dev/null +++ b/app/components/assistant-ui/SettingsButton.tsx @@ -0,0 +1,14 @@ +import { SettingsIcon } from "lucide-react"; +import { TooltipIconButton } from "./tooltip-icon-button"; + +interface SettingsButtonProps { + onClick: () => void; +} + +export const SettingsButton = ({ onClick }: SettingsButtonProps) => { + return ( + + + + ); +}; diff --git a/app/components/assistant-ui/SettingsDialog.tsx b/app/components/assistant-ui/SettingsDialog.tsx new file mode 100644 index 0000000..a8520cf --- /dev/null +++ b/app/components/assistant-ui/SettingsDialog.tsx @@ -0,0 +1,101 @@ +"use client"; + +import { useAssistantSettings } from "@/app/hooks/useAssistantSettings"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/components/ui/dialog"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { RadioGroup, RadioGroupItem } from "@/app/components/ui/radio-group"; +import { Button } from "@/components/ui/button"; + +interface SettingsDialogProps { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; +} + +export const SettingsDialog = ({ + isOpen, + onOpenChange, +}: SettingsDialogProps) => { + const { + provider, + setProvider, + openaiApiKey, + setOpenaiApiKey, + geminiApiKey, + setGeminiApiKey, + refreshFromStorage, + } = useAssistantSettings(); + + const handleSave = () => { + // Force refresh from localStorage to ensure all components get the latest values + refreshFromStorage(); + onOpenChange(false); + }; + + return ( + + + + AI Assistant Settings + + +
+
+ + + setProvider(value as "openai" | "gemini") + } + > +
+ + +
+
+ + +
+
+
+ + {provider === "openai" && ( +
+ + setOpenaiApiKey(e.target.value)} + /> +
+ )} + + {provider === "gemini" && ( +
+ + setGeminiApiKey(e.target.value)} + /> +
+ )} +
+ + + + +
+
+ ); +}; diff --git a/app/components/assistant-ui/assistant-modal.tsx b/app/components/assistant-ui/assistant-modal.tsx new file mode 100644 index 0000000..ee46937 --- /dev/null +++ b/app/components/assistant-ui/assistant-modal.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { BotIcon, ChevronDownIcon } from "lucide-react"; + +import { type FC, forwardRef } from "react"; +import { AssistantModalPrimitive } from "@assistant-ui/react"; + +import { Thread } from "@/app/components/assistant-ui/thread"; +import { TooltipIconButton } from "@/app/components/assistant-ui/tooltip-icon-button"; + +interface AssistantModalProps { + errorMessage?: string; + showSettingsAction?: boolean; + onClearError?: () => void; +} + +export const AssistantModal: FC = ({ + errorMessage, + showSettingsAction = false, + onClearError, +}) => { + return ( + + + + + + + + + + + ); +}; + +type AssistantModalButtonProps = { "data-state"?: "open" | "closed" }; + +const AssistantModalButton = forwardRef< + HTMLButtonElement, + AssistantModalButtonProps +>(({ "data-state": state, ...rest }, ref) => { + const tooltip = state === "open" ? "Close Assistant" : "Open Assistant"; + + return ( + + + + + {tooltip} + + ); +}); + +AssistantModalButton.displayName = "AssistantModalButton"; diff --git a/app/components/assistant-ui/attachment.tsx b/app/components/assistant-ui/attachment.tsx new file mode 100644 index 0000000..e8e3c69 --- /dev/null +++ b/app/components/assistant-ui/attachment.tsx @@ -0,0 +1,239 @@ +"use client"; + +import { PropsWithChildren, useEffect, useState, type FC } from "react"; +import Image from "next/image"; +import { XIcon, PlusIcon, FileText } from "lucide-react"; +import { + AttachmentPrimitive, + ComposerPrimitive, + MessagePrimitive, + useAssistantState, + useAssistantApi, +} from "@assistant-ui/react"; +import { useShallow } from "zustand/shallow"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/app/components/ui/tooltip"; +import { + Dialog, + DialogTitle, + DialogContent, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Avatar, + AvatarImage, + AvatarFallback, +} from "@/app/components/ui/avatar"; +import { TooltipIconButton } from "@/app/components/assistant-ui/tooltip-icon-button"; +import { cn } from "@/lib/utils"; + +const useFileSrc = (file: File | undefined) => { + const [src, setSrc] = useState(undefined); + + useEffect(() => { + if (!file) { + setSrc(undefined); + return; + } + + const objectUrl = URL.createObjectURL(file); + setSrc(objectUrl); + + return () => { + URL.revokeObjectURL(objectUrl); + }; + }, [file]); + + return src; +}; + +const useAttachmentSrc = () => { + const { file, src } = useAssistantState( + useShallow(({ attachment }): { file?: File; src?: string } => { + if (attachment.type !== "image") return {}; + if (attachment.file) return { file: attachment.file }; + const src = attachment.content?.filter((c) => c.type === "image")[0] + ?.image; + if (!src) return {}; + return { src }; + }), + ); + + return useFileSrc(file) ?? src; +}; + +type AttachmentPreviewProps = { + src: string; +}; + +const AttachmentPreview: FC = ({ src }) => { + const [isLoaded, setIsLoaded] = useState(false); + return ( + Image Preview setIsLoaded(true)} + priority={false} + /> + ); +}; + +const AttachmentPreviewDialog: FC = ({ children }) => { + const src = useAttachmentSrc(); + + if (!src) return children; + + return ( + + + {children} + + + + Image Attachment Preview + +
+ +
+
+
+ ); +}; + +const AttachmentThumb: FC = () => { + const isImage = useAssistantState( + ({ attachment }) => attachment.type === "image", + ); + const src = useAttachmentSrc(); + + return ( + + + + + + + ); +}; + +const AttachmentUI: FC = () => { + const api = useAssistantApi(); + const isComposer = api.attachment.source === "composer"; + + const isImage = useAssistantState( + ({ attachment }) => attachment.type === "image", + ); + const typeLabel = useAssistantState(({ attachment }) => { + const type = attachment.type; + switch (type) { + case "image": + return "Image"; + case "document": + return "Document"; + case "file": + return "File"; + default: + const _exhaustiveCheck: never = type; + throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`); + } + }); + + return ( + + #attachment-tile]:size-24", + )} + > + + +
+ +
+
+
+ {isComposer && } +
+ + + +
+ ); +}; + +const AttachmentRemove: FC = () => { + return ( + + + + + + ); +}; + +export const UserMessageAttachments: FC = () => { + return ( +
+ +
+ ); +}; + +export const ComposerAttachments: FC = () => { + return ( +
+ +
+ ); +}; + +export const ComposerAddAttachment: FC = () => { + return ( + + + + + + ); +}; diff --git a/app/components/assistant-ui/markdown-text.tsx b/app/components/assistant-ui/markdown-text.tsx new file mode 100644 index 0000000..89275b9 --- /dev/null +++ b/app/components/assistant-ui/markdown-text.tsx @@ -0,0 +1,228 @@ +"use client"; + +import "@assistant-ui/react-markdown/styles/dot.css"; + +import { + type CodeHeaderProps, + MarkdownTextPrimitive, + unstable_memoizeMarkdownComponents as memoizeMarkdownComponents, + useIsMarkdownCodeBlock, +} from "@assistant-ui/react-markdown"; +import remarkGfm from "remark-gfm"; +import { type FC, memo, useState } from "react"; +import { CheckIcon, CopyIcon } from "lucide-react"; + +import { TooltipIconButton } from "@/app/components/assistant-ui/tooltip-icon-button"; +import { cn } from "@/lib/utils"; + +const MarkdownTextImpl = () => { + return ( + + ); +}; + +export const MarkdownText = memo(MarkdownTextImpl); + +const CodeHeader: FC = ({ language, code }) => { + const { isCopied, copyToClipboard } = useCopyToClipboard(); + const onCopy = () => { + if (!code || isCopied) return; + copyToClipboard(code); + }; + + return ( +
+ + {language} + + + {!isCopied && } + {isCopied && } + +
+ ); +}; + +const useCopyToClipboard = ({ + copiedDuration = 3000, +}: { + copiedDuration?: number; +} = {}) => { + const [isCopied, setIsCopied] = useState(false); + + const copyToClipboard = (value: string) => { + if (!value) return; + + navigator.clipboard.writeText(value).then(() => { + setIsCopied(true); + setTimeout(() => setIsCopied(false), copiedDuration); + }); + }; + + return { isCopied, copyToClipboard }; +}; + +const defaultComponents = memoizeMarkdownComponents({ + h1: ({ className, ...props }) => ( +

+ ), + h2: ({ className, ...props }) => ( +

+ ), + h3: ({ className, ...props }) => ( +

+ ), + h4: ({ className, ...props }) => ( +

+ ), + h5: ({ className, ...props }) => ( +

+ ), + h6: ({ className, ...props }) => ( +
+ ), + p: ({ className, ...props }) => ( +

+ ), + a: ({ className, ...props }) => ( + + ), + blockquote: ({ className, ...props }) => ( +

+ ), + ul: ({ className, ...props }) => ( +
    li]:mt-2", className)} + {...props} + /> + ), + ol: ({ className, ...props }) => ( +
      li]:mt-2", className)} + {...props} + /> + ), + hr: ({ className, ...props }) => ( +
      + ), + table: ({ className, ...props }) => ( + + ), + th: ({ className, ...props }) => ( + td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg", + className, + )} + {...props} + /> + ), + sup: ({ className, ...props }) => ( + a]:text-xs [&>a]:no-underline", className)} + {...props} + /> + ), + pre: ({ className, ...props }) => ( +
      +  ),
      +  code: function Code({ className, ...props }) {
      +    const isCodeBlock = useIsMarkdownCodeBlock();
      +    return (
      +      
      +    );
      +  },
      +  CodeHeader,
      +});
      diff --git a/app/components/assistant-ui/thread.tsx b/app/components/assistant-ui/thread.tsx
      new file mode 100644
      index 0000000..c71b6cc
      --- /dev/null
      +++ b/app/components/assistant-ui/thread.tsx
      @@ -0,0 +1,550 @@
      +import {
      +  ActionBarPrimitive,
      +  BranchPickerPrimitive,
      +  ComposerPrimitive,
      +  ErrorPrimitive,
      +  MessagePrimitive,
      +  ThreadPrimitive,
      +} from "@assistant-ui/react";
      +import {
      +  AlertCircleIcon,
      +  ArrowDownIcon,
      +  ArrowUpIcon,
      +  CheckIcon,
      +  ChevronLeftIcon,
      +  ChevronRightIcon,
      +  CopyIcon,
      +  LockIcon,
      +  PencilIcon,
      +  RefreshCwIcon,
      +  Square,
      +} from "lucide-react";
      +import type { FC } from "react";
      +import { useCallback, useState } from "react";
      +
      +import {
      +  ComposerAttachments,
      +  UserMessageAttachments,
      +} from "@/app/components/assistant-ui/attachment";
      +import { SettingsButton } from "@/app/components/assistant-ui/SettingsButton";
      +import { SettingsDialog } from "@/app/components/assistant-ui/SettingsDialog";
      +import { MarkdownText } from "@/app/components/assistant-ui/markdown-text";
      +import { ToolFallback } from "@/app/components/assistant-ui/tool-fallback";
      +import { TooltipIconButton } from "@/app/components/assistant-ui/tooltip-icon-button";
      +import { useAssistantSettings } from "@/app/hooks/useAssistantSettings";
      +import { Button } from "@/components/ui/button";
      +import { cn } from "@/lib/utils";
      +import { LazyMotion, MotionConfig, domAnimation } from "motion/react";
      +import * as m from "motion/react-m";
      +
      +interface ThreadProps {
      +  errorMessage?: string;
      +  showSettingsAction?: boolean;
      +  onClearError?: () => void;
      +}
      +
      +export const Thread: FC = ({
      +  errorMessage,
      +  showSettingsAction = false,
      +  onClearError,
      +}) => {
      +  const [isSettingsOpen, setIsSettingsOpen] = useState(false);
      +
      +  const handleSettingsChange = useCallback(
      +    (open: boolean) => {
      +      if (open) {
      +        onClearError?.();
      +      }
      +      setIsSettingsOpen(open);
      +    },
      +    [onClearError],
      +  );
      +
      +  const handleOpenSettings = useCallback(() => {
      +    handleSettingsChange(true);
      +  }, [handleSettingsChange]);
      +
      +  return (
      +    
      +      
      +        
      +          
      +            
      +
      +            
      +            {errorMessage ? (
      +              
      +            ) : null}
      +            
      +              
      + + + + + + + ); +}; + +interface ThreadErrorNoticeProps { + message: string; + onDismiss?: () => void; + onOpenSettings?: () => void; +} + +const ThreadErrorNotice: FC = ({ + message, + onDismiss, + onOpenSettings, +}) => { + return ( +
      +
      + +
      + + {message} + +
      + {onOpenSettings ? ( + + ) : null} + {onDismiss ? ( + + ) : null} +
      +
      +
      +
      + ); +}; + +const ThreadScrollToBottom: FC = () => { + return ( + + + + + + ); +}; + +const ThreadWelcome: FC = () => { + return ( + +
      +
      +
      + + Hello there! + + + How can I help you today? + +
      +
      +
      +
      + ); +}; + +const ThreadWelcomeSuggestions: FC = () => { + return ( +
      + {[ + { + title: "总结本文", + label: "内容要点", + action: "请帮我总结一下当前页面的主要内容和要点", + }, + { + title: "什么是基座大模型", + label: "概念解释", + action: "什么是基座大模型?请详细解释一下", + }, + { + title: "解释技术概念", + label: "深入理解", + action: "请解释一下这个页面中提到的核心技术概念", + }, + { + title: "学习建议", + label: "如何入门", + action: "基于当前内容,你能给出一些学习建议和入门路径吗?", + }, + ].map((suggestedAction, index) => ( + + + + + + ))} +
      + ); +}; + +interface ComposerProps { + isSettingsOpen: boolean; + onOpenChange: (open: boolean) => void; + onClearError?: () => void; +} + +const Composer: FC = ({ + isSettingsOpen, + onOpenChange, + onClearError, +}) => { + const { provider, openaiApiKey, geminiApiKey } = useAssistantSettings(); + const activeKey = provider === "openai" ? openaiApiKey : geminiApiKey; + const hasActiveKey = activeKey.trim().length > 0; + const providerLabel = provider === "gemini" ? "Google Gemini" : "OpenAI"; + + const handleOpenSettings = useCallback(() => { + onClearError?.(); + onOpenChange(true); + }, [onClearError, onOpenChange]); + + return ( +
      + + + + + + {!hasActiveKey && ( +
      +

      + Add your {providerLabel} API key in Settings to start chatting. +

      + +
      + )} + + + +
      +
      + ); +}; + +interface ComposerActionProps { + canSend: boolean; + isSettingsOpen: boolean; + onOpenChange: (open: boolean) => void; + onOpenSettings: () => void; + onClearError?: () => void; +} + +const ComposerAction: FC = ({ + canSend, + isSettingsOpen, + onOpenChange, + onOpenSettings, + onClearError, +}) => { + return ( + <> +
      + + + + {canSend ? ( + + + + + + ) : ( + + + + )} + + + + + + + +
      + + + ); +}; + +const MessageError: FC = () => { + return ( + + + + + + ); +}; + +const AssistantMessage: FC = () => { + return ( + +
      +
      + + +
      + +
      + + +
      +
      +
      + ); +}; + +const AssistantActionBar: FC = () => { + return ( + + + + + + + + + + + + + + + + + + ); +}; + +const UserMessage: FC = () => { + return ( + +
      + + +
      +
      + +
      +
      + +
      +
      + + +
      +
      + ); +}; + +const UserActionBar: FC = () => { + return ( + + + + + + + + ); +}; + +const EditComposer: FC = () => { + return ( +
      + + + +
      + + + + + + +
      +
      +
      + ); +}; + +const BranchPicker: FC = ({ + className, + ...rest +}) => { + return ( + + + + + + + + / + + + + + + + + ); +}; diff --git a/app/components/assistant-ui/tool-fallback.tsx b/app/components/assistant-ui/tool-fallback.tsx new file mode 100644 index 0000000..aca4030 --- /dev/null +++ b/app/components/assistant-ui/tool-fallback.tsx @@ -0,0 +1,46 @@ +import type { ToolCallMessagePartComponent } from "@assistant-ui/react"; +import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"; +import { useState } from "react"; +import { Button } from "@/components/ui/button"; + +export const ToolFallback: ToolCallMessagePartComponent = ({ + toolName, + argsText, + result, +}) => { + const [isCollapsed, setIsCollapsed] = useState(true); + return ( +
      +
      + +

      + Used tool: {toolName} +

      + +
      + {!isCollapsed && ( +
      +
      +
      +              {argsText}
      +            
      +
      + {result !== undefined && ( +
      +

      + Result: +

      +
      +                {typeof result === "string"
      +                  ? result
      +                  : JSON.stringify(result, null, 2)}
      +              
      +
      + )} +
      + )} +
      + ); +}; diff --git a/app/components/assistant-ui/tooltip-icon-button.tsx b/app/components/assistant-ui/tooltip-icon-button.tsx new file mode 100644 index 0000000..3028635 --- /dev/null +++ b/app/components/assistant-ui/tooltip-icon-button.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { ComponentPropsWithRef, forwardRef } from "react"; +import { Slottable } from "@radix-ui/react-slot"; + +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/app/components/ui/tooltip"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +export type TooltipIconButtonProps = ComponentPropsWithRef & { + tooltip: string; + side?: "top" | "bottom" | "left" | "right"; +}; + +export const TooltipIconButton = forwardRef< + HTMLButtonElement, + TooltipIconButtonProps +>(({ children, tooltip, side = "bottom", className, ...rest }, ref) => { + return ( + + + + + {tooltip} + + ); +}); + +TooltipIconButton.displayName = "TooltipIconButton"; diff --git a/app/components/ui/avatar.tsx b/app/components/ui/avatar.tsx new file mode 100644 index 0000000..c4475c2 --- /dev/null +++ b/app/components/ui/avatar.tsx @@ -0,0 +1,53 @@ +"use client"; + +import * as React from "react"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; + +import { cn } from "@/lib/utils"; + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/app/components/ui/button.tsx b/app/components/ui/button.tsx new file mode 100644 index 0000000..2adaf00 --- /dev/null +++ b/app/components/ui/button.tsx @@ -0,0 +1,59 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", + destructive: + "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", + ghost: + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean; + }) { + const Comp = asChild ? Slot : "button"; + + return ( + + ); +} + +export { Button, buttonVariants }; diff --git a/app/components/ui/dialog.tsx b/app/components/ui/dialog.tsx new file mode 100644 index 0000000..7d60dd3 --- /dev/null +++ b/app/components/ui/dialog.tsx @@ -0,0 +1,143 @@ +"use client"; + +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { XIcon } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +function Dialog({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean; +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ); +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
      + ); +} + +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
      + ); +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +}; diff --git a/app/components/ui/label.tsx b/app/components/ui/label.tsx new file mode 100644 index 0000000..79d77b4 --- /dev/null +++ b/app/components/ui/label.tsx @@ -0,0 +1,24 @@ +"use client"; + +import * as React from "react"; +import * as LabelPrimitive from "@radix-ui/react-label"; + +import { cn } from "@/lib/utils"; + +function Label({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Label }; diff --git a/app/components/ui/radio-group.tsx b/app/components/ui/radio-group.tsx new file mode 100644 index 0000000..bc5495a --- /dev/null +++ b/app/components/ui/radio-group.tsx @@ -0,0 +1,45 @@ +"use client"; + +import * as React from "react"; +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"; +import { CircleIcon } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +function RadioGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function RadioGroupItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ); +} + +export { RadioGroup, RadioGroupItem }; diff --git a/app/components/ui/tooltip.tsx b/app/components/ui/tooltip.tsx new file mode 100644 index 0000000..bf4a342 --- /dev/null +++ b/app/components/ui/tooltip.tsx @@ -0,0 +1,61 @@ +"use client"; + +import * as React from "react"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; + +import { cn } from "@/lib/utils"; + +function TooltipProvider({ + delayDuration = 0, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function Tooltip({ + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +function TooltipTrigger({ + ...props +}: React.ComponentProps) { + return ; +} + +function TooltipContent({ + className, + sideOffset = 0, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + ); +} + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/app/docs/[...slug]/page.tsx b/app/docs/[...slug]/page.tsx index a415990..73ce5ab 100644 --- a/app/docs/[...slug]/page.tsx +++ b/app/docs/[...slug]/page.tsx @@ -7,6 +7,26 @@ import { GiscusComments } from "@/app/components/GiscusComments"; import { EditOnGithub } from "@/app/components/EditOnGithub"; import { buildDocsEditUrl, getContributors } from "@/lib/github"; import { Contributors } from "@/app/components/Contributors"; +import { DocsAssistant } from "@/app/components/DocsAssistant"; +import fs from "fs/promises"; +import path from "path"; + +// Extract clean text content from MDX +function extractTextFromMDX(content: string): string { + return content + .replace(/^---[\s\S]*?---/m, "") // Remove frontmatter + .replace(/```[\s\S]*?```/g, "") // Remove code blocks + .replace(/`([^`]+)`/g, "$1") // Remove inline code + .replace(/<[^>]+>/g, "") // Remove HTML/MDX tags + .replace(/\*\*([^*]+)\*\*/g, "$1") // Remove bold + .replace(/\*([^*]+)\*/g, "$1") // Remove italic + .replace(/#{1,6}\s+/g, "") // Remove headers + .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") // Remove links, keep text + .replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1") // Remove images, keep alt text + .replace(/[#*`\[\]()!]/g, "") // Remove common markdown symbols + .replace(/\n{2,}/g, "\n") // Normalize line breaks + .trim(); +} interface Param { params: Promise<{ @@ -30,20 +50,46 @@ export default async function DocPage({ params }: Param) { const contributors = await getContributors(filePath); const Mdx = page.data.body; + // Prepare page content for AI assistant + let pageContentForAI = ""; + try { + const fullFilePath = path.join(process.cwd(), "app/docs", page.file.path); + const rawContent = await fs.readFile(fullFilePath, "utf-8"); + const extractedText = extractTextFromMDX(rawContent); + // Use full extracted content without truncation + pageContentForAI = extractedText; + } catch (error) { + console.warn("Failed to read file content for AI assistant:", error); + // Fallback to using page metadata + pageContentForAI = `${page.data.title}\n${page.data.description || ""}`; + } + return ( - - -
      -

      - {page.data.title} -

      - -
      - - - -
      -
      + <> + + +
      +

      + {page.data.title} +

      + +
      + + +
      + +
      +
      +
      + + ); } diff --git a/app/hooks/useAssistantSettings.tsx b/app/hooks/useAssistantSettings.tsx new file mode 100644 index 0000000..878d697 --- /dev/null +++ b/app/hooks/useAssistantSettings.tsx @@ -0,0 +1,149 @@ +"use client"; + +import { + createContext, + useContext, + useEffect, + useMemo, + useState, + useCallback, +} from "react"; +import type { ReactNode } from "react"; + +type Provider = "openai" | "gemini"; + +interface AssistantSettingsState { + provider: Provider; + openaiApiKey: string; + geminiApiKey: string; +} + +interface AssistantSettingsContextValue extends AssistantSettingsState { + setProvider: (provider: Provider) => void; + setOpenaiApiKey: (key: string) => void; + setGeminiApiKey: (key: string) => void; + refreshFromStorage: () => void; +} + +const SETTINGS_KEY = "assistant-settings-storage"; + +const defaultSettings: AssistantSettingsState = { + provider: "openai", + openaiApiKey: "", + geminiApiKey: "", +}; + +const AssistantSettingsContext = createContext< + AssistantSettingsContextValue | undefined +>(undefined); + +const parseStoredSettings = (raw: string | null): AssistantSettingsState => { + if (!raw) { + return { ...defaultSettings }; + } + + try { + const parsed = JSON.parse(raw) as Partial; + return { + provider: parsed.provider === "gemini" ? "gemini" : "openai", + openaiApiKey: + typeof parsed.openaiApiKey === "string" ? parsed.openaiApiKey : "", + geminiApiKey: + typeof parsed.geminiApiKey === "string" ? parsed.geminiApiKey : "", + }; + } catch (error) { + console.error( + "Failed to parse assistant settings from localStorage", + error, + ); + return { ...defaultSettings }; + } +}; + +const readStoredSettings = (): AssistantSettingsState => { + if (typeof window === "undefined") { + return { ...defaultSettings }; + } + + const raw = window.localStorage.getItem(SETTINGS_KEY); + return parseStoredSettings(raw); +}; + +export const AssistantSettingsProvider = ({ + children, +}: { + children: ReactNode; +}) => { + const [settings, setSettings] = useState(() => + readStoredSettings(), + ); + + useEffect(() => { + if (typeof window === "undefined") { + return; + } + + try { + window.localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); + } catch (error) { + console.error("Failed to save assistant settings to localStorage", error); + } + }, [settings]); + + useEffect(() => { + if (typeof window === "undefined") { + return; + } + + const handleStorage = (event: StorageEvent) => { + if (event.key !== SETTINGS_KEY) { + return; + } + + setSettings(parseStoredSettings(event.newValue)); + }; + + window.addEventListener("storage", handleStorage); + return () => window.removeEventListener("storage", handleStorage); + }, []); + + const refreshFromStorage = useCallback(() => { + const latestSettings = readStoredSettings(); + setSettings(latestSettings); + }, []); + + const value = useMemo( + (): AssistantSettingsContextValue => ({ + ...settings, + setProvider: (provider: Provider) => { + setSettings((prev) => ({ ...prev, provider })); + }, + setOpenaiApiKey: (key: string) => { + setSettings((prev) => ({ ...prev, openaiApiKey: key })); + }, + setGeminiApiKey: (key: string) => { + setSettings((prev) => ({ ...prev, geminiApiKey: key })); + }, + refreshFromStorage, + }), + [settings, refreshFromStorage], + ); + + return ( + + {children} + + ); +}; + +export const useAssistantSettings = () => { + const context = useContext(AssistantSettingsContext); + + if (!context) { + throw new Error( + "useAssistantSettings must be used within an AssistantSettingsProvider", + ); + } + + return context; +}; diff --git a/components.json b/components.json index b7b9791..d1b5548 100644 --- a/components.json +++ b/components.json @@ -12,9 +12,9 @@ }, "iconLibrary": "lucide", "aliases": { - "components": "@/components", + "components": "@/app/components", "utils": "@/lib/utils", - "ui": "@/components/ui", + "ui": "@/app/components/ui", "lib": "@/lib", "hooks": "@/hooks" }, diff --git a/components/ui/label.tsx b/components/ui/label.tsx new file mode 100644 index 0000000..79d77b4 --- /dev/null +++ b/components/ui/label.tsx @@ -0,0 +1,24 @@ +"use client"; + +import * as React from "react"; +import * as LabelPrimitive from "@radix-ui/react-label"; + +import { cn } from "@/lib/utils"; + +function Label({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Label }; diff --git a/package.json b/package.json index d734e08..8d3f082 100644 --- a/package.json +++ b/package.json @@ -14,13 +14,24 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@ai-sdk/google": "^2.0.14", + "@ai-sdk/openai": "^2.0.32", + "@ai-sdk/react": "^2.0.45", + "@assistant-ui/react": "^0.11.10", + "@assistant-ui/react-ai-sdk": "^1.1.0", + "@assistant-ui/react-markdown": "^0.11.0", "@giscus/react": "^3.1.0", "@orama/orama": "^3.1.13", "@orama/tokenizers": "^3.1.13", + "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-radio-group": "^1.3.8", "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tooltip": "^1.2.8", "@types/mdx": "^2.0.13", "@vercel/speed-insights": "^1.2.0", + "ai": "^5.0.45", "antd": "^5.27.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -29,6 +40,7 @@ "fumadocs-mdx": "^11.9.1", "fumadocs-ui": "^15.7.11", "lucide-react": "^0.544.0", + "motion": "^12.23.14", "next": "^15.5.3", "next-intl": "^4.3.8", "react": "^19.1.1", @@ -36,7 +48,9 @@ "rehype-autolink-headings": "^7.1.0", "rehype-slug": "^6.0.0", "remark-gfm": "^4.0.1", - "tailwind-merge": "^3.3.1" + "tailwind-merge": "^3.3.1", + "zod": "^4.1.9", + "zustand": "^5.0.8" }, "devDependencies": { "@tailwindcss/postcss": "^4.1.13", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75c5e84..9fe9b16 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,24 @@ settings: importers: .: dependencies: + "@ai-sdk/google": + specifier: ^2.0.14 + version: 2.0.14(zod@4.1.9) + "@ai-sdk/openai": + specifier: ^2.0.32 + version: 2.0.32(zod@4.1.9) + "@ai-sdk/react": + specifier: ^2.0.45 + version: 2.0.45(react@19.1.1)(zod@4.1.9) + "@assistant-ui/react": + specifier: ^0.11.10 + version: 0.11.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)) + "@assistant-ui/react-ai-sdk": + specifier: ^1.1.0 + version: 1.1.0(@assistant-ui/react@0.11.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)))(@types/react@19.1.12)(assistant-cloud@0.1.1)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)) + "@assistant-ui/react-markdown": + specifier: ^0.11.0 + version: 0.11.0(@assistant-ui/react@0.11.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)))(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) "@giscus/react": specifier: ^3.1.0 version: 3.1.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -16,18 +34,33 @@ importers: "@orama/tokenizers": specifier: ^3.1.13 version: 3.1.13 + "@radix-ui/react-avatar": + specifier: ^1.1.10 + version: 1.1.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) "@radix-ui/react-dialog": specifier: ^1.1.15 version: 1.1.15(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-label": + specifier: ^2.1.7 + version: 2.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-radio-group": + specifier: ^1.3.8 + version: 1.3.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) "@radix-ui/react-slot": specifier: ^1.2.3 version: 1.2.3(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-tooltip": + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) "@types/mdx": specifier: ^2.0.13 version: 2.0.13 "@vercel/speed-insights": specifier: ^1.2.0 version: 1.2.0(next@15.5.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) + ai: + specifier: ^5.0.45 + version: 5.0.45(zod@4.1.9) antd: specifier: ^5.27.3 version: 5.27.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -52,6 +85,9 @@ importers: lucide-react: specifier: ^0.544.0 version: 0.544.0(react@19.1.1) + motion: + specifier: ^12.23.14 + version: 12.23.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1) next: specifier: ^15.5.3 version: 15.5.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -76,6 +112,12 @@ importers: tailwind-merge: specifier: ^3.3.1 version: 3.3.1 + zod: + specifier: ^4.1.9 + version: 4.1.9 + zustand: + specifier: ^5.0.8 + version: 5.0.8(@types/react@19.1.12)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)) devDependencies: "@tailwindcss/postcss": specifier: ^4.1.13 @@ -121,6 +163,62 @@ importers: version: 5.9.2 packages: + "@ai-sdk/gateway@1.0.23": + resolution: + { + integrity: sha512-ynV7WxpRK2zWLGkdOtrU2hW22mBVkEYVS3iMg1+ZGmAYSgzCqzC74bfOJZ2GU1UdcrFWUsFI9qAYjsPkd+AebA==, + } + engines: { node: ">=18" } + peerDependencies: + zod: ^3.25.76 || ^4 + + "@ai-sdk/google@2.0.14": + resolution: + { + integrity: sha512-OCBBkEUq1RNLkbJuD+ejqGsWDD0M5nRyuFWDchwylxy0J4HSsAiGNhutNYVTdnqmNw+r9LyZlkyZ1P4YfAfLdg==, + } + engines: { node: ">=18" } + peerDependencies: + zod: ^3.25.76 || ^4 + + "@ai-sdk/openai@2.0.32": + resolution: + { + integrity: sha512-p7giSkCs66Q1qYO/NPYI41CrSg65mcm8R2uAdF86+Y1D1/q4mUrWMyf5UTOJ0bx/z4jIPiNgGDCg2Kabi5zrKQ==, + } + engines: { node: ">=18" } + peerDependencies: + zod: ^3.25.76 || ^4 + + "@ai-sdk/provider-utils@3.0.9": + resolution: + { + integrity: sha512-Pm571x5efqaI4hf9yW4KsVlDBDme8++UepZRnq+kqVBWWjgvGhQlzU8glaFq0YJEB9kkxZHbRRyVeHoV2sRYaQ==, + } + engines: { node: ">=18" } + peerDependencies: + zod: ^3.25.76 || ^4 + + "@ai-sdk/provider@2.0.0": + resolution: + { + integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==, + } + engines: { node: ">=18" } + + "@ai-sdk/react@2.0.45": + resolution: + { + integrity: sha512-jrTeBQpIsueV6EB/L6KNdH/yadK/Ehx1qCus+9RC29kRikVhjgj8xNvHfH3qHCwsfGqLX9ljj69dCRLrmzpvnw==, + } + engines: { node: ">=18" } + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.25.76 || ^4 + peerDependenciesMeta: + zod: + optional: true + "@alloc/quick-lru@5.2.0": resolution: { @@ -183,6 +281,62 @@ packages: peerDependencies: react: ">=16.9.0" + "@assistant-ui/react-ai-sdk@1.1.0": + resolution: + { + integrity: sha512-1eGXHH8HBeBX0vu0nvjNamrHkqovee8MirSgndprbijFsL0dbb7c1OA2yg76lnja3vmmPLN/GUiqEY9NrPac1g==, + } + peerDependencies: + "@assistant-ui/react": ^0.11.0 + "@types/react": "*" + assistant-cloud: "*" + react: ^18 || ^19 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + assistant-cloud: + optional: true + + "@assistant-ui/react-markdown@0.11.0": + resolution: + { + integrity: sha512-w5zKMKnAvJVyeDvHL1Afy3Pm+Bb5NBYpQ8FQRsYbaZMZIhT3/zCN6lMYID+XaB1nkvhztHucVk9u+G7Uk5DJIw==, + } + peerDependencies: + "@assistant-ui/react": ^0.11.0 + "@types/react": "*" + react: ^18 || ^19 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@assistant-ui/react@0.11.10": + resolution: + { + integrity: sha512-8g8O8e3imLQd1GYSyQWfCvi7Tjh0m5h5rVHyge4js+LW+6y7+UnpSmZ/eyIajOkLmdHiwkkpiNkce2cUC9r/5Q==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@assistant-ui/tap@0.1.1": + resolution: + { + integrity: sha512-CG+u/h9yOzy7OcHwcti+GlnmyD6RoTd1pq7n1Rm3rqqVyFpdsfL25X6hwqir1NKQYflFVOyhR957X6/OFUbyDg==, + } + peerDependencies: + react: "*" + peerDependenciesMeta: + react: + optional: true + "@babel/runtime@7.28.4": resolution: { @@ -1046,6 +1200,22 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-avatar@1.1.10": + resolution: + { + integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + "@radix-ui/react-collapsible@1.1.12": resolution: { @@ -1186,6 +1356,22 @@ packages: "@types/react": optional: true + "@radix-ui/react-label@2.1.7": + resolution: + { + integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + "@radix-ui/react-navigation-menu@1.2.14": resolution: { @@ -1282,6 +1468,22 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-radio-group@1.3.8": + resolution: + { + integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + "@radix-ui/react-roving-focus@1.1.11": resolution: { @@ -1342,6 +1544,22 @@ packages: "@types/react-dom": optional: true + "@radix-ui/react-tooltip@1.2.8": + resolution: + { + integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + "@radix-ui/react-use-callback-ref@1.1.1": resolution: { @@ -1390,6 +1608,18 @@ packages: "@types/react": optional: true + "@radix-ui/react-use-is-hydrated@0.1.0": + resolution: + { + integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@radix-ui/react-use-layout-effect@1.1.1": resolution: { @@ -2146,6 +2376,15 @@ packages: engines: { node: ">=0.4.0" } hasBin: true + ai@5.0.45: + resolution: + { + integrity: sha512-go6J78B1oTXZMN2XLlNJnrFxwcqXQtpPqUVyk1wvzvpb2dk5nP9yNuxqqOX9HrrKuf5U9M6rSezEJWr1eEG9RA==, + } + engines: { node: ">=18" } + peerDependencies: + zod: ^3.25.76 || ^4 + ajv@6.12.6: resolution: { @@ -2265,6 +2504,18 @@ packages: } engines: { node: ">= 0.4" } + assistant-cloud@0.1.1: + resolution: + { + integrity: sha512-lTlNjBQGICdx08SgmKBcyuQkay6vBEhoasSQenz2ecvyQ25O0527H75v5OG+QMkNKthru3p5zOiOti90fJ0LCw==, + } + + assistant-stream@0.2.26: + resolution: + { + integrity: sha512-mTfTkaf9PIFE1x7/5PVAue4F/7DOmxZNPv9yVDGy5UMjLKZeIQu0nsmNUjw5BsbgXQJL0Gdb9plucSr40T3Xwg==, + } + ast-types-flow@0.0.8: resolution: { @@ -3063,6 +3314,13 @@ packages: integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==, } + eventsource-parser@3.0.6: + resolution: + { + integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==, + } + engines: { node: ">=18.0.0" } + extend@3.0.2: resolution: { @@ -3166,6 +3424,23 @@ packages: integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==, } + framer-motion@12.23.14: + resolution: + { + integrity: sha512-8BQ6dvqOht2w8P1CwIEvAA0gypDR3fNG/M6/f5lT0QgNIKnJf7J43Bpv++NnCWU8YfmL47UEm2hbI0GRvdVhsQ==, + } + peerDependencies: + "@emotion/is-prop-valid": "*" + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/is-prop-valid": + optional: true + react: + optional: true + react-dom: + optional: true + fumadocs-core@15.7.11: resolution: { @@ -3454,6 +3729,12 @@ packages: integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==, } + html-url-attributes@3.0.1: + resolution: + { + integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==, + } + html-void-elements@3.0.0: resolution: { @@ -3806,6 +4087,12 @@ packages: integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, } + json-schema@0.4.0: + resolution: + { + integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==, + } + json-stable-stringify-without-jsonify@1.0.1: resolution: { @@ -4437,6 +4724,35 @@ packages: engines: { node: ">=10" } hasBin: true + motion-dom@12.23.12: + resolution: + { + integrity: sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==, + } + + motion-utils@12.23.6: + resolution: + { + integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==, + } + + motion@12.23.14: + resolution: + { + integrity: sha512-kcJde+A4AeUD2ujAhpvhCOjzt6NtXjqL9m0LsLdyPO5SPVQFsCpxVyLsqtS1o9Z+CEJ7U8kSIhsRSJF1oDZXfg==, + } + peerDependencies: + "@emotion/is-prop-valid": "*" + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/is-prop-valid": + optional: true + react: + optional: true + react-dom: + optional: true + ms@2.1.3: resolution: { @@ -4458,6 +4774,14 @@ packages: engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } hasBin: true + nanoid@5.1.5: + resolution: + { + integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==, + } + engines: { node: ^18 || >=20 } + hasBin: true + napi-postinstall@0.3.3: resolution: { @@ -5133,6 +5457,15 @@ packages: integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==, } + react-markdown@10.1.0: + resolution: + { + integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==, + } + peerDependencies: + "@types/react": ">=18" + react: ">=18" + react-medium-image-zoom@5.3.0: resolution: { @@ -5181,6 +5514,15 @@ packages: "@types/react": optional: true + react-textarea-autosize@8.5.9: + resolution: + { + integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==, + } + engines: { node: ">=10" } + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react@19.1.1: resolution: { @@ -5400,6 +5742,12 @@ packages: integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==, } + secure-json-parse@4.0.0: + resolution: + { + integrity: sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==, + } + semver@6.3.1: resolution: { @@ -5687,6 +6035,14 @@ packages: } engines: { node: ">= 0.4" } + swr@2.3.6: + resolution: + { + integrity: sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==, + } + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + tailwind-merge@3.3.1: resolution: { @@ -5720,6 +6076,13 @@ packages: } engines: { node: ">=12.22" } + throttleit@2.1.0: + resolution: + { + integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==, + } + engines: { node: ">=18" } + tinyexec@1.0.1: resolution: { @@ -5917,6 +6280,18 @@ packages: "@types/react": optional: true + use-composed-ref@1.4.0: + resolution: + { + integrity: sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + use-intl@4.3.8: resolution: { @@ -5925,6 +6300,30 @@ packages: peerDependencies: react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 + use-isomorphic-layout-effect@1.2.1: + resolution: + { + integrity: sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + + use-latest@1.3.0: + resolution: + { + integrity: sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + use-sidecar@1.1.3: resolution: { @@ -5938,6 +6337,14 @@ packages: "@types/react": optional: true + use-sync-external-store@1.5.0: + resolution: + { + integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==, + } + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: { @@ -6028,11 +6435,32 @@ packages: } engines: { node: ">=10" } - zod@4.1.8: + zod@4.1.9: + resolution: + { + integrity: sha512-HI32jTq0AUAC125z30E8bQNz0RQ+9Uc+4J7V97gLYjZVKRjeydPgGt6dvQzFrav7MYOUGFqqOGiHpA/fdbd0cQ==, + } + + zustand@5.0.8: resolution: { - integrity: sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==, + integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==, } + engines: { node: ">=12.20.0" } + peerDependencies: + "@types/react": ">=18.0.0" + immer: ">=9.0.6" + react: ">=18.0.0" + use-sync-external-store: ">=1.2.0" + peerDependenciesMeta: + "@types/react": + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true zwitch@2.0.4: resolution: @@ -6041,6 +6469,45 @@ packages: } snapshots: + "@ai-sdk/gateway@1.0.23(zod@4.1.9)": + dependencies: + "@ai-sdk/provider": 2.0.0 + "@ai-sdk/provider-utils": 3.0.9(zod@4.1.9) + zod: 4.1.9 + + "@ai-sdk/google@2.0.14(zod@4.1.9)": + dependencies: + "@ai-sdk/provider": 2.0.0 + "@ai-sdk/provider-utils": 3.0.9(zod@4.1.9) + zod: 4.1.9 + + "@ai-sdk/openai@2.0.32(zod@4.1.9)": + dependencies: + "@ai-sdk/provider": 2.0.0 + "@ai-sdk/provider-utils": 3.0.9(zod@4.1.9) + zod: 4.1.9 + + "@ai-sdk/provider-utils@3.0.9(zod@4.1.9)": + dependencies: + "@ai-sdk/provider": 2.0.0 + "@standard-schema/spec": 1.0.0 + eventsource-parser: 3.0.6 + zod: 4.1.9 + + "@ai-sdk/provider@2.0.0": + dependencies: + json-schema: 0.4.0 + + "@ai-sdk/react@2.0.45(react@19.1.1)(zod@4.1.9)": + dependencies: + "@ai-sdk/provider-utils": 3.0.9(zod@4.1.9) + ai: 5.0.45(zod@4.1.9) + react: 19.1.1 + swr: 2.3.6(react@19.1.1) + throttleit: 2.1.0 + optionalDependencies: + zod: 4.1.9 + "@alloc/quick-lru@5.2.0": {} "@ant-design/colors@7.2.1": @@ -6092,6 +6559,74 @@ snapshots: resize-observer-polyfill: 1.5.1 throttle-debounce: 5.0.2 + "@assistant-ui/react-ai-sdk@1.1.0(@assistant-ui/react@0.11.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)))(@types/react@19.1.12)(assistant-cloud@0.1.1)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1))": + dependencies: + "@ai-sdk/provider": 2.0.0 + "@ai-sdk/react": 2.0.45(react@19.1.1)(zod@4.1.9) + "@assistant-ui/react": 0.11.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)) + "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.12)(react@19.1.1) + "@types/json-schema": 7.0.15 + ai: 5.0.45(zod@4.1.9) + assistant-stream: 0.2.26 + json-schema: 0.4.0 + react: 19.1.1 + zod: 4.1.9 + zustand: 5.0.8(@types/react@19.1.12)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)) + optionalDependencies: + "@types/react": 19.1.12 + assistant-cloud: 0.1.1 + transitivePeerDependencies: + - immer + - use-sync-external-store + + "@assistant-ui/react-markdown@0.11.0(@assistant-ui/react@0.11.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)))(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@assistant-ui/react": 0.11.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.12)(react@19.1.1) + "@types/hast": 3.0.4 + classnames: 2.5.1 + react: 19.1.1 + react-markdown: 10.1.0(@types/react@19.1.12)(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.12 + transitivePeerDependencies: + - "@types/react-dom" + - react-dom + - supports-color + + "@assistant-ui/react@0.11.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1))": + dependencies: + "@assistant-ui/tap": 0.1.1(react@19.1.1) + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-context": 1.1.2(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-popover": 1.1.15(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-slot": 1.2.3(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-use-escape-keydown": 1.1.1(@types/react@19.1.12)(react@19.1.1) + "@standard-schema/spec": 1.0.0 + assistant-cloud: 0.1.1 + assistant-stream: 0.2.26 + json-schema: 0.4.0 + nanoid: 5.1.5 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-textarea-autosize: 8.5.9(@types/react@19.1.12)(react@19.1.1) + zod: 4.1.9 + zustand: 5.0.8(@types/react@19.1.12)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)) + optionalDependencies: + "@types/react": 19.1.12 + "@types/react-dom": 19.1.9(@types/react@19.1.12) + transitivePeerDependencies: + - immer + - use-sync-external-store + + "@assistant-ui/tap@0.1.1(react@19.1.1)": + optionalDependencies: + react: 19.1.1 + "@babel/runtime@7.28.4": {} "@emnapi/core@1.5.0": @@ -6496,8 +7031,7 @@ snapshots: "@nolyfill/is-core-module@1.0.39": {} - "@opentelemetry/api@1.9.0": - optional: true + "@opentelemetry/api@1.9.0": {} "@orama/orama@3.1.13": {} @@ -6535,6 +7069,19 @@ snapshots: "@types/react": 19.1.12 "@types/react-dom": 19.1.9(@types/react@19.1.12) + "@radix-ui/react-avatar@1.1.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/react-context": 1.1.2(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-use-is-hydrated": 0.1.0(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.12 + "@types/react-dom": 19.1.9(@types/react@19.1.12) + "@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": dependencies: "@radix-ui/primitive": 1.1.3 @@ -6640,6 +7187,15 @@ snapshots: optionalDependencies: "@types/react": 19.1.12 + "@radix-ui/react-label@2.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.12 + "@types/react-dom": 19.1.9(@types/react@19.1.12) + "@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": dependencies: "@radix-ui/primitive": 1.1.3 @@ -6732,6 +7288,24 @@ snapshots: "@types/react": 19.1.12 "@types/react-dom": 19.1.9(@types/react@19.1.12) + "@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-context": 1.1.2(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-direction": 1.1.1(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-presence": 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-roving-focus": 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-use-previous": 1.1.1(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-use-size": 1.1.1(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.12 + "@types/react-dom": 19.1.9(@types/react@19.1.12) + "@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": dependencies: "@radix-ui/primitive": 1.1.3 @@ -6789,6 +7363,26 @@ snapshots: "@types/react": 19.1.12 "@types/react-dom": 19.1.9(@types/react@19.1.12) + "@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)": + dependencies: + "@radix-ui/primitive": 1.1.3 + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-context": 1.1.2(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-dismissable-layer": 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-id": 1.1.1(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-popper": 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-portal": 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-presence": 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + "@radix-ui/react-slot": 1.2.3(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.12)(react@19.1.1) + "@radix-ui/react-visually-hidden": 1.2.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.12 + "@types/react-dom": 19.1.9(@types/react@19.1.12) + "@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.12)(react@19.1.1)": dependencies: react: 19.1.1 @@ -6817,6 +7411,13 @@ snapshots: optionalDependencies: "@types/react": 19.1.12 + "@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.1.12)(react@19.1.1)": + dependencies: + react: 19.1.1 + use-sync-external-store: 1.5.0(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.12 + "@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.12)(react@19.1.1)": dependencies: react: 19.1.1 @@ -7268,6 +7869,14 @@ snapshots: acorn@8.15.0: {} + ai@5.0.45(zod@4.1.9): + dependencies: + "@ai-sdk/gateway": 1.0.23(zod@4.1.9) + "@ai-sdk/provider": 2.0.0 + "@ai-sdk/provider-utils": 3.0.9(zod@4.1.9) + "@opentelemetry/api": 1.9.0 + zod: 4.1.9 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -7420,6 +8029,16 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assistant-cloud@0.1.1: + dependencies: + assistant-stream: 0.2.26 + + assistant-stream@0.2.26: + dependencies: + "@types/json-schema": 7.0.15 + nanoid: 5.1.5 + secure-json-parse: 4.0.0 + ast-types-flow@0.0.8: {} astring@1.9.0: {} @@ -8056,6 +8675,8 @@ snapshots: eventemitter3@5.0.1: {} + eventsource-parser@3.0.6: {} + extend@3.0.2: {} fast-deep-equal@3.1.3: {} @@ -8114,6 +8735,15 @@ snapshots: fraction.js@4.3.7: {} + framer-motion@12.23.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + motion-dom: 12.23.12 + motion-utils: 12.23.6 + tslib: 2.8.1 + optionalDependencies: + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + fumadocs-core@15.7.11(@types/react@19.1.12)(next@15.5.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: "@formatjs/intl-localematcher": 0.6.1 @@ -8158,7 +8788,7 @@ snapshots: tinyglobby: 0.2.15 unified: 11.0.5 unist-util-visit: 5.0.0 - zod: 4.1.8 + zod: 4.1.9 optionalDependencies: next: 15.5.3(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 @@ -8366,6 +8996,8 @@ snapshots: dependencies: "@types/hast": 3.0.4 + html-url-attributes@3.0.1: {} + html-void-elements@3.0.0: {} husky@9.1.7: {} @@ -8556,6 +9188,8 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json2mq@0.2.0: @@ -9165,12 +9799,28 @@ snapshots: mkdirp@3.0.1: {} + motion-dom@12.23.12: + dependencies: + motion-utils: 12.23.6 + + motion-utils@12.23.6: {} + + motion@12.23.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + framer-motion: 12.23.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + tslib: 2.8.1 + optionalDependencies: + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + ms@2.1.3: {} nano-spawn@1.0.3: {} nanoid@3.3.11: {} + nanoid@5.1.5: {} + napi-postinstall@0.3.3: {} natural-compare@1.4.0: {} @@ -9692,6 +10342,24 @@ snapshots: react-is@18.3.1: {} + react-markdown@10.1.0(@types/react@19.1.12)(react@19.1.1): + dependencies: + "@types/hast": 3.0.4 + "@types/mdast": 4.0.4 + "@types/react": 19.1.12 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.0 + react: 19.1.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + react-medium-image-zoom@5.3.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: react: 19.1.1 @@ -9724,6 +10392,15 @@ snapshots: optionalDependencies: "@types/react": 19.1.12 + react-textarea-autosize@8.5.9(@types/react@19.1.12)(react@19.1.1): + dependencies: + "@babel/runtime": 7.28.4 + react: 19.1.1 + use-composed-ref: 1.4.0(@types/react@19.1.12)(react@19.1.1) + use-latest: 1.3.0(@types/react@19.1.12)(react@19.1.1) + transitivePeerDependencies: + - "@types/react" + react@19.1.1: {} readdirp@4.1.2: {} @@ -9918,6 +10595,8 @@ snapshots: dependencies: compute-scroll-into-view: 3.1.1 + secure-json-parse@4.0.0: {} + semver@6.3.1: {} semver@7.7.2: {} @@ -10143,6 +10822,12 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + swr@2.3.6(react@19.1.1): + dependencies: + dequal: 2.0.3 + react: 19.1.1 + use-sync-external-store: 1.5.0(react@19.1.1) + tailwind-merge@3.3.1: {} tailwindcss@4.1.13: {} @@ -10160,6 +10845,8 @@ snapshots: throttle-debounce@5.0.2: {} + throttleit@2.1.0: {} + tinyexec@1.0.1: {} tinyglobby@0.2.15: @@ -10318,6 +11005,12 @@ snapshots: optionalDependencies: "@types/react": 19.1.12 + use-composed-ref@1.4.0(@types/react@19.1.12)(react@19.1.1): + dependencies: + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.12 + use-intl@4.3.8(react@19.1.1): dependencies: "@formatjs/fast-memoize": 2.2.7 @@ -10325,6 +11018,19 @@ snapshots: intl-messageformat: 10.7.16 react: 19.1.1 + use-isomorphic-layout-effect@1.2.1(@types/react@19.1.12)(react@19.1.1): + dependencies: + react: 19.1.1 + optionalDependencies: + "@types/react": 19.1.12 + + use-latest@1.3.0(@types/react@19.1.12)(react@19.1.1): + dependencies: + react: 19.1.1 + use-isomorphic-layout-effect: 1.2.1(@types/react@19.1.12)(react@19.1.1) + optionalDependencies: + "@types/react": 19.1.12 + use-sidecar@1.1.3(@types/react@19.1.12)(react@19.1.1): dependencies: detect-node-es: 1.1.0 @@ -10333,6 +11039,10 @@ snapshots: optionalDependencies: "@types/react": 19.1.12 + use-sync-external-store@1.5.0(react@19.1.1): + dependencies: + react: 19.1.1 + util-deprecate@1.0.2: {} vfile-message@4.0.3: @@ -10404,6 +11114,12 @@ snapshots: yocto-queue@0.1.0: {} - zod@4.1.8: {} + zod@4.1.9: {} + + zustand@5.0.8(@types/react@19.1.12)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)): + optionalDependencies: + "@types/react": 19.1.12 + react: 19.1.1 + use-sync-external-store: 1.5.0(react@19.1.1) zwitch@2.0.4: {}
      + ), + td: ({ className, ...props }) => ( + + ), + tr: ({ className, ...props }) => ( +