Skip to content

feat (ai/ui): useChat refactor #6243

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 29 commits into
base: v5
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2a60379
feat: chat store in-progress
iteratetograceness May 9, 2025
325eccf
core/ui
iteratetograceness May 9, 2025
d4f28b1
Merge branch 'v5' into chat-store
iteratetograceness May 9, 2025
f884143
react wip
iteratetograceness May 9, 2025
e413ada
react usechat
iteratetograceness May 9, 2025
86ab10a
prettier fix
iteratetograceness May 9, 2025
f338e93
Merge branch 'v5' into chat-store
iteratetograceness May 10, 2025
ea64043
address comments outside chat-store
iteratetograceness May 10, 2025
a7280a1
address comments for chat store
iteratetograceness May 11, 2025
7d545c7
button up useChat react + example
iteratetograceness May 11, 2025
741a3c2
tests
iteratetograceness May 11, 2025
cfca5ea
remove iscontinued
iteratetograceness May 11, 2025
43780cc
prettier
iteratetograceness May 11, 2025
e0116af
split b/w new and legacy
iteratetograceness May 11, 2025
cde4c17
prettier, fix test
iteratetograceness May 11, 2025
6d8e2a5
legacy snapshots
iteratetograceness May 11, 2025
755265f
throttle
iteratetograceness May 12, 2025
46c4e3b
feat (ui): UI message metadata (#6265)
lgrammel May 12, 2025
86ef699
Version Packages (canary) (#6232)
github-actions[bot] May 12, 2025
62dd165
chore (ai): remove deprecated useChat isLoading helper (#6276)
lgrammel May 12, 2025
01b7082
chore (ai): remove `data` and `allowEmptySubmit` from `ChatRequestOpt…
lgrammel May 12, 2025
af50338
chore (ai): remove onResponse callback (#6281)
lgrammel May 12, 2025
265b59e
chore (ai): rename DataStream* to UIMessage* (#6283)
lgrammel May 12, 2025
cfbdc52
Version Packages (canary) (#6277)
github-actions[bot] May 12, 2025
9cf932b
queue
iteratetograceness May 13, 2025
48fc2c1
lars code
iteratetograceness May 13, 2025
49a0dc9
more wip
iteratetograceness May 13, 2025
d865cdd
oop
iteratetograceness May 13, 2025
9ea97ec
Merge branch 'v5' into chat-store
iteratetograceness May 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/next-openai/app/api/use-chat-tools/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { openai } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { convertToModelMessages, streamText, tool } from 'ai';
import { z } from 'zod';

// Allow streaming responses up to 30 seconds
Expand All @@ -11,7 +11,7 @@ export async function POST(req: Request) {
const result = streamText({
model: openai('gpt-4o'),
// model: anthropic('claude-3-5-sonnet-latest'),
messages,
messages: convertToModelMessages(messages),
toolCallStreaming: true,
maxSteps: 5, // multi-steps for server-side tools
tools: {
Expand Down
10 changes: 10 additions & 0 deletions examples/next-openai/app/use-chat-v2/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Chat } from '../chat';

export default async function ChatPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
return <Chat key={id} id={id} />;
}
159 changes: 159 additions & 0 deletions examples/next-openai/app/use-chat-v2/chat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
'use client';

import { useChat } from '@ai-sdk/react';
import { store } from './store';

export function Chat({ id }: { id?: string }) {
const { messages, input, handleInputChange, handleSubmit, addToolResult } =
useChat({
api: '/api/use-chat-tools',
maxSteps: 5,
store,
id,
// run client-side tools that are automatically executed:
async onToolCall({ toolCall }) {
// artificial 2 second delay
await new Promise(resolve => setTimeout(resolve, 2000));

if (toolCall.toolName === 'getLocation') {
const cities = [
'New York',
'Los Angeles',
'Chicago',
'San Francisco',
];
return cities[Math.floor(Math.random() * cities.length)];
}
},

onError(error) {
console.error('error', error);
},
onFinish(message) {
console.log('onFinish', message);
},
});

return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
{messages?.map(message => (
<div key={message.id} className="whitespace-pre-wrap">
<strong>{`${message.role}: `}</strong>
{message.parts.map((part, index) => {
switch (part.type) {
case 'text':
return <div key={index}>{part.text}</div>;
case 'step-start':
return index > 0 ? (
<div key={index} className="text-gray-500">
<hr className="my-2 border-gray-300" />
</div>
) : null;
case 'tool-invocation': {
switch (part.toolInvocation.toolName) {
case 'askForConfirmation': {
switch (part.toolInvocation.state) {
case 'call':
return (
<div key={index} className="text-gray-500">
{part.toolInvocation.args.message}
<div className="flex gap-2">
<button
className="px-4 py-2 font-bold text-white bg-blue-500 rounded hover:bg-blue-700"
onClick={() =>
addToolResult({
toolCallId: part.toolInvocation.toolCallId,
result: 'Yes, confirmed.',
})
}
>
Yes
</button>
<button
className="px-4 py-2 font-bold text-white bg-red-500 rounded hover:bg-red-700"
onClick={() =>
addToolResult({
toolCallId: part.toolInvocation.toolCallId,
result: 'No, denied',
})
}
>
No
</button>
</div>
</div>
);
case 'result':
return (
<div key={index} className="text-gray-500">
Location access allowed:{' '}
{part.toolInvocation.result}
</div>
);
}
break;
}

case 'getLocation': {
switch (part.toolInvocation.state) {
case 'call':
return (
<div key={index} className="text-gray-500">
Getting location...
</div>
);
case 'result':
return (
<div key={index} className="text-gray-500">
Location: {part.toolInvocation.result}
</div>
);
}
break;
}

case 'getWeatherInformation': {
switch (part.toolInvocation.state) {
// example of pre-rendering streaming tool calls:
case 'partial-call':
return (
<pre key={index}>
{JSON.stringify(part.toolInvocation, null, 2)}
</pre>
);
case 'call':
return (
<div key={index} className="text-gray-500">
Getting weather information for{' '}
{part.toolInvocation.args.city}...
</div>
);
case 'result':
return (
<div key={index} className="text-gray-500">
Weather in {part.toolInvocation.args.city}:{' '}
{part.toolInvocation.result}
</div>
);
}
break;
}
}
}
}
})}
<br />
</div>
))}

<form onSubmit={handleSubmit}>
<input
className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={handleInputChange}
/>
</form>
</div>
);
}
44 changes: 44 additions & 0 deletions examples/next-openai/app/use-chat-v2/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client';

import { ChatState } from 'ai';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { store } from './store';

const ChatList = () => {
const [chats, setChats] = useState<[string, ChatState][]>(store.getChats());

useEffect(() => {
const unsubscribe = store.subscribe({
onChatChanged: () => {
setChats(store.getChats());
},
});

return unsubscribe;
}, []);

return (
<div>
<Link href={`/use-chat-v2`}>New Chat</Link>
{chats.map(([id, chat]) => (
<Link key={id} href={`/use-chat-v2/${id}`}>
{id} ({chat.status})
</Link>
))}
</div>
);
};

export default function ChatLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div>
<ChatList />
{children}
</div>
);
}
9 changes: 9 additions & 0 deletions examples/next-openai/app/use-chat-v2/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use client';

import { generateId } from 'ai';
import { Chat } from './chat';

export default function ChatPage() {
const id = generateId();
return <Chat key={id} id={id} />;
}
48 changes: 48 additions & 0 deletions examples/next-openai/app/use-chat-v2/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ChatStore } from 'ai';

export const store = new ChatStore({
chats: {
'1': {
messages: [
{
id: '1',
role: 'user',
parts: [{ type: 'text', text: 'Hello, world!' }],
},
{
id: '2',
role: 'assistant',
parts: [{ type: 'text', text: 'Hello, world!' }],
},
],
},
'2': {
messages: [
{
id: '1',
role: 'user',
parts: [{ type: 'text', text: 'Lucky' }],
},
{
id: '2',
role: 'assistant',
parts: [{ type: 'text', text: 'Vicky' }],
},
],
},
'3': {
messages: [
{
id: '1',
role: 'user',
parts: [{ type: 'text', text: 'Cooper' }],
},
{
id: '2',
role: 'assistant',
parts: [{ type: 'text', text: 'Louie' }],
},
],
},
},
});
Loading
Loading