chore: update readme (#920)
This commit is contained in:
parent
61d8653a58
commit
ca20164763
6 changed files with 4 additions and 604 deletions
|
|
@ -1,13 +1,14 @@
|
||||||
<a href="https://chat.vercel.ai/">
|
<a href="https://chat.vercel.ai/">
|
||||||
<img alt="Next.js 14 and App Router-ready AI chatbot." src="app/(chat)/opengraph-image.png">
|
<img alt="Next.js 14 and App Router-ready AI chatbot." src="app/(chat)/opengraph-image.png">
|
||||||
<h1 align="center">Next.js AI Chatbot</h1>
|
<h1 align="center">Chat SDK</h1>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
An Open-Source AI Chatbot Template Built With Next.js and the AI SDK by Vercel.
|
Chat SDK is a free, open-source template built with Next.js and the AI SDK that helps you quickly build powerful chatbot applications.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
<a href="https://chat-sdk.dev"><strong>Read Docs</strong></a> ·
|
||||||
<a href="#features"><strong>Features</strong></a> ·
|
<a href="#features"><strong>Features</strong></a> ·
|
||||||
<a href="#model-providers"><strong>Model Providers</strong></a> ·
|
<a href="#model-providers"><strong>Model Providers</strong></a> ·
|
||||||
<a href="#deploy-your-own"><strong>Deploy Your Own</strong></a> ·
|
<a href="#deploy-your-own"><strong>Deploy Your Own</strong></a> ·
|
||||||
|
|
@ -58,4 +59,4 @@ pnpm install
|
||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Your app template should now be running on [localhost:3000](http://localhost:3000/).
|
Your app template should now be running on [localhost:3000](http://localhost:3000).
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 162 KiB |
|
|
@ -1,45 +0,0 @@
|
||||||
# Quick Start
|
|
||||||
|
|
||||||
The chatbot template is a web application built using [Next.js](https://nextjs.org) and the [AI SDK](https://sdk.vercel.ai) that can be used as a starting point for building your own AI applications. The template is designed to be easily customizable and extendable, allowing you to add new features and integrations as needed.
|
|
||||||
|
|
||||||
Deploying to [Vercel](https://vercel.com) is the quickest way to get started with the chatbot template, as it automatically sets up the project by connecting to integrations and deploys it to the cloud. You can then later develop the project locally and push changes to the Vercel project.
|
|
||||||
|
|
||||||
### Pre-requisites:
|
|
||||||
|
|
||||||
- Vercel account and [Vercel CLI](https://vercel.com/docs/cli)
|
|
||||||
- GitHub/GitLab/Bitbucket account
|
|
||||||
- API Keys from three AI model providers:
|
|
||||||
- [xAI](https://console.x.ai/)
|
|
||||||
- [OpenAI](https://platform.openai.com/account/api-keys)
|
|
||||||
- [Fireworks](https://fireworks.ai/account/api-keys)
|
|
||||||
|
|
||||||
### Deploy to Vercel
|
|
||||||
|
|
||||||
To deploy the chatbot template to Vercel, click the button below to enter the 1-click deploy flow.
|
|
||||||
|
|
||||||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fai-chatbot&env=AUTH_SECRET&envDescription=Generate%20a%20random%20secret%20to%20use%20for%20authentication&envLink=https%3A%2F%2Fgenerate-secret.vercel.app%2F32&project-name=my-awesome-chatbot&repository-name=my-awesome-chatbot&demo-title=AI%20Chatbot&demo-description=An%20Open-Source%20AI%20Chatbot%20Template%20Built%20With%20Next.js%20and%20the%20AI%20SDK%20by%20Vercel&demo-url=https%3A%2F%2Fchat.vercel.ai&products=%5B%7B%22type%22%3A%22integration%22%2C%22protocol%22%3A%22ai%22%2C%22productSlug%22%3A%22grok%22%2C%22integrationSlug%22%3A%22xai%22%7D%2C%7B%22type%22%3A%22integration%22%2C%22protocol%22%3A%22ai%22%2C%22productSlug%22%3A%22api-key%22%2C%22integrationSlug%22%3A%22groq%22%7D%2C%7B%22type%22%3A%22integration%22%2C%22protocol%22%3A%22storage%22%2C%22productSlug%22%3A%22neon%22%2C%22integrationSlug%22%3A%22neon%22%7D%2C%7B%22type%22%3A%22blob%22%7D%5D)
|
|
||||||
|
|
||||||
During the flow, you will be prompted to create and connect to a postgres database and blob store. You will also need to provide environment variables for the application.
|
|
||||||
|
|
||||||
After deploying the project, you can access the chatbot template by visiting the URL provided by Vercel.
|
|
||||||
|
|
||||||
### Local Development
|
|
||||||
|
|
||||||
To develop the chatbot template locally, you can clone the repository and link it to your Vercel project. This will allow you to pull the environment variables from the Vercel project and use them locally.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/<username>/<repository>
|
|
||||||
cd <repository>
|
|
||||||
pnpm install
|
|
||||||
|
|
||||||
vercel link
|
|
||||||
vercel env pull
|
|
||||||
```
|
|
||||||
|
|
||||||
After linking the project, you can start the development server by running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm dev
|
|
||||||
```
|
|
||||||
|
|
||||||
The chatbot template will be available at `http://localhost:3000`.
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
# Update Models
|
|
||||||
|
|
||||||
The chatbot template ships with [xAI](https://sdk.vercel.ai/providers/ai-sdk-providers/xai) as the default model provider. Since the template is powered by the [AI SDK](https://sdk.vercel.ai), which supports [multiple providers](https://sdk.vercel.ai/providers/ai-sdk-providers) out of the box, you can easily switch to another provider of your choice.
|
|
||||||
|
|
||||||
To update the models, you will need to update the custom provider called `myProvider` at `/lib/ai/models.ts` shown below.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { customProvider } from "ai";
|
|
||||||
import { xai } from "@ai-sdk/xai";
|
|
||||||
import { groq } from "@ai-sdk/groq";
|
|
||||||
import { fal } from "@ai-sdk/fal";
|
|
||||||
|
|
||||||
export const myProvider = customProvider({
|
|
||||||
languageModels: {
|
|
||||||
"chat-model": xai("grok-2-1212"),
|
|
||||||
"chat-model-reasoning": wrapLanguageModel({
|
|
||||||
model: groq("deepseek-r1-distill-llama-70b"),
|
|
||||||
middleware: extractReasoningMiddleware({ tagName: "think" }),
|
|
||||||
}),
|
|
||||||
"title-model": xai("grok-2-1212"),
|
|
||||||
"artifact-model": xai("grok-2-1212"),
|
|
||||||
},
|
|
||||||
imageModels: {
|
|
||||||
"small-model": fal.image("fal-ai/fast-sdxl"),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
You can replace the models with any other provider of your choice. You will need to install the provider library and switch the models accordingly.
|
|
||||||
|
|
||||||
For example, if you want to use Anthropic's `claude-3-5-sonnet` model for `chat-model`, you can replace the `xai` model with the `anthropic` model as shown below.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { customProvider } from "ai";
|
|
||||||
import { fal } from "@ai-sdk/fal";
|
|
||||||
import { groq } from "@ai-sdk/groq";
|
|
||||||
import { anthropic } from "@ai-sdk/anthropic";
|
|
||||||
|
|
||||||
export const myProvider = customProvider({
|
|
||||||
languageModels: {
|
|
||||||
"chat-model": anthropic("claude-3-5-sonnet"), // Replace xai with anthropic
|
|
||||||
"chat-model-reasoning": wrapLanguageModel({
|
|
||||||
model: groq("deepseek-r1-distill-llama-70b"),
|
|
||||||
middleware: extractReasoningMiddleware({ tagName: "think" }),
|
|
||||||
}),
|
|
||||||
"title-model": anthropic("claude-3-5-haiku"),
|
|
||||||
"artifact-model": anthropic("claude-3-5-haiku"),
|
|
||||||
},
|
|
||||||
imageModels: {
|
|
||||||
"small-model": fal.image("fal-ai/fast-sdxl"),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
You can find the provider library and model names in the [provider](https://sdk.vercel.ai/providers/ai-sdk-providers)'s documentation. Once you have updated the models, you should be able to use the new models in your chatbot.
|
|
||||||
|
|
@ -1,258 +0,0 @@
|
||||||
# Artifacts
|
|
||||||
|
|
||||||
Artifacts is a special user interface mode that allows you to have a workspace like interface along with the chat interface. This is similar to [ChatGPT's Canvas](https://openai.com/index/introducing-canvas) and [Claude's Artifacts](https://www.anthropic.com/news/artifacts).
|
|
||||||
|
|
||||||
The template already ships with the following artifacts:
|
|
||||||
|
|
||||||
- **Text Artifact**: A artifact that allows you to work with text content like drafting essays and emails.
|
|
||||||
- **Code Artifact**: A artifact that allows you to write and execute code (Python).
|
|
||||||
- **Image Artifact**: A artifact that allows you to work with images like editing, annotating, and processing images.
|
|
||||||
- **Sheet Artifact**: A artifact that allows you to work with tabular data like creating, editing, and analyzing data.
|
|
||||||
|
|
||||||
## Adding a Custom Artifact
|
|
||||||
|
|
||||||
To add a custom artifact, you will need to create a folder in the `artifacts` directory with the artifact name. The folder should contain the following files:
|
|
||||||
|
|
||||||
- `client.tsx`: The client-side code for the artifact.
|
|
||||||
- `server.ts`: The server-side code for the artifact.
|
|
||||||
|
|
||||||
Here is an example of a custom artifact called `CustomArtifact`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
artifacts/
|
|
||||||
custom/
|
|
||||||
client.tsx
|
|
||||||
server.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Client-Side Example (client.tsx)
|
|
||||||
|
|
||||||
This file is responsible for rendering your custom artifact. You might replace the inner UI with your own components, but the overall pattern (initialization, handling streamed data, and rendering content) remains the same. For instance:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Artifact } from "@/components/create-artifact";
|
|
||||||
import { ExampleComponent } from "@/components/example-component";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface CustomArtifactMetadata {
|
|
||||||
// Define metadata your custom artifact might need—the example below is minimal.
|
|
||||||
info: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const customArtifact = new Artifact<"custom", CustomArtifactMetadata>({
|
|
||||||
kind: "custom",
|
|
||||||
description: "A custom artifact for demonstrating custom functionality.",
|
|
||||||
// Initialization can fetch any extra data or perform side effects
|
|
||||||
initialize: async ({ documentId, setMetadata }) => {
|
|
||||||
// For example, initialize the artifact with default metadata.
|
|
||||||
setMetadata({
|
|
||||||
info: `Document ${documentId} initialized.`,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Handle streamed parts from the server (if your artifact supports streaming updates)
|
|
||||||
onStreamPart: ({ streamPart, setMetadata, setArtifact }) => {
|
|
||||||
if (streamPart.type === "info-update") {
|
|
||||||
setMetadata((metadata) => ({
|
|
||||||
...metadata,
|
|
||||||
info: streamPart.content as string,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (streamPart.type === "content-update") {
|
|
||||||
setArtifact((draftArtifact) => ({
|
|
||||||
...draftArtifact,
|
|
||||||
content: draftArtifact.content + (streamPart.content as string),
|
|
||||||
status: "streaming",
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Defines how the artifact content is rendered
|
|
||||||
content: ({
|
|
||||||
mode,
|
|
||||||
status,
|
|
||||||
content,
|
|
||||||
isCurrentVersion,
|
|
||||||
currentVersionIndex,
|
|
||||||
onSaveContent,
|
|
||||||
getDocumentContentById,
|
|
||||||
isLoading,
|
|
||||||
metadata,
|
|
||||||
}) => {
|
|
||||||
if (isLoading) {
|
|
||||||
return <div>Loading custom artifact...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode === "diff") {
|
|
||||||
const oldContent = getDocumentContentById(currentVersionIndex - 1);
|
|
||||||
const newContent = getDocumentContentById(currentVersionIndex);
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3>Diff View</h3>
|
|
||||||
<pre>{oldContent}</pre>
|
|
||||||
<pre>{newContent}</pre>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="custom-artifact">
|
|
||||||
<ExampleComponent
|
|
||||||
content={content}
|
|
||||||
metadata={metadata}
|
|
||||||
onSaveContent={onSaveContent}
|
|
||||||
isCurrentVersion={isCurrentVersion}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
navigator.clipboard.writeText(content);
|
|
||||||
toast.success("Content copied to clipboard!");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Copy
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
// An optional set of actions exposed in the artifact toolbar.
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
icon: <span>⟳</span>,
|
|
||||||
description: "Refresh artifact info",
|
|
||||||
onClick: ({ appendMessage }) => {
|
|
||||||
appendMessage({
|
|
||||||
role: "user",
|
|
||||||
content: "Please refresh the info for my custom artifact.",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
// Additional toolbar actions for more control
|
|
||||||
toolbar: [
|
|
||||||
{
|
|
||||||
icon: <span>✎</span>,
|
|
||||||
description: "Edit custom artifact",
|
|
||||||
onClick: ({ appendMessage }) => {
|
|
||||||
appendMessage({
|
|
||||||
role: "user",
|
|
||||||
content: "Edit the custom artifact content.",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Server-Side Example (server.ts)
|
|
||||||
|
|
||||||
The server file processes the document for the artifact. It streams updates (if applicable) and returns the final content. For example:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { smoothStream, streamText } from "ai";
|
|
||||||
import { myProvider } from "@/lib/ai/providers";
|
|
||||||
import { createDocumentHandler } from "@/lib/artifacts/server";
|
|
||||||
import { updateDocumentPrompt } from "@/lib/ai/prompts";
|
|
||||||
|
|
||||||
export const customDocumentHandler = createDocumentHandler<"custom">({
|
|
||||||
kind: "custom",
|
|
||||||
// Called when the document is first created.
|
|
||||||
onCreateDocument: async ({ title, dataStream }) => {
|
|
||||||
let draftContent = "";
|
|
||||||
// For demonstration, use streamText to generate content.
|
|
||||||
const { fullStream } = streamText({
|
|
||||||
model: myProvider.languageModel("artifact-model"),
|
|
||||||
system:
|
|
||||||
"Generate a creative piece based on the title. Markdown is supported.",
|
|
||||||
experimental_transform: smoothStream({ chunking: "word" }),
|
|
||||||
prompt: title,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Stream the content back to the client.
|
|
||||||
for await (const delta of fullStream) {
|
|
||||||
if (delta.type === "text-delta") {
|
|
||||||
draftContent += delta.textDelta;
|
|
||||||
dataStream.writeData({
|
|
||||||
type: "content-update",
|
|
||||||
content: delta.textDelta,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return draftContent;
|
|
||||||
},
|
|
||||||
// Called when updating the document based on user modifications.
|
|
||||||
onUpdateDocument: async ({ document, description, dataStream }) => {
|
|
||||||
let draftContent = "";
|
|
||||||
const { fullStream } = streamText({
|
|
||||||
model: myProvider.languageModel("artifact-model"),
|
|
||||||
system: updateDocumentPrompt(document.content, "custom"),
|
|
||||||
experimental_transform: smoothStream({ chunking: "word" }),
|
|
||||||
prompt: description,
|
|
||||||
experimental_providerMetadata: {
|
|
||||||
openai: {
|
|
||||||
prediction: {
|
|
||||||
type: "content",
|
|
||||||
content: document.content,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
for await (const delta of fullStream) {
|
|
||||||
if (delta.type === "text-delta") {
|
|
||||||
draftContent += delta.textDelta;
|
|
||||||
dataStream.writeData({
|
|
||||||
type: "content-update",
|
|
||||||
content: delta.textDelta,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return draftContent;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Once you have created the client and server files, you can import the artifact in the `lib/artifacts/server.ts` file and add it to the `documentHandlersByArtifactKind` array.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export const documentHandlersByArtifactKind: Array<DocumentHandler> = [
|
|
||||||
...,
|
|
||||||
customDocumentHandler,
|
|
||||||
];
|
|
||||||
|
|
||||||
export const artifactKinds = [..., "custom"] as const;
|
|
||||||
```
|
|
||||||
|
|
||||||
Specify it in document schema at `lib/db/schema.ts`.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
export const document = pgTable(
|
|
||||||
"Document",
|
|
||||||
{
|
|
||||||
id: uuid("id").notNull().defaultRandom(),
|
|
||||||
createdAt: timestamp("createdAt").notNull(),
|
|
||||||
title: text("title").notNull(),
|
|
||||||
content: text("content"),
|
|
||||||
kind: varchar("text", { enum: [..., "custom"] }) // Add the custom artifact kind here
|
|
||||||
.notNull()
|
|
||||||
.default("text"),
|
|
||||||
userId: uuid("userId")
|
|
||||||
.notNull()
|
|
||||||
.references(() => user.id),
|
|
||||||
},
|
|
||||||
(table) => {
|
|
||||||
return {
|
|
||||||
pk: primaryKey({ columns: [table.id, table.createdAt] }),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
And also add the client-side artifact to the `artifactDefinitions` array in the `components/artifact.tsx` file.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { customArtifact } from "@/artifacts/custom/client";
|
|
||||||
|
|
||||||
export const artifactDefinitions = [..., customArtifact];
|
|
||||||
```
|
|
||||||
|
|
||||||
You should now be able to see the custom artifact in the workspace!
|
|
||||||
|
|
@ -1,243 +0,0 @@
|
||||||
# Migrate to Message Parts
|
|
||||||
|
|
||||||
The release of [`@ai-sdk/react@1.1.10`](https://github.com/vercel/ai/pull/4670) introduced a new property called `parts` to messages in the `useChat` hook. We recommend rendering the messages using the `parts` property instead of the `content` property. The parts property supports different message types, including text, tool invocation, and tool result, and allows for more flexible and complex chat and agent-like user interfaces.
|
|
||||||
|
|
||||||
You can read the API reference for the `parts` property [here](https://sdk.vercel.ai/docs/reference/ai-sdk-ui/use-chat#messages.ui-message.parts).
|
|
||||||
|
|
||||||
## Migrating your existing project to use the `parts` property
|
|
||||||
|
|
||||||
Your existing project must already have messages stored in the database. To migrate your messages to use `parts`, you will have to create new tables for `Message` and `Vote`, backfill the new tables with transformed messages, and delete (optional) the old tables.
|
|
||||||
|
|
||||||
These are the following steps:
|
|
||||||
1. Create tables `Message_v2` and `Vote_v2` with updated schemas at `/lib/db/schema.ts`
|
|
||||||
2. Update the `Message` component at `/components/message.tsx` to use parts and render content.
|
|
||||||
3. Run migration script at `src/lib/db/helpers/01-migrate-to-parts.ts`
|
|
||||||
|
|
||||||
### 1. Creating Tables with Updated Schemas
|
|
||||||
|
|
||||||
You will mark the earlier tables as deprecated and create two new tables, `Message_v2` and `Vote_v2`. Table `Message_v2` contains the updated schema that includes the `parts` property. Table `Vote_v2` does not contain schema changes but will contain votes that point to the transformed messages in `Message_v2`.
|
|
||||||
|
|
||||||
Before creating the new tables, you will need to update the variables of existing tables to indicate that they are deprecated.
|
|
||||||
|
|
||||||
```tsx title="/lib/db/schema.ts"
|
|
||||||
export const messageDeprecated = pgTable("Message", {
|
|
||||||
id: uuid("id").primaryKey().notNull().defaultRandom(),
|
|
||||||
chatId: uuid("chatId")
|
|
||||||
.notNull()
|
|
||||||
.references(() => chat.id),
|
|
||||||
role: varchar("role").notNull(),
|
|
||||||
content: json("content").notNull(),
|
|
||||||
createdAt: timestamp("createdAt").notNull(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type MessageDeprecated = InferSelectModel<typeof messageDeprecated>;
|
|
||||||
|
|
||||||
export const voteDeprecated = pgTable(
|
|
||||||
"Vote",
|
|
||||||
{
|
|
||||||
chatId: uuid("chatId")
|
|
||||||
.notNull()
|
|
||||||
.references(() => chat.id),
|
|
||||||
messageId: uuid("messageId")
|
|
||||||
.notNull()
|
|
||||||
.references(() => messageDeprecated.id),
|
|
||||||
isUpvoted: boolean("isUpvoted").notNull(),
|
|
||||||
},
|
|
||||||
(table) => {
|
|
||||||
return {
|
|
||||||
pk: primaryKey({ columns: [table.chatId, table.messageId] }),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export type VoteDeprecated = InferSelectModel<typeof voteDeprecated>;
|
|
||||||
```
|
|
||||||
|
|
||||||
After deprecating the current table schemas, you can now proceed to create schemas for the new tables.
|
|
||||||
|
|
||||||
```ts title="/lib/db/schema.ts"
|
|
||||||
export const message = pgTable("Message_v2", {
|
|
||||||
id: uuid("id").primaryKey().notNull().defaultRandom(),
|
|
||||||
chatId: uuid("chatId")
|
|
||||||
.notNull()
|
|
||||||
.references(() => chat.id),
|
|
||||||
role: varchar("role").notNull(),
|
|
||||||
parts: json("parts").notNull(),
|
|
||||||
attachments: json("attachments").notNull(),
|
|
||||||
createdAt: timestamp("createdAt").notNull(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type Message = InferSelectModel<typeof message>;
|
|
||||||
|
|
||||||
export const vote = pgTable(
|
|
||||||
"Vote_v2",
|
|
||||||
{
|
|
||||||
chatId: uuid("chatId")
|
|
||||||
.notNull()
|
|
||||||
.references(() => chat.id),
|
|
||||||
messageId: uuid("messageId")
|
|
||||||
.notNull()
|
|
||||||
.references(() => message.id),
|
|
||||||
isUpvoted: boolean("isUpvoted").notNull(),
|
|
||||||
},
|
|
||||||
(table) => {
|
|
||||||
return {
|
|
||||||
pk: primaryKey({ columns: [table.chatId, table.messageId] }),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export type Vote = InferSelectModel<typeof vote>;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Updating the Message Component
|
|
||||||
|
|
||||||
Previously you were using content types to render messages and tool invocations. Now you will use the `parts` property to render messages and tool invocations.
|
|
||||||
|
|
||||||
```tsx title="components/message.tsx"
|
|
||||||
{message.parts?.map((part, index) => {
|
|
||||||
const { type } = part;
|
|
||||||
const key = `message-${message.id}-part-${index}`;
|
|
||||||
|
|
||||||
if (type === "reasoning") {
|
|
||||||
return (
|
|
||||||
<MessageReasoning
|
|
||||||
key={key}
|
|
||||||
isLoading={isLoading}
|
|
||||||
reasoning={part.reasoning}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "text") {
|
|
||||||
if (mode === "view") {
|
|
||||||
return (
|
|
||||||
<div key={key} className="flex flex-row gap-2 items-start">
|
|
||||||
{message.role === "user" && !isReadonly && (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button
|
|
||||||
data-testid="message-edit-button"
|
|
||||||
variant="ghost"
|
|
||||||
className="px-2 h-fit rounded-full text-muted-foreground opacity-0 group-hover/message:opacity-100"
|
|
||||||
onClick={() => {
|
|
||||||
setMode("edit");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PencilEditIcon />
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>Edit message</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
data-testid="message-content"
|
|
||||||
className={cn("flex flex-col gap-4", {
|
|
||||||
"bg-primary text-primary-foreground px-3 py-2 rounded-xl":
|
|
||||||
message.role === "user",
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Markdown>{part.text}</Markdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode === "edit") {
|
|
||||||
return (
|
|
||||||
<div key={key} className="flex flex-row gap-2 items-start">
|
|
||||||
<div className="size-8" />
|
|
||||||
|
|
||||||
<MessageEditor
|
|
||||||
key={message.id}
|
|
||||||
message={message}
|
|
||||||
setMode={setMode}
|
|
||||||
setMessages={setMessages}
|
|
||||||
reload={reload}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "tool-invocation") {
|
|
||||||
const { toolInvocation } = part;
|
|
||||||
const { toolName, toolCallId, state } = toolInvocation;
|
|
||||||
|
|
||||||
if (state === "call") {
|
|
||||||
const { args } = toolInvocation;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={toolCallId}
|
|
||||||
className={cx({
|
|
||||||
skeleton: ["getWeather"].includes(toolName),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{toolName === "getWeather" ? (
|
|
||||||
<Weather />
|
|
||||||
) : toolName === "createDocument" ? (
|
|
||||||
<DocumentPreview isReadonly={isReadonly} args={args} />
|
|
||||||
) : toolName === "updateDocument" ? (
|
|
||||||
<DocumentToolCall
|
|
||||||
type="update"
|
|
||||||
args={args}
|
|
||||||
isReadonly={isReadonly}
|
|
||||||
/>
|
|
||||||
) : toolName === "requestSuggestions" ? (
|
|
||||||
<DocumentToolCall
|
|
||||||
type="request-suggestions"
|
|
||||||
args={args}
|
|
||||||
isReadonly={isReadonly}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state === "result") {
|
|
||||||
const { result } = toolInvocation;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={toolCallId}>
|
|
||||||
{toolName === "getWeather" ? (
|
|
||||||
<Weather weatherAtLocation={result} />
|
|
||||||
) : toolName === "createDocument" ? (
|
|
||||||
<DocumentPreview
|
|
||||||
isReadonly={isReadonly}
|
|
||||||
result={result}
|
|
||||||
/>
|
|
||||||
) : toolName === "updateDocument" ? (
|
|
||||||
<DocumentToolResult
|
|
||||||
type="update"
|
|
||||||
result={result}
|
|
||||||
isReadonly={isReadonly}
|
|
||||||
/>
|
|
||||||
) : toolName === "requestSuggestions" ? (
|
|
||||||
<DocumentToolResult
|
|
||||||
type="request-suggestions"
|
|
||||||
result={result}
|
|
||||||
isReadonly={isReadonly}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<pre>{JSON.stringify(result, null, 2)}</pre>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Running the Migration Script
|
|
||||||
|
|
||||||
At this point, you can deploy your application so new messages can be stored in the new format.
|
|
||||||
|
|
||||||
To restore messages of previous chat conversations, you can run the following script that applies a transformation to the old messages and stores them in the new format.
|
|
||||||
|
|
||||||
```zsh title="shell"
|
|
||||||
pnpm exec tsx lib/db/helpers/01-core-to-parts.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
This script will take some time to complete based on the number of messages to be migrated. After completion, you can verify that the messages have been successfully migrated by checking the database.
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue