├── .eslintrc.json ├── app ├── favicon.ico ├── api │ └── chat │ │ └── route.ts ├── MyRuntimeProvider.tsx ├── layout.tsx ├── syntax-highlighter.tsx ├── page.tsx ├── globals.css └── ArtifactsView.tsx ├── .env.example ├── next.config.mjs ├── postcss.config.mjs ├── lib └── utils.ts ├── components.json ├── .gitignore ├── README.md ├── tsconfig.json ├── package.json ├── components └── ui │ └── tabs.tsx ├── tailwind.config.ts └── pnpm-lock.yaml /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yonom/assistant-ui-artifacts/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_FIREWORKS_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 2 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /app/api/chat/route.ts: -------------------------------------------------------------------------------- 1 | import { openai } from "@ai-sdk/openai"; 2 | import { createEdgeRuntimeAPI } from "@assistant-ui/react/edge"; 3 | 4 | export const runtime = "edge"; 5 | 6 | export const { POST } = createEdgeRuntimeAPI({ 7 | model: openai("gpt-3.5-turbo"), 8 | }); 9 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /app/MyRuntimeProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { AssistantRuntimeProvider, useEdgeRuntime } from "@assistant-ui/react"; 4 | 5 | export function MyRuntimeProvider({ 6 | children, 7 | }: Readonly<{ 8 | children: React.ReactNode; 9 | }>) { 10 | const runtime = useEdgeRuntime({ 11 | api: "/api/chat", 12 | }); 13 | 14 | return ( 15 | 16 | {children} 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is the [assistant-ui](https://github.com/Yonom/assistant-ui) starter project. 2 | 3 | ## Getting Started 4 | 5 | First, add your OpenAI API key to `.env.local` file: 6 | 7 | ``` 8 | NEXT_PUBLIC_FIREWORKS_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 9 | ``` 10 | 11 | Then, run the development server: 12 | 13 | ```bash 14 | npm run dev 15 | # or 16 | yarn dev 17 | # or 18 | pnpm dev 19 | # or 20 | bun dev 21 | ``` 22 | 23 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 24 | 25 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import { MyRuntimeProvider } from "@/app/MyRuntimeProvider"; 4 | import { cn } from "@/lib/utils"; 5 | 6 | import "./globals.css"; 7 | 8 | const inter = Inter({ subsets: ["latin"] }); 9 | 10 | export const metadata: Metadata = { 11 | title: "Create Next App", 12 | description: "Generated by create next app", 13 | }; 14 | 15 | export default function RootLayout({ 16 | children, 17 | }: Readonly<{ 18 | children: React.ReactNode; 19 | }>) { 20 | return ( 21 | 22 | 23 | {children} 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /app/syntax-highlighter.tsx: -------------------------------------------------------------------------------- 1 | import { PrismAsyncLight } from "react-syntax-highlighter"; 2 | import { makePrismAsyncLightSyntaxHighlighter } from "@assistant-ui/react-syntax-highlighter"; 3 | 4 | import tsx from "react-syntax-highlighter/dist/esm/languages/prism/tsx"; 5 | import python from "react-syntax-highlighter/dist/esm/languages/prism/python"; 6 | import html from "react-syntax-highlighter/dist/esm/languages/prism/xml-doc"; 7 | 8 | import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism"; 9 | import { makeMarkdownText } from "@assistant-ui/react-markdown"; 10 | 11 | // register languages you want to support 12 | PrismAsyncLight.registerLanguage("html", html); 13 | PrismAsyncLight.registerLanguage("js", tsx); 14 | PrismAsyncLight.registerLanguage("jsx", tsx); 15 | PrismAsyncLight.registerLanguage("ts", tsx); 16 | PrismAsyncLight.registerLanguage("tsx", tsx); 17 | PrismAsyncLight.registerLanguage("python", python); 18 | 19 | export const SyntaxHighlighter = makePrismAsyncLightSyntaxHighlighter({ 20 | style: coldarkDark, 21 | customStyle: { 22 | margin: 0, 23 | width: "100%", 24 | background: "#000", 25 | padding: "1.5rem 1rem", 26 | }, 27 | }); 28 | 29 | export const MarkdownText = makeMarkdownText({ 30 | components: { SyntaxHighlighter }, 31 | }); 32 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { makeAssistantTool, Thread } from "@assistant-ui/react"; 4 | import { ArtifactsView } from "./ArtifactsView"; 5 | import { MarkdownText } from "./syntax-highlighter"; 6 | import { TerminalIcon } from "lucide-react"; 7 | import { z } from "zod"; 8 | 9 | const RenderHTMLTool = makeAssistantTool({ 10 | toolName: "render_html", 11 | description: 12 | "Whenever the user asks for HTML code, call this function. The user will see the HTML code rendered in their browser.", 13 | parameters: z.object({ 14 | code: z.string(), 15 | }), 16 | execute: async () => { 17 | return {}; 18 | }, 19 | render: () => { 20 | return ( 21 |
22 | 23 | render_html({"{"} code: "..." {"}"}) 24 |
25 | ); 26 | }, 27 | }); 28 | 29 | export default function Home() { 30 | return ( 31 |
32 |
33 | 34 |
35 | 36 | 37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "assistant-ui-starter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@ai-sdk/openai": "^0.0.36", 13 | "@assistant-ui/react": "^0.4", 14 | "@assistant-ui/react-markdown": "^0.1.2", 15 | "@assistant-ui/react-syntax-highlighter": "^0.0.3", 16 | "@radix-ui/react-avatar": "^1.1.0", 17 | "@radix-ui/react-tabs": "^1.1.0", 18 | "class-variance-authority": "^0.7.0", 19 | "clsx": "^2.1.1", 20 | "html-format": "^1.1.7", 21 | "lucide-react": "^0.408.0", 22 | "next": "14.2.5", 23 | "react": "^18", 24 | "react-dom": "^18", 25 | "react-syntax-highlighter": "^15.5.0", 26 | "tailwind-merge": "^2.4.0", 27 | "tailwindcss-animate": "^1.0.7", 28 | "zod": "^3.23.8" 29 | }, 30 | "devDependencies": { 31 | "@types/node": "^20", 32 | "@types/react": "^18", 33 | "@types/react-dom": "^18", 34 | "@types/react-syntax-highlighter": "^15.5.13", 35 | "eslint": "^8", 36 | "eslint-config-next": "14.2.5", 37 | "postcss": "^8", 38 | "tailwindcss": "^3.4.5", 39 | "typescript": "^5" 40 | }, 41 | "packageManager": "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903" 42 | } 43 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 240 10% 3.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 240 10% 3.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 240 10% 3.9%; 15 | 16 | --primary: 240 5.9% 10%; 17 | --primary-foreground: 0 0% 98%; 18 | 19 | --secondary: 240 4.8% 95.9%; 20 | --secondary-foreground: 240 5.9% 10%; 21 | 22 | --muted: 240 4.8% 95.9%; 23 | --muted-foreground: 240 3.8% 46.1%; 24 | 25 | --accent: 240 4.8% 95.9%; 26 | --accent-foreground: 240 5.9% 10%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 0 0% 98%; 30 | 31 | --border: 240 5.9% 90%; 32 | --input: 240 5.9% 90%; 33 | --ring: 240 10% 3.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 240 10% 3.9%; 40 | --foreground: 0 0% 98%; 41 | 42 | --card: 240 10% 3.9%; 43 | --card-foreground: 0 0% 98%; 44 | 45 | --popover: 240 10% 3.9%; 46 | --popover-foreground: 0 0% 98%; 47 | 48 | --primary: 0 0% 98%; 49 | --primary-foreground: 240 5.9% 10%; 50 | 51 | --secondary: 240 3.7% 15.9%; 52 | --secondary-foreground: 0 0% 98%; 53 | 54 | --muted: 240 3.7% 15.9%; 55 | --muted-foreground: 240 5% 64.9%; 56 | 57 | --accent: 240 3.7% 15.9%; 58 | --accent-foreground: 0 0% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 0 0% 98%; 62 | 63 | --border: 240 3.7% 15.9%; 64 | --input: 240 3.7% 15.9%; 65 | --ring: 240 4.9% 83.9%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } -------------------------------------------------------------------------------- /app/ArtifactsView.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { 3 | ToolCallContentPart, 4 | useAssistantTool, 5 | useMessageContext, 6 | useThreadContext, 7 | } from "@assistant-ui/react"; 8 | import { TerminalIcon } from "lucide-react"; 9 | import { useCallback, useRef, useState } from "react"; 10 | import z from "zod"; 11 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 12 | 13 | export const ArtifactsView = () => { 14 | const { useThreadMessages } = useThreadContext(); 15 | const artifact = useThreadMessages((t) => { 16 | return t 17 | .flatMap((m) => 18 | m.content.filter( 19 | (c): c is ToolCallContentPart => 20 | c.type === "tool-call" && c.toolName === "render_html" 21 | ) 22 | ) 23 | .at(-1)?.args["code"] as string | undefined; 24 | }); 25 | 26 | if (!artifact) return null; 27 | 28 | return ( 29 |
34 |
35 | 36 | 37 | Source Code 38 | Preview 39 | 40 | 44 | {artifact} 45 | 46 | 47 | {artifact &&