diff --git a/.gitignore b/.gitignore index df8a7f1d1..673bd6c04 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,7 @@ out/ # production build dist -apps/www/public/registry/ +apps/docs/public/registry/ # turbo .turbo diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore deleted file mode 100644 index 51c0e6b51..000000000 --- a/apps/docs/.gitignore +++ /dev/null @@ -1,28 +0,0 @@ -# deps -/node_modules - -# generated content -.map.ts -.contentlayer -.content-collections - -# test & build -/coverage -/.next/ -/out/ -/build -*.tsbuildinfo - -# misc -.DS_Store -*.pem -/.pnp -.pnp.js -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# others -.env*.local -.vercel -next-env.d.ts \ No newline at end of file diff --git a/apps/docs/.map.ts b/apps/docs/.map.ts new file mode 100644 index 000000000..4da1afa55 --- /dev/null +++ b/apps/docs/.map.ts @@ -0,0 +1,4 @@ +/** Auto-generated **/ +declare const map: Record + +export { map } \ No newline at end of file diff --git a/apps/docs/app/(home)/examples/page.tsx b/apps/docs/app/(home)/examples/page.tsx new file mode 100644 index 000000000..4d6107a51 --- /dev/null +++ b/apps/docs/app/(home)/examples/page.tsx @@ -0,0 +1,89 @@ +const Examples = () => { + return ( +
+

Examples

+ +

RSC Example

+

This is an example of how to use the Vercel AI SDK RSC Runtime.

+ + +

eCommerce Example

+

+ This is an example of an eCommerce chatbot that can help users find + products. Built with Vercel AI SDK RSC Runtime. +

+ + +

Inline Suggestions Example

+

This is an example of how to use inline suggestions.

+ + +

React Hook Form Example

+

This is an example of how to use @assistant-ui/react-hook-form.

+ + +

Custom REST API Example

+

+ This is an example of how to use @assistant-ui/react with a custom REST + API. +

+ + +

Thread History Persistence example

+

+ This is an example of how to restore the thread history using + @assistant-ui/react-ai-sdk and Vercel AI SDK. +

+ +
+ ); +}; + +export default Examples; diff --git a/apps/docs/app/(home)/layout.tsx b/apps/docs/app/(home)/layout.tsx new file mode 100644 index 000000000..f7bfbf830 --- /dev/null +++ b/apps/docs/app/(home)/layout.tsx @@ -0,0 +1,11 @@ +import { ReactNode } from "react"; +import { Layout } from "fumadocs-ui/layout"; +import { baseOptions } from "../docs/layout.config"; + +export default function HomeLayout({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + return {children}; +} diff --git a/apps/docs/app/(home)/page.tsx b/apps/docs/app/(home)/page.tsx new file mode 100644 index 000000000..3953e986d --- /dev/null +++ b/apps/docs/app/(home)/page.tsx @@ -0,0 +1,5 @@ +import Home from "@/components/Home"; + +export default function HomePage() { + return ; +} diff --git a/apps/docs/app/api/chat/route.ts b/apps/docs/app/api/chat/route.ts new file mode 100644 index 000000000..273b2b1c5 --- /dev/null +++ b/apps/docs/app/api/chat/route.ts @@ -0,0 +1,24 @@ +import { createOpenAI } from "@ai-sdk/openai"; +import { streamText } from "ai"; + +const openai = createOpenAI({ + baseURL: process.env["OPENAI_BASE_URL"] as string, +}); + +export const runtime = "edge"; + +export async function POST(req: Request) { + const { messages } = await req.json(); + const result = await streamText({ + model: openai("gpt-3.5-turbo"), + messages: [ + { + role: "system", + content: "You are a helpful assistant.", + }, + ...messages, + ], + }); + + return result.toAIStreamResponse(); +} diff --git a/apps/docs/app/api/search/route.ts b/apps/docs/app/api/search/route.ts index 74983f973..b68090053 100644 --- a/apps/docs/app/api/search/route.ts +++ b/apps/docs/app/api/search/route.ts @@ -1,10 +1,10 @@ -import { getPages } from '@/app/source'; -import { createSearchAPI } from 'fumadocs-core/search/server'; +import { getPages } from "@/app/source"; +import { createSearchAPI } from "fumadocs-core/search/server"; -export const { GET } = createSearchAPI('advanced', { +export const { GET } = createSearchAPI("advanced", { indexes: getPages().map((page) => ({ - title: page.data.title, - structuredData: page.data.exports.structuredData, + title: (page.data as any).title, + structuredData: (page.data as any).exports.structuredData, id: page.url, url: page.url, })), diff --git a/apps/docs/app/docs/[[...slug]]/page.tsx b/apps/docs/app/docs/[[...slug]]/page.tsx index 07ea900f8..ecf42855e 100644 --- a/apps/docs/app/docs/[[...slug]]/page.tsx +++ b/apps/docs/app/docs/[[...slug]]/page.tsx @@ -1,25 +1,52 @@ -import { getPage, getPages } from '@/app/source'; -import type { Metadata } from 'next'; -import { DocsPage, DocsBody } from 'fumadocs-ui/page'; -import { notFound } from 'next/navigation'; +import { getPages, getPage } from "@/app/source"; +import type { Metadata } from "next"; +import { DocsPage, DocsBody } from "fumadocs-ui/page"; +import { notFound } from "next/navigation"; +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; +import { EditIcon } from "lucide-react"; export default async function Page({ params, }: { params: { slug?: string[] }; }) { - const page = getPage(params.slug); + const page = getPage(["docs", ...(params.slug ?? [])]); if (page == null) { notFound(); } - const MDX = page.data.exports.default; + const path = `apps/docs/content/docs/${page.file.path}`; + + const footer = ( + + + Edit on Github + + ); + + const MDX = (page.data as any).exports.default; return ( - + -

{page.data.title}

+

{(page.data as any).title}

@@ -27,18 +54,20 @@ export default async function Page({ } export async function generateStaticParams() { - return getPages().map((page) => ({ - slug: page.slugs, - })); + return getPages() + .filter((page) => page.slugs[0] === "docs") + .map((page) => ({ + slug: page.slugs.slice(1), + })); } export function generateMetadata({ params }: { params: { slug?: string[] } }) { - const page = getPage(params.slug); + const page = getPage(["docs", ...(params.slug ?? [])]); if (page == null) notFound(); return { - title: page.data.title, - description: page.data.description, + title: (page.data as any).title, + description: (page.data as any).description, } satisfies Metadata; } diff --git a/apps/docs/app/docs/layout.config.tsx b/apps/docs/app/docs/layout.config.tsx new file mode 100644 index 000000000..1af1f111e --- /dev/null +++ b/apps/docs/app/docs/layout.config.tsx @@ -0,0 +1,102 @@ +import { type BaseLayoutProps, type DocsLayoutProps } from "fumadocs-ui/layout"; +import { pageTree } from "@/app/source"; +import { RootToggle } from "fumadocs-ui/components/layout/root-toggle"; +import { BookIcon, LayoutTemplateIcon, UserIcon } from "lucide-react"; +import { LibraryIcon, type LucideIcon } from "lucide-react"; +import icon from "@/public/favicon/favicon.svg"; +import Image from "next/image"; + +type Mode = { + url: string; + title: string; + description: string; + icon: LucideIcon; + param: string; +}; + +export const modes: Mode[] = [ + { + icon: LibraryIcon, + title: "Documentation", + description: "@assistant-ui/react", + url: "/docs", + param: "docs", + }, + { + icon: LibraryIcon, + title: "Reference", + description: "@assistant-ui/react", + url: "/reference", + param: "reference", + }, +]; + +// shared configuration +export const baseOptions: BaseLayoutProps = { + githubUrl: "https://github.com/Yonom/assistant-ui", + nav: { + title: ( + <> + logo + assistant-ui + + ), + transparentMode: "none", + }, + links: [ + { + text: "Documentation", + url: "/docs", + icon: , + active: "nested-url", + }, + { + text: "Reference", + url: "/reference", + icon: , + active: "nested-url", + }, + { + text: "Examples", + url: "/examples", + icon: , + }, + { + text: "Discord", + url: "https://discord.gg/S9dwgCNEFs", + icon: , + external: true, + }, + ], +}; + +export const sharedDocsOptions: Partial = { + ...baseOptions, + sidebar: { + defaultOpenLevel: 0, + banner: ( + ({ + url: mode.url, + icon: ( + + ), + title: mode.title, + description: mode.description, + }))} + /> + ), + }, +}; + +// docs layout configuration +export const docsOptions: DocsLayoutProps = { + ...sharedDocsOptions, + tree: pageTree, +}; diff --git a/apps/docs/app/docs/layout.tsx b/apps/docs/app/docs/layout.tsx index ae2cc9e99..5d0998beb 100644 --- a/apps/docs/app/docs/layout.tsx +++ b/apps/docs/app/docs/layout.tsx @@ -1,6 +1,6 @@ -import { DocsLayout } from 'fumadocs-ui/layout'; -import type { ReactNode } from 'react'; -import { docsOptions } from '../layout.config'; +import { DocsLayout } from "fumadocs-ui/layout"; +import type { ReactNode } from "react"; +import { docsOptions } from "./layout.config"; export default function Layout({ children }: { children: ReactNode }) { return {children}; diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index b5c61c956..fdd04564e 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -1,3 +1,94 @@ @tailwind base; @tailwind components; @tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 240 10% 3.9%; + + --radius: 0.5rem; + } + + .dark { + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 240 4.9% 83.9%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} + +.mode-docs { + --primary: var(--docs-color); +} + +.mode-reference { + --primary: var(--reference-color); +} + +:root { + --docs-color: 220deg 91% 54%; + --reference-color: 250 80% 54%; +} + +.dark { + --docs-color: 217deg 92% 76%; + --reference-color: 250 100% 80%; +} diff --git a/apps/docs/app/layout.client.tsx b/apps/docs/app/layout.client.tsx new file mode 100644 index 000000000..a8a7ffe4a --- /dev/null +++ b/apps/docs/app/layout.client.tsx @@ -0,0 +1,24 @@ +"use client"; + +import { usePathname } from "next/navigation"; +import { type ReactNode } from "react"; +import { cn } from "@/lib/utils"; + +function useMode(): string | undefined { + const name = usePathname(); + return name.split("/")[1]; +} + +export function Body({ + children, +}: { + children: ReactNode; +}): React.ReactElement { + const mode = useMode(); + + return ( + + {children} + + ); +} diff --git a/apps/docs/app/layout.config.tsx b/apps/docs/app/layout.config.tsx deleted file mode 100644 index 659e35aa9..000000000 --- a/apps/docs/app/layout.config.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { type BaseLayoutProps, type DocsLayoutProps } from 'fumadocs-ui/layout'; -import { pageTree } from '@/app/source'; - -// shared configuration -export const baseOptions: BaseLayoutProps = { - nav: { - title: 'My App', - }, - links: [ - { - text: 'Documentation', - url: '/docs', - active: 'nested-url', - }, - ], -}; - -// docs layout configuration -export const docsOptions: DocsLayoutProps = { - ...baseOptions, - tree: pageTree, -}; diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx index a9c12cac8..bc23ad2dc 100644 --- a/apps/docs/app/layout.tsx +++ b/apps/docs/app/layout.tsx @@ -3,13 +3,18 @@ import { RootProvider } from "fumadocs-ui/provider"; import type { ReactNode } from "react"; import { GeistSans } from "geist/font/sans"; import { GeistMono } from "geist/font/mono"; +import { Body } from "./layout.client"; export default function Layout({ children }: { children: ReactNode }) { return ( - - + + {children} - + ); } diff --git a/apps/docs/app/page.tsx b/apps/docs/app/page.tsx deleted file mode 100644 index fa093f695..000000000 --- a/apps/docs/app/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import Link from 'next/link'; - -export default function HomePage() { - return ( -
-

Hello World

-

- You can open{' '} - - /docs - {' '} - and see the documentation. -

-
- ); -} diff --git a/apps/docs/app/reference/[...slug]/page.tsx b/apps/docs/app/reference/[...slug]/page.tsx new file mode 100644 index 000000000..b45e1603a --- /dev/null +++ b/apps/docs/app/reference/[...slug]/page.tsx @@ -0,0 +1,46 @@ +import { getPages, getPage } from "@/app/source"; +import type { Metadata } from "next"; +import { DocsPage, DocsBody } from "fumadocs-ui/page"; +import { notFound } from "next/navigation"; + +export default async function Page({ + params, +}: { + params: { slug?: string[] }; +}) { + const page = getPage(["reference", ...(params.slug ?? [])]); + + if (page == null) { + notFound(); + } + + const MDX = (page.data as any).exports.default; + + return ( + + +

{(page.data as any).title}

+ +
+
+ ); +} + +export async function generateStaticParams() { + return getPages() + .filter((page) => page.slugs[0] === "reference") + .map((page) => ({ + slug: page.slugs.slice(1), + })); +} + +export function generateMetadata({ params }: { params: { slug?: string[] } }) { + const page = getPage(["reference", ...(params.slug ?? [])]); + + if (page == null) notFound(); + + return { + title: (page.data as any).title, + description: (page.data as any).description, + } satisfies Metadata; +} diff --git a/apps/docs/app/reference/layout.config.tsx b/apps/docs/app/reference/layout.config.tsx new file mode 100644 index 000000000..dadc493f2 --- /dev/null +++ b/apps/docs/app/reference/layout.config.tsx @@ -0,0 +1,9 @@ +import { type DocsLayoutProps } from "fumadocs-ui/layout"; +import { pageTree } from "@/app/source"; +import { sharedDocsOptions } from "../docs/layout.config"; + +// docs layout configuration +export const docsOptions: DocsLayoutProps = { + ...sharedDocsOptions, + tree: pageTree, +}; diff --git a/apps/docs/app/reference/layout.tsx b/apps/docs/app/reference/layout.tsx new file mode 100644 index 000000000..5d0998beb --- /dev/null +++ b/apps/docs/app/reference/layout.tsx @@ -0,0 +1,7 @@ +import { DocsLayout } from "fumadocs-ui/layout"; +import type { ReactNode } from "react"; +import { docsOptions } from "./layout.config"; + +export default function Layout({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/apps/docs/app/reference/page.tsx b/apps/docs/app/reference/page.tsx new file mode 100644 index 000000000..b61832207 --- /dev/null +++ b/apps/docs/app/reference/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function Page() { + return redirect("/reference/runtime"); +} diff --git a/apps/docs/app/source.ts b/apps/docs/app/source.ts index 5cb887be3..734dfc6b0 100644 --- a/apps/docs/app/source.ts +++ b/apps/docs/app/source.ts @@ -1,9 +1,21 @@ -import { map } from '@/.map'; -import { createMDXSource } from 'fumadocs-mdx'; -import { loader } from 'fumadocs-core/source'; +import { map } from "@/.map"; +import { createMDXSource } from "fumadocs-mdx"; +import { loader } from "fumadocs-core/source"; +import { z } from "zod"; -export const { getPage, getPages, pageTree } = loader({ - baseUrl: '/docs', - rootDir: 'docs', - source: createMDXSource(map), +const frontmatterSchema = z.object({ + title: z.string(), + description: z.string().optional(), + icon: z.string().optional(), + full: z.boolean().optional(), +}); + +export const { getPages, getPage, pageTree } = loader({ + baseUrl: "/", + rootDir: "docs", + source: createMDXSource(map, { + schema: { + frontmatter: frontmatterSchema, + }, + }), }); diff --git a/apps/docs/assets/docs/architecture.png b/apps/docs/assets/docs/architecture.png new file mode 100644 index 000000000..297745a9c Binary files /dev/null and b/apps/docs/assets/docs/architecture.png differ diff --git a/apps/docs/assets/providers/anthropic.svg b/apps/docs/assets/providers/anthropic.svg new file mode 100644 index 000000000..98c7b0cae --- /dev/null +++ b/apps/docs/assets/providers/anthropic.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/apps/docs/assets/providers/fireworks.svg b/apps/docs/assets/providers/fireworks.svg new file mode 100644 index 000000000..f70c2849d --- /dev/null +++ b/apps/docs/assets/providers/fireworks.svg @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/apps/docs/assets/providers/google.svg b/apps/docs/assets/providers/google.svg new file mode 100644 index 000000000..0fe37f3d8 --- /dev/null +++ b/apps/docs/assets/providers/google.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/apps/docs/assets/providers/huggingface.svg b/apps/docs/assets/providers/huggingface.svg new file mode 100644 index 000000000..d5770e91a --- /dev/null +++ b/apps/docs/assets/providers/huggingface.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/apps/docs/assets/providers/meta.svg b/apps/docs/assets/providers/meta.svg new file mode 100644 index 000000000..6621c0979 --- /dev/null +++ b/apps/docs/assets/providers/meta.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/apps/docs/assets/providers/mistral.svg b/apps/docs/assets/providers/mistral.svg new file mode 100644 index 000000000..6f81a163a --- /dev/null +++ b/apps/docs/assets/providers/mistral.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/docs/assets/providers/openai.svg b/apps/docs/assets/providers/openai.svg new file mode 100644 index 000000000..9ea7020f8 --- /dev/null +++ b/apps/docs/assets/providers/openai.svg @@ -0,0 +1,11 @@ + + + + + OpenAI icon + + + + diff --git a/apps/docs/components.json b/apps/docs/components.json new file mode 100644 index 000000000..ff3d721de --- /dev/null +++ b/apps/docs/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/global.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} \ No newline at end of file diff --git a/apps/docs/components/Home.tsx b/apps/docs/components/Home.tsx new file mode 100644 index 000000000..0417ac633 --- /dev/null +++ b/apps/docs/components/Home.tsx @@ -0,0 +1,99 @@ +"use client"; + +import { Claude } from "@/components/claude/Claude"; +import { Shadcn } from "@/components/shadcn/Shadcn"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { useChat } from "@ai-sdk/react"; +import { AssistantRuntimeProvider } from "@assistant-ui/react"; +import { useVercelUseChatRuntime } from "@assistant-ui/react-ai-sdk"; +import Link from "next/link"; +import { useState } from "react"; +import { ChatGPT } from "./chatgpt/ChatGPT"; +import { GenUI } from "./genui/GenUI"; +import { ModalChat } from "./modal/ModalChat"; + +const supportedModels = [ + { + name: "Standalone", + component: Shadcn, + }, + { + name: "Modal", + component: ModalChat, + }, + { + name: "Tool UI", + component: GenUI, + }, + { + name: "ChatGPT Theme", + component: ChatGPT, + }, + { + name: "Claude Theme", + component: Claude, + }, +]; + +export default function Home() { + const [selectedModel, setSelectedModel] = useState(supportedModels[0]!); + const ChatComponent = selectedModel.component; + + return ( +
+
+

+ React Components for AI Chat +

+

+ Add an AI chatbot to your app in minutes. +

+
+ +
+ +
+
+

Examples:

+
+
+ {supportedModels.map((model) => ( + setSelectedModel(model)} + className="shrink-0 cursor-pointer px-4 py-2" + variant={ + selectedModel.name === model.name ? "default" : "secondary" + } + > + {model.name} + + ))} +
+
+
+ + + +
+
+
+ ); +} + +export type AssistantProps = { + chat: ReturnType; +}; + +const MyRuntimeProvider = ({ children }: { children: React.ReactNode }) => { + const chat = useChat(); + const runtime = useVercelUseChatRuntime(chat); + return ( + + {children} + + ); +}; diff --git a/apps/docs/components/chatgpt/ChatGPT.tsx b/apps/docs/components/chatgpt/ChatGPT.tsx new file mode 100644 index 000000000..1559d5f9b --- /dev/null +++ b/apps/docs/components/chatgpt/ChatGPT.tsx @@ -0,0 +1,207 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { + ActionBarPrimitive, + BranchPickerPrimitive, + ComposerPrimitive, + MessagePrimitive, + ThreadPrimitive, +} from "@assistant-ui/react"; +import * as Avatar from "@radix-ui/react-avatar"; +import { + ArrowUpIcon, + CheckIcon, + ChevronLeftIcon, + ChevronRightIcon, + CopyIcon, + Pencil1Icon, + ReloadIcon, +} from "@radix-ui/react-icons"; +import type { FC } from "react"; +import { Button, type ButtonProps } from "../ui/button"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; + +export const ChatGPT: FC = () => { + return ( + + + +
+ + C + +

How can I help you today?

+
+
+ + +
+ + + + + + + + + + +
+ + + +

+ ChatGPT can make mistakes. Check important info. +

+ + ); +}; + +const UserMessage: FC = () => { + return ( + +
+ + + + + + + + +
+ +
+
+ + +
+ ); +}; + +const EditComposer: FC = () => { + return ( + + + +
+ + Cancel + + + Send + +
+
+ ); +}; + +const AssistantMessage: FC = () => { + return ( + + + + C + + + +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+
+
+ ); +}; + +const BranchPicker: FC<{ className?: string }> = ({ className }) => { + return ( + + + + + + + / + + + + + + + ); +}; + +type ActionButtonProps = ButtonProps & { tooltip: string }; + +const ActionButton: FC = ({ + tooltip, + className, + children, + ...rest +}) => { + return ( + + + + + {tooltip} + + ); +}; diff --git a/apps/docs/components/claude/Claude.tsx b/apps/docs/components/claude/Claude.tsx new file mode 100644 index 000000000..783f182a1 --- /dev/null +++ b/apps/docs/components/claude/Claude.tsx @@ -0,0 +1,102 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { + ActionBarPrimitive, + ComposerPrimitive, + MessagePrimitive, + ThreadPrimitive, +} from "@assistant-ui/react"; +import { useMessageContext } from "@assistant-ui/react"; +import * as Avatar from "@radix-ui/react-avatar"; +import { ArrowUpIcon, ClipboardIcon, ReloadIcon } from "@radix-ui/react-icons"; +import type { FC } from "react"; + +export const Claude: FC = () => { + return ( + + + + +

+ Claude can make mistakes. Please double-check responses. +

+
+
+ + +
+ + + + +
+

+ Claude 3 Sonnet +

+
+
+ ); +}; + +const ChatMessage: FC = () => { + const { useMessage } = useMessageContext(); + const { message } = useMessage(); + + return ( + +
+ {message.role === "assistant" && ( +
+ )} +
+ + + + U + + + + +

+ +

+
+
+ + + + + + Retry + + + + + Copy + + + + + ); +}; diff --git a/apps/docs/components/docs/DataAttributesTable.tsx b/apps/docs/components/docs/DataAttributesTable.tsx new file mode 100644 index 000000000..e3514a693 --- /dev/null +++ b/apps/docs/components/docs/DataAttributesTable.tsx @@ -0,0 +1,52 @@ +import { Box, Code, Table, Text } from "@radix-ui/themes"; +import React from "react"; + +type KeyboardDef = { + attribute: string; + values: string; +}; + +export function DataAttributesTable({ data }: { data: KeyboardDef[] }) { + return ( + + + + + + Data attribute + + Values + + + + + {data.map(({ attribute, values }, i) => { + const key = `${attribute}-${i}`; + return ( + + + {attribute} + + + + {Array.isArray(values) ? ( + + {values.map( + (value, index) => + `"${value}" ${values.length !== index + 1 ? " | " : ""}`, + )} + + ) : ( + + {values} + + )} + + + ); + })} + + + + ); +} diff --git a/apps/docs/components/docs/KeyboardTable.tsx b/apps/docs/components/docs/KeyboardTable.tsx new file mode 100644 index 000000000..81986dc6f --- /dev/null +++ b/apps/docs/components/docs/KeyboardTable.tsx @@ -0,0 +1,43 @@ +import { Box, Flex, Kbd, Table } from "@radix-ui/themes"; +import type React from "react"; + +type KeyboardDef = { + keys: string[]; + description: React.ReactNode; +}; + +export function KeyboardTable({ data }: { data: KeyboardDef[] }) { + return ( + + + + + + Key + + Description + + + + + {data.map(({ keys, description }, i) => { + const key = `${description}-${i}`; + return ( + + + + {keys.map((k) => ( + {k} + ))} + + + + {description} + + ); + })} + + + + ); +} diff --git a/apps/docs/components/docs/ParametersTable.tsx b/apps/docs/components/docs/ParametersTable.tsx new file mode 100644 index 000000000..ff94786f0 --- /dev/null +++ b/apps/docs/components/docs/ParametersTable.tsx @@ -0,0 +1,115 @@ +import { cn } from "@/lib/utils"; +import type { FC, ReactNode } from "react"; + +type ParameterDef = { + name: string; + type?: string; + description: string | ReactNode; + required?: boolean; + default?: string; + children?: Array; +}; + +type ParameterProps = { + parameter: ParameterDef; + isLast: boolean; +}; + +const COMMON_PARAMS: Record = { + asChild: { + name: "asChild", + type: "boolean", + default: "false", + description: ( + <> + Change the default rendered element for the one passed as a child, + merging their props and behavior. +
+
+ Read the{" "} + + Composition + {" "} + guide for more details. + + ), + }, +}; + +const Parameter: FC = ({ + parameter: partialParameter, + isLast, +}) => { + const parameter = { + ...COMMON_PARAMS[partialParameter.name], + ...partialParameter, + }; + + return ( +
+
+

+ {parameter.name} + {!parameter.required && !parameter.default && "?"} + {!!parameter.type && ":"} +

+
+ {parameter.type} + {parameter.default && ` = ${parameter.default}`} +
+
+
+
+

{parameter.description}

+
+ {parameter.children?.map((property) => ( + + ))} +
+ ); +}; + +const ParametersList = ({ + parameters, +}: { + parameters: Array; +}) => { + return parameters.map((parameter, idx) => ( + + )); +}; +const ParametersBox: FC = ({ type, parameters }) => { + return ( +
+ {!!type && ( +

+ {type} +

+ )} + +
+ ); +}; + +export type ParametersTableProps = { + type?: string | undefined; + parameters: Array; +}; + +export const ParametersTable: FC = ({ + type, + parameters, +}) => { + return ( +
+ +
+ ); +}; diff --git a/apps/docs/components/docs/index.ts b/apps/docs/components/docs/index.ts new file mode 100644 index 000000000..1a807541f --- /dev/null +++ b/apps/docs/components/docs/index.ts @@ -0,0 +1,3 @@ +export { KeyboardTable } from "./KeyboardTable"; +export { DataAttributesTable } from "./DataAttributesTable"; +export { ParametersTable } from "./ParametersTable"; diff --git a/apps/docs/components/docs/parameters/context.tsx b/apps/docs/components/docs/parameters/context.tsx new file mode 100644 index 000000000..7302092c3 --- /dev/null +++ b/apps/docs/components/docs/parameters/context.tsx @@ -0,0 +1,402 @@ +import { ParametersTableProps } from "../ParametersTable"; + +export const AssistantActionsState: ParametersTableProps = { + type: "AssistantActionsState", + parameters: [ + { + name: "switchToThread", + type: "(threadId: string | null) => void", + description: "Switch to a new thread.", + required: true, + }, + ], +}; + +export const AssistantModelConfigState: ParametersTableProps = { + type: "AssistantModelConfigState", + parameters: [ + { + name: "getModelConfig", + type: "() => ModelConfig", + description: "Gets the current model config.", + required: true, + children: [ + { + type: "ModelConfig", + parameters: [ + { + name: "system", + type: "string", + description: "The system prompt.", + }, + { + name: "tools", + type: "Record>", + description: "The tools available to the model.", + children: [ + { + type: "Tool", + parameters: [ + { + name: "description", + type: "string", + description: "The tool description.", + }, + { + name: "parameters", + type: "z.ZodType", + description: "The tool parameters.", + }, + { + name: "execute", + type: "(args: TArgs) => Promise", + description: "The tool execution function.", + }, + ], + }, + ], + }, + ], + }, + ], + }, + { + name: "registerModelConfigProvider", + type: "(provider: () => ModelConfig) => Unsubscribe", + description: + "Registers a model config provider to update the model config.", + required: true, + }, + ], +}; + +export const AssistantToolUIsState: ParametersTableProps = { + type: "AssistantToolUIsState", + parameters: [ + { + name: "getToolUI", + type: "(toolName: string) => ToolCallContentPartProps", + description: "Gets the current tool UI for a given tool name.", + required: true, + children: [ + { + type: "ToolCallContentPartProps", + parameters: [ + { + name: "part", + type: "ToolCallContentPart", + description: "The tool call content part.", + }, + { + name: "status", + type: "'in_progress' | 'done' | 'error'", + description: "The tool call status.", + }, + { + name: "addResult", + type: "(result: TResult) => void", + description: "Adds a result to the tool call.", + }, + ], + }, + ], + }, + { + name: "setToolUI", + type: "(toolName: string, render: ToolCallContentPartComponent) => Unsubscribe", + description: "Sets the tool UI.", + required: true, + }, + ], +}; + +export const AssistantContextValue: ParametersTableProps = { + type: "AssistantContextValue", + parameters: [ + { + name: "useAssistantActions", + type: "ReadonlyStore", + required: true, + description: "Provides functions to perform actions on the assistant.", + }, + { + name: "useModelConfig", + type: "ReadonlyStore", + required: true, + description: "Configuration of the model (system prompt, tools, etc.)", + }, + { + name: "useToolUIs", + type: "ReadonlyStore", + required: true, + description: "Tool UIs to render on tool calls.", + children: [], + }, + ], +}; + +export const ThreadState: ParametersTableProps = { + type: "ThreadState", + parameters: [ + { + name: "isRunning", + type: "boolean", + required: true, + description: "Whether the thread is running.", + }, + ], +}; + +export const ThreadMessagesState: ParametersTableProps = { + type: "ThreadMessagesState", + parameters: [ + { + name: "messages", + type: "readonly ThreadMessage[]", + required: true, + description: "The messages in the thread.", + }, + ], +}; + +export const ThreadActionsState: ParametersTableProps = { + type: "ThreadActionsState", + parameters: [ + { + name: "getBranches", + type: "(messageId: string) => readonly string[]", + required: true, + description: "A function to get the branches for a message.", + }, + { + name: "switchToBranch", + type: "(branchId: string) => void", + required: true, + description: "A function to switch to a branch.", + }, + { + name: "append", + type: "(message: AppendMessage) => void", + required: true, + description: "A function to append a message to the thread.", + }, + { + name: "startRun", + type: "(parentId: string | null) => void", + required: true, + description: "A function to start a run.", + }, + { + name: "cancelRun", + type: "() => void", + required: true, + description: "A function to cancel a run.", + }, + { + name: "addToolResult", + type: "(toolCallId: string, result: any) => void", + required: true, + description: "A function to add a tool result.", + }, + ], +}; + +export const BaseComposerState: ParametersTableProps = { + type: "BaseComposerState", + parameters: [ + { + name: "value", + type: "string", + required: true, + description: "The current value of the composer.", + }, + { + name: "setValue", + type: "(value: string) => void", + required: true, + description: "A function to set the value of the composer.", + }, + ], +}; + +export const ComposerState: ParametersTableProps = { + type: "ComposerState", + parameters: [ + ...BaseComposerState.parameters, + { + name: "canCancel", + type: "true", + required: true, + description: "Whether the composer can be canceled.", + }, + { + name: "isEditing", + type: "true", + required: true, + description: "Whether the composer is in edit mode.", + }, + { + name: "send", + type: "() => void", + required: true, + description: "A function to send the message.", + }, + { + name: "cancel", + type: "() => void", + required: true, + description: "A function to cancel the run.", + }, + { + name: "focus", + type: "() => void", + required: true, + description: "A function to focus the composer.", + }, + { + name: "onFocus", + type: "(listener: () => void) => Unsubscribe", + required: true, + description: "A function to subscribe to focus events.", + }, + ], +}; + +export const EditComposerState: ParametersTableProps = { + type: "EditComposerState", + parameters: [ + ...BaseComposerState.parameters, + { + name: "canCancel", + type: "boolean", + required: true, + description: "Whether the composer can be canceled.", + }, + { + name: "isEditing", + type: "boolean", + required: true, + description: "Whether the composer is in edit mode.", + }, + { + name: "edit", + type: "() => void", + required: true, + description: "A function to enter edit mode.", + }, + { + name: "send", + type: "() => void", + required: true, + description: "A function to send the message.", + }, + { + name: "cancel", + type: "() => void", + required: true, + description: "A function to exit the edit mode.", + }, + ], +}; + +export const ThreadViewportState: ParametersTableProps = { + type: "ThreadViewportState", + parameters: [ + { + name: "isAtBottom", + type: "boolean", + required: true, + description: "Whether the thread is at the bottom.", + }, + { + name: "scrollToBottom", + type: "() => void", + required: true, + description: "A function to scroll to the bottom.", + }, + { + name: "onScrollToBottom", + type: "(callback: () => void) => Unsubscribe", + required: true, + description: "A function to subscribe to scroll to bottom events.", + }, + ], +}; + +export const ContentPartState: ParametersTableProps = { + type: "ContentPartState", + parameters: [ + { + name: "part", + type: "Readonly", + required: true, + description: "The current content part.", + }, + { + name: "status", + type: "'done' | 'in_progress' | 'error'", + required: true, + description: "The current content part status.", + }, + ], +}; + +export const MessageState: ParametersTableProps = { + type: "MessageState", + parameters: [ + { + name: "message", + type: "Readonly", + required: true, + description: "The current message.", + }, + { + name: "parentId", + type: "string | null", + required: true, + description: "The parent message id.", + }, + { + name: "branches", + type: "readonly string[]", + required: true, + description: "The branches for the message.", + }, + { + name: "isLast", + type: "boolean", + required: true, + description: "Whether the message is the last in the thread.", + }, + ], +}; + +export const MessageUtilsState: ParametersTableProps = { + type: "MessageUtilsState", + parameters: [ + { + name: "isCopied", + type: "boolean", + required: true, + description: "Whether the message is copied.", + }, + { + name: "setIsCopied", + type: "(value: boolean) => void", + required: true, + description: "A function to set the is copied.", + }, + { + name: "isHovering", + type: "boolean", + required: true, + description: "Whether the message is being hovered.", + }, + { + name: "setIsHovering", + type: "(value: boolean) => void", + required: true, + description: "A function to set the is hovering.", + }, + ], +}; diff --git a/apps/docs/components/docs/parameters/runtime.tsx b/apps/docs/components/docs/parameters/runtime.tsx new file mode 100644 index 000000000..8531380ce --- /dev/null +++ b/apps/docs/components/docs/parameters/runtime.tsx @@ -0,0 +1,126 @@ +import { ParametersTable } from "@/components/docs"; + +export const AssistantRuntimeProviderProps = () => { + return ( + readonly string[]", + required: true, + description: "A function to get the branches for a message.", + }, + { + name: "switchToBranch", + type: "(branchId: string) => void", + required: true, + description: "A function to switch to a branch.", + }, + { + name: "append", + type: "(message: AppendMessage) => void", + required: true, + description: "A function to append a message to the thread.", + }, + { + name: "startRun", + type: "(parentId: string | null) => void", + required: true, + description: "A function to start a run.", + }, + { + name: "cancelRun", + type: "() => void", + required: true, + description: "A function to cancel a run.", + }, + { + name: "addToolResult", + type: "(toolCallId: string, result: any) => void", + required: true, + description: "A function to add a tool result.", + }, + { + name: "subscribe", + type: "(callback: () => void) => Unsubscribe", + required: true, + description: "A function to subscribe to updates.", + }, + { + name: "registerModelConfigProvider", + type: "(provider: ModelConfigProvider) => Unsubscribe", + required: true, + description: + "A function to register a model config provider.", + }, + ], + }, + ], + }, + ]} + /> + ); +}; diff --git a/apps/docs/components/genui/GenUI.tsx b/apps/docs/components/genui/GenUI.tsx new file mode 100644 index 000000000..87d796b21 --- /dev/null +++ b/apps/docs/components/genui/GenUI.tsx @@ -0,0 +1,9 @@ +export const GenUI = () => { + return ( +