Getting Started
Start with a new project
Initialize assistant-ui
Create a new project:
# Create a new project with the default template
npx assistant-ui@latest create
# Or start with a template:
# LangGraph
npx assistant-ui@latest create -t langgraph
# MCP support
npx assistant-ui@latest create -t mcp
Add assistant-ui to an existing React project:
# Add assistant-ui to an existing React project
npx assistant-ui@latest init
Add API key
Add a new .env
file to your project with your OpenAI API key:
OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# chat history -- sign up for free on https://cloud.assistant-ui.com
# NEXT_PUBLIC_ASSISTANT_BASE_URL="https://..."
Start the app
npm run dev
Manual installation
We recommend npx assistant-ui init
to setup existing projects.
Add assistant-ui
npx assistant-ui add thread thread-list
Add the following packages:
npm install \
@assistant-ui/react \
@assistant-ui/react-markdown \
@assistant-ui/styles \
@radix-ui/react-tooltip \
@radix-ui/react-slot \
lucide-react \
remark-gfm \
class-variance-authority \
clsx
Copy the following components into your project:
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("aui-button", {
variants: {
variant: {
default: "aui-button-primary",
outline: "aui-button-outline",
ghost: "aui-button-ghost",
},
size: {
default: "aui-button-medium",
icon: "aui-button-icon",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
});
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}) {
const Comp = asChild ? Slot : "button";
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
export { Button, buttonVariants };
"use client";
import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils";
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn("aui-tooltip-content", className)}
{...props}
/>
</TooltipPrimitive.Portal>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
import {
ActionBarPrimitive,
BranchPickerPrimitive,
ComposerPrimitive,
MessagePrimitive,
ThreadPrimitive,
} from "@assistant-ui/react";
import type { FC } from "react";
import {
ArrowDownIcon,
CheckIcon,
ChevronLeftIcon,
ChevronRightIcon,
CopyIcon,
PencilIcon,
RefreshCwIcon,
SendHorizontalIcon,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { MarkdownText } from "@/components/assistant-ui/markdown-text";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
export const Thread: FC = () => {
return (
<ThreadPrimitive.Root
className="aui-root aui-thread-root"
style={{
["--thread-max-width" as string]: "42rem",
}}
>
<ThreadPrimitive.Viewport className="aui-thread-viewport">
<ThreadWelcome />
<ThreadPrimitive.Messages
components={{
UserMessage: UserMessage,
EditComposer: EditComposer,
AssistantMessage: AssistantMessage,
}}
/>
<ThreadPrimitive.If empty={false}>
<div className="aui-thread-viewport-spacer" />
</ThreadPrimitive.If>
<div className="aui-thread-viewport-footer">
<ThreadScrollToBottom />
<Composer />
</div>
</ThreadPrimitive.Viewport>
</ThreadPrimitive.Root>
);
};
const ThreadScrollToBottom: FC = () => {
return (
<ThreadPrimitive.ScrollToBottom asChild>
<TooltipIconButton
tooltip="Scroll to bottom"
variant="outline"
className="aui-thread-scroll-to-bottom"
>
<ArrowDownIcon />
</TooltipIconButton>
</ThreadPrimitive.ScrollToBottom>
);
};
const ThreadWelcome: FC = () => {
return (
<ThreadPrimitive.Empty>
<div className="aui-thread-welcome-root">
<div className="aui-thread-welcome-center">
<p className="aui-thread-welcome-message">
How can I help you today?
</p>
</div>
<ThreadWelcomeSuggestions />
</div>
</ThreadPrimitive.Empty>
);
};
const ThreadWelcomeSuggestions: FC = () => {
return (
<div className="aui-thread-welcome-suggestions">
<ThreadPrimitive.Suggestion
className="aui-thread-welcome-suggestion"
prompt="What is the weather in Tokyo?"
method="replace"
autoSend
>
<span className="aui-thread-welcome-suggestion-text">
What is the weather in Tokyo?
</span>
</ThreadPrimitive.Suggestion>
<ThreadPrimitive.Suggestion
className="aui-thread-welcome-suggestion"
prompt="What is assistant-ui?"
method="replace"
autoSend
>
<span className="aui-thread-welcome-suggestion-text">
What is assistant-ui?
</span>
</ThreadPrimitive.Suggestion>
</div>
);
};
const Composer: FC = () => {
return (
<ComposerPrimitive.Root className="aui-composer-root">
<ComposerPrimitive.Input
rows={1}
autoFocus
placeholder="Write a message..."
className="aui-composer-input"
/>
<ComposerAction />
</ComposerPrimitive.Root>
);
};
const ComposerAction: FC = () => {
return (
<>
<ThreadPrimitive.If running={false}>
<ComposerPrimitive.Send asChild>
<TooltipIconButton
tooltip="Send"
variant="default"
className="aui-composer-send"
>
<SendHorizontalIcon />
</TooltipIconButton>
</ComposerPrimitive.Send>
</ThreadPrimitive.If>
<ThreadPrimitive.If running>
<ComposerPrimitive.Cancel asChild>
<TooltipIconButton
tooltip="Cancel"
variant="default"
className="aui-composer-cancel"
>
<CircleStopIcon />
</TooltipIconButton>
</ComposerPrimitive.Cancel>
</ThreadPrimitive.If>
</>
);
};
const UserMessage: FC = () => {
return (
<MessagePrimitive.Root className="aui-user-message-root">
<UserActionBar />
<div className="aui-user-message-content">
<MessagePrimitive.Content />
</div>
<BranchPicker className="aui-user-branch-picker" />
</MessagePrimitive.Root>
);
};
const UserActionBar: FC = () => {
return (
<ActionBarPrimitive.Root
hideWhenRunning
autohide="not-last"
className="aui-user-action-bar-root"
>
<ActionBarPrimitive.Edit asChild>
<TooltipIconButton tooltip="Edit">
<PencilIcon />
</TooltipIconButton>
</ActionBarPrimitive.Edit>
</ActionBarPrimitive.Root>
);
};
const EditComposer: FC = () => {
return (
<ComposerPrimitive.Root className="aui-edit-composer-root">
<ComposerPrimitive.Input className="aui-edit-composer-input" />
<div className="aui-edit-composer-footer">
<ComposerPrimitive.Cancel asChild>
<Button variant="ghost">Cancel</Button>
</ComposerPrimitive.Cancel>
<ComposerPrimitive.Send asChild>
<Button>Send</Button>
</ComposerPrimitive.Send>
</div>
</ComposerPrimitive.Root>
);
};
const AssistantMessage: FC = () => {
return (
<MessagePrimitive.Root className="aui-assistant-message-root">
<div className="aui-assistant-message-content">
<MessagePrimitive.Content components={{ Text: MarkdownText }} />
</div>
<AssistantActionBar />
<BranchPicker className="aui-assistant-branch-picker" />
</MessagePrimitive.Root>
);
};
const AssistantActionBar: FC = () => {
return (
<ActionBarPrimitive.Root
hideWhenRunning
autohide="not-last"
autohideFloat="single-branch"
className="aui-assistant-action-bar-root"
>
<ActionBarPrimitive.Copy asChild>
<TooltipIconButton tooltip="Copy">
<MessagePrimitive.If copied>
<CheckIcon />
</MessagePrimitive.If>
<MessagePrimitive.If copied={false}>
<CopyIcon />
</MessagePrimitive.If>
</TooltipIconButton>
</ActionBarPrimitive.Copy>
<ActionBarPrimitive.Reload asChild>
<TooltipIconButton tooltip="Refresh">
<RefreshCwIcon />
</TooltipIconButton>
</ActionBarPrimitive.Reload>
</ActionBarPrimitive.Root>
);
};
const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
className,
...rest
}) => {
return (
<BranchPickerPrimitive.Root
hideWhenSingleBranch
className={cn("aui-branch-picker-root", className)}
{...rest}
>
<BranchPickerPrimitive.Previous asChild>
<TooltipIconButton tooltip="Previous">
<ChevronLeftIcon />
</TooltipIconButton>
</BranchPickerPrimitive.Previous>
<span className="aui-branch-picker-state">
<BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />
</span>
<BranchPickerPrimitive.Next asChild>
<TooltipIconButton tooltip="Next">
<ChevronRightIcon />
</TooltipIconButton>
</BranchPickerPrimitive.Next>
</BranchPickerPrimitive.Root>
);
};
const CircleStopIcon = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
width="16"
height="16"
>
<rect width="10" height="10" x="3" y="3" rx="2" />
</svg>
);
};
import type { FC } from "react";
import {
ThreadListItemPrimitive,
ThreadListPrimitive,
} from "@assistant-ui/react";
import { ArchiveIcon, PlusIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
export const ThreadList: FC = () => {
return (
<ThreadListPrimitive.Root className="aui-root aui-thread-list-root">
<ThreadListNew />
<ThreadListItems />
</ThreadListPrimitive.Root>
);
};
const ThreadListNew: FC = () => {
return (
<ThreadListPrimitive.New asChild>
<Button className="aui-thread-list-new" variant="ghost">
<PlusIcon />
New Thread
</Button>
</ThreadListPrimitive.New>
);
};
const ThreadListItems: FC = () => {
return <ThreadListPrimitive.Items components={{ ThreadListItem }} />;
};
const ThreadListItem: FC = () => {
return (
<ThreadListItemPrimitive.Root className="aui-thread-list-item">
<ThreadListItemPrimitive.Trigger className="aui-thread-list-item-trigger">
<ThreadListItemTitle />
</ThreadListItemPrimitive.Trigger>
<ThreadListItemArchive />
</ThreadListItemPrimitive.Root>
);
};
const ThreadListItemTitle: FC = () => {
return (
<p className="aui-thread-list-item-title">
<ThreadListItemPrimitive.Title fallback="New Chat" />
</p>
);
};
const ThreadListItemArchive: FC = () => {
return (
<ThreadListItemPrimitive.Archive asChild>
<TooltipIconButton
className="aui-thread-list-item-archive"
variant="ghost"
tooltip="Archive thread"
>
<ArchiveIcon />
</TooltipIconButton>
</ThreadListItemPrimitive.Archive>
);
};
"use client";
import "@assistant-ui/react-markdown/styles/dot.css";
import {
CodeHeaderProps,
MarkdownTextPrimitive,
unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,
useIsMarkdownCodeBlock,
} from "@assistant-ui/react-markdown";
import remarkGfm from "remark-gfm";
import { FC, memo, useState } from "react";
import { CheckIcon, CopyIcon } from "lucide-react";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
import { cn } from "@/lib/utils";
const MarkdownTextImpl = () => {
return (
<MarkdownTextPrimitive
remarkPlugins={[remarkGfm]}
className="aui-md"
components={defaultComponents}
/>
);
};
export const MarkdownText = memo(MarkdownTextImpl);
const CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {
const { isCopied, copyToClipboard } = useCopyToClipboard();
const onCopy = () => {
if (!code || isCopied) return;
copyToClipboard(code);
};
return (
<div className="aui-code-header-root">
<span className="aui-code-header-language">{language}</span>
<TooltipIconButton tooltip="Copy" onClick={onCopy}>
{!isCopied && <CopyIcon />}
{isCopied && <CheckIcon />}
</TooltipIconButton>
</div>
);
};
const useCopyToClipboard = ({
copiedDuration = 3000,
}: {
copiedDuration?: number;
} = {}) => {
const [isCopied, setIsCopied] = useState<boolean>(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 }) => (
<h1 className={cn("aui-md-h1", className)} {...props} />
),
h2: ({ className, ...props }) => (
<h2 className={cn("aui-md-h2", className)} {...props} />
),
h3: ({ className, ...props }) => (
<h3 className={cn("aui-md-h3", className)} {...props} />
),
h4: ({ className, ...props }) => (
<h4 className={cn("aui-md-h4", className)} {...props} />
),
h5: ({ className, ...props }) => (
<h5 className={cn("aui-md-h5", className)} {...props} />
),
h6: ({ className, ...props }) => (
<h6 className={cn("aui-md-h6", className)} {...props} />
),
p: ({ className, ...props }) => (
<p className={cn("aui-md-p", className)} {...props} />
),
a: ({ className, ...props }) => (
<a className={cn("aui-md-a", className)} {...props} />
),
blockquote: ({ className, ...props }) => (
<blockquote className={cn("aui-md-blockquote", className)} {...props} />
),
ul: ({ className, ...props }) => (
<ul className={cn("aui-md-ul", className)} {...props} />
),
ol: ({ className, ...props }) => (
<ol className={cn("aui-md-ol", className)} {...props} />
),
hr: ({ className, ...props }) => (
<hr className={cn("aui-md-hr", className)} {...props} />
),
table: ({ className, ...props }) => (
<table className={cn("aui-md-table", className)} {...props} />
),
th: ({ className, ...props }) => (
<th className={cn("aui-md-th", className)} {...props} />
),
td: ({ className, ...props }) => (
<td className={cn("aui-md-td", className)} {...props} />
),
tr: ({ className, ...props }) => (
<tr className={cn("aui-md-tr", className)} {...props} />
),
sup: ({ className, ...props }) => (
<sup className={cn("aui-md-sup", className)} {...props} />
),
pre: ({ className, ...props }) => (
<pre className={cn("aui-md-pre", className)} {...props} />
),
code: function Code({ className, ...props }) {
const isCodeBlock = useIsMarkdownCodeBlock();
return (
<code
className={cn(!isCodeBlock && "aui-md-inline-code", className)}
{...props}
/>
);
},
CodeHeader,
});
"use client";
import { ComponentPropsWithoutRef, forwardRef } from "react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
export type TooltipIconButtonProps = ComponentPropsWithoutRef<typeof Button> & {
tooltip: string;
side?: "top" | "bottom" | "left" | "right";
};
export const TooltipIconButton = forwardRef<
HTMLButtonElement,
TooltipIconButtonProps
>(({ children, tooltip, side = "bottom", className, ...rest }, ref) => {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
{...rest}
className={cn("", className)}
ref={ref}
>
{children}
<span className="aui-sr-only">{tooltip}</span>
</Button>
</TooltipTrigger>
<TooltipContent side={side}>{tooltip}</TooltipContent>
</Tooltip>
</TooltipProvider>
);
});
TooltipIconButton.displayName = "TooltipIconButton";
import { type ClassValue, clsx } from "clsx";
export function cn(...inputs: ClassValue[]) {
return clsx(inputs);
}
The components above reference CSS class names like aui-thread-root
, aui-composer-input
, etc. These are normally replaced by our CLI with Tailwind class names, but in this case you'll use our pre-compiled CSS files without a need for Tailwind:
import "@assistant-ui/styles/index.css";
import "@assistant-ui/styles/markdown.css";
// import "@assistant-ui/styles/modal.css"; // for future reference, only if you use our modal component
Setup Backend Endpoint
Install provider SDK:
npm install ai @assistant-ui/react-ai-sdk @ai-sdk/openai
npm install ai @assistant-ui/react-ai-sdk @ai-sdk/anthropic
npm install ai @assistant-ui/react-ai-sdk @ai-sdk/azure
npm install ai @assistant-ui/react-ai-sdk @ai-sdk/amazon-bedrock
npm install ai @assistant-ui/react-ai-sdk @ai-sdk/google
npm install ai @assistant-ui/react-ai-sdk @ai-sdk/google-vertex
npm install ai @assistant-ui/react-ai-sdk @ai-sdk/openai
npm install ai @assistant-ui/react-ai-sdk @ai-sdk/openai
npm install ai @assistant-ui/react-ai-sdk @ai-sdk/cohere
npm install ai @assistant-ui/react-ai-sdk ollama-ai-provider
npm install ai @assistant-ui/react-ai-sdk chrome-ai
Add an API endpoint:
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai("gpt-4o-mini"),
messages,
});
return result.toDataStreamResponse();
}
import { anthropic } from "@ai-sdk/anthropic";
import { streamText } from "ai";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: anthropic("claude-3-5-sonnet-20240620"),
messages,
});
return result.toDataStreamResponse();
}
import { azure } from "@ai-sdk/azure";
import { streamText } from "ai";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: azure("your-deployment-name"),
messages,
});
return result.toDataStreamResponse();
}
import { bedrock } from "@ai-sdk/amazon-bedrock";
import { streamText } from "ai";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: bedrock("anthropic.claude-3-5-sonnet-20240620-v1:0"),
messages,
});
return result.toDataStreamResponse();
}
import { google } from "@ai-sdk/google";
import { streamText } from "ai";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: google("gemini-2.0-flash"),
messages,
});
return result.toDataStreamResponse();
}
import { vertex } from "@ai-sdk/google-vertex";
import { streamText } from "ai";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: vertex("gemini-1.5-pro"),
messages,
});
return result.toDataStreamResponse();
}
import { createOpenAI } from "@ai-sdk/openai";
import { streamText } from "ai";
export const maxDuration = 30;
const groq = createOpenAI({
apiKey: process.env.GROQ_API_KEY ?? "",
baseURL: "https://api.groq.com/openai/v1",
});
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: groq("llama3-70b-8192"),
messages,
});
return result.toDataStreamResponse();
}
import { createOpenAI } from "@ai-sdk/openai";
import { streamText } from "ai";
export const maxDuration = 30;
const fireworks = createOpenAI({
apiKey: process.env.FIREWORKS_API_KEY ?? "",
baseURL: "https://api.fireworks.ai/inference/v1",
});
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: fireworks("accounts/fireworks/models/firefunction-v2"),
messages,
});
return result.toDataStreamResponse();
}
import { cohere } from "@ai-sdk/cohere";
import { streamText } from "ai";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: cohere("command-r-plus"),
messages,
});
return result.toDataStreamResponse();
}
import { ollama } from "ollama-ai-provider";
import { streamText } from "ai";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: ollama("llama3"),
messages,
});
return result.toDataStreamResponse();
}
import { chromeai } from "chrome-ai";
import { streamText } from "ai";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: chromeai(),
messages,
});
return result.toDataStreamResponse();
}
Define environment variables:
OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
ANTHROPIC_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
AZURE_RESOURCE_NAME="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
AZURE_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
AWS_ACCESS_KEY_ID="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
AWS_SECRET_ACCESS_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
AWS_REGION="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
GOOGLE_GENERATIVE_AI_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
GOOGLE_VERTEX_PROJECT="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
GOOGLE_VERTEX_LOCATION="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
GOOGLE_APPLICATION_CREDENTIALS="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
GROQ_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
FIREWORKS_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
COHERE_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
<none>
<none>
If you aren't using Next.js, you can also deploy this endpoint to Cloudflare Workers, or any other serverless platform.
Use it in your app
import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
import { ThreadList } from "@/components/assistant-ui/thread-list";
import { Thread } from "@/components/assistant-ui/thread";
const MyApp = () => {
const runtime = useChatRuntime({
api: "/api/chat",
});
return (
<AssistantRuntimeProvider runtime={runtime}>
<div className="grid h-dvh grid-cols-[200px_1fr] gap-x-2 px-4 py-4">
<ThreadList />
<Thread />
</div>
</AssistantRuntimeProvider>
);
};
// run `npx shadcn@latest add "https://r.assistant-ui.com/assistant-modal"`
import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
import { AssistantModal } from "@/components/assistant-ui/assistant-modal";
const MyApp = () => {
const runtime = useChatRuntime({
api: "/api/chat",
});
return (
<AssistantRuntimeProvider runtime={runtime}>
<AssistantModal />
</AssistantRuntimeProvider>
);
};