Skip to content

Commit 4d7425e

Browse files
feat: add ai sdk devtools (#128)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 2509c7f commit 4d7425e

File tree

7 files changed

+849
-35
lines changed

7 files changed

+849
-35
lines changed

examples/react/start/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
"build": "vite build"
1616
},
1717
"dependencies": {
18+
"@ai-sdk-tools/devtools": "^0.6.0",
19+
"@ai-sdk-tools/store": "^0.1.0",
20+
"@ai-sdk/openai": "^2.0.30",
1821
"@prisma/client": "^6.13.0",
1922
"@prisma/extension-accelerate": "^2.0.2",
2023
"@prisma/studio-core": "^0.5.1",
@@ -29,6 +32,7 @@
2932
"@tanstack/react-router-with-query": "^1.130.2",
3033
"@tanstack/react-start": "^1.131.2",
3134
"@tanstack/router-plugin": "^1.121.2",
35+
"ai": "^5.0.44",
3236
"prisma": "^6.13.0",
3337
"react": "^19.1.0",
3438
"react-dom": "^19.1.0",
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { useChat } from '@ai-sdk-tools/store'
2+
import { DefaultChatTransport } from 'ai'
3+
import { useState, useRef, useEffect } from 'react'
4+
5+
export default function Chat() {
6+
const { messages, sendMessage, status } = useChat({
7+
transport: new DefaultChatTransport({
8+
api: '/api/chat',
9+
}),
10+
})
11+
const [input, setInput] = useState('')
12+
const messagesEndRef = useRef<HTMLDivElement | null>(null)
13+
14+
// Scroll to bottom when new messages arrive
15+
useEffect(() => {
16+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
17+
}, [messages])
18+
19+
return (
20+
<div className="max-w-md mx-auto bg-white rounded-lg shadow-md p-6 flex flex-col h-[70vh]">
21+
<div className="flex-1 overflow-y-auto space-y-4 mb-4">
22+
{messages.map((message) => (
23+
<div
24+
key={message.id}
25+
className={`flex ${
26+
message.role === 'user' ? 'justify-end' : 'justify-start'
27+
}`}
28+
>
29+
<div
30+
className={`px-4 py-2 rounded-lg max-w-xs break-words ${
31+
message.role === 'user'
32+
? 'bg-blue-500 text-white'
33+
: 'bg-gray-200 text-gray-900'
34+
}`}
35+
>
36+
{message.parts.map((part, index) =>
37+
part.type === 'text' ? (
38+
<span key={index}>{part.text}</span>
39+
) : null,
40+
)}
41+
</div>
42+
</div>
43+
))}
44+
<div ref={messagesEndRef} />
45+
</div>
46+
<form
47+
className="flex gap-2"
48+
onSubmit={(e) => {
49+
e.preventDefault()
50+
if (input.trim()) {
51+
sendMessage({ text: input })
52+
setInput('')
53+
}
54+
}}
55+
>
56+
<input
57+
value={input}
58+
onChange={(e) => setInput(e.target.value)}
59+
disabled={status !== 'ready'}
60+
placeholder="Say something..."
61+
className="flex-1 px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-400 disabled:bg-gray-100"
62+
/>
63+
<button
64+
type="submit"
65+
disabled={status !== 'ready'}
66+
className="px-4 py-2 bg-blue-500 text-white rounded disabled:bg-blue-300"
67+
>
68+
Submit
69+
</button>
70+
</form>
71+
</div>
72+
)
73+
}

examples/react/start/src/components/devtools.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ import { TanStackDevtools } from '@tanstack/react-devtools'
55
import { StudioPlugin } from './prisma-plugin'
66
import ClientPlugin from './client-plugin'
77

8+
import { DevtoolsPanel, useAIDevtools } from '@ai-sdk-tools/devtools'
9+
810
const queryClient = new QueryClient()
911

1012
export default function DevtoolsExample() {
13+
const { events, isCapturing, clearEvents, toggleCapturing } = useAIDevtools()
1114
return (
1215
<>
1316
<QueryClientProvider client={queryClient}>
@@ -25,6 +28,24 @@ export default function DevtoolsExample() {
2528
name: 'TanStack Router',
2629
render: <TanStackRouterDevtoolsPanel />,
2730
},
31+
{
32+
name: 'AI SDK',
33+
render: (
34+
<DevtoolsPanel
35+
onClose={() => {}}
36+
onTogglePosition={() => {}}
37+
config={{
38+
enabled: true,
39+
maxEvents: 1000,
40+
position: 'bottom',
41+
}}
42+
events={events}
43+
isCapturing={isCapturing}
44+
onClearEvents={clearEvents}
45+
onToggleCapturing={toggleCapturing}
46+
/>
47+
),
48+
},
2849
{
2950
name: 'Prisma Studio',
3051
render: <StudioPlugin />,

examples/react/start/src/routeTree.gen.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { Route as DemoStartServerFuncsRouteImport } from './routes/demo.start.se
1717
import { Route as DemoStartApiRequestRouteImport } from './routes/demo.start.api-request'
1818
import { ServerRoute as StudioServerRouteImport } from './routes/studio'
1919
import { ServerRoute as ApiDemoNamesServerRouteImport } from './routes/api.demo-names'
20+
import { ServerRoute as ApiChatServerRouteImport } from './routes/api.chat'
2021

2122
const rootServerRouteImport = createServerRootRoute()
2223

@@ -50,6 +51,11 @@ const ApiDemoNamesServerRoute = ApiDemoNamesServerRouteImport.update({
5051
path: '/api/demo-names',
5152
getParentRoute: () => rootServerRouteImport,
5253
} as any)
54+
const ApiChatServerRoute = ApiChatServerRouteImport.update({
55+
id: '/api/chat',
56+
path: '/api/chat',
57+
getParentRoute: () => rootServerRouteImport,
58+
} as any)
5359

5460
export interface FileRoutesByFullPath {
5561
'/': typeof IndexRoute
@@ -95,27 +101,31 @@ export interface RootRouteChildren {
95101
}
96102
export interface FileServerRoutesByFullPath {
97103
'/studio': typeof StudioServerRoute
104+
'/api/chat': typeof ApiChatServerRoute
98105
'/api/demo-names': typeof ApiDemoNamesServerRoute
99106
}
100107
export interface FileServerRoutesByTo {
101108
'/studio': typeof StudioServerRoute
109+
'/api/chat': typeof ApiChatServerRoute
102110
'/api/demo-names': typeof ApiDemoNamesServerRoute
103111
}
104112
export interface FileServerRoutesById {
105113
__root__: typeof rootServerRouteImport
106114
'/studio': typeof StudioServerRoute
115+
'/api/chat': typeof ApiChatServerRoute
107116
'/api/demo-names': typeof ApiDemoNamesServerRoute
108117
}
109118
export interface FileServerRouteTypes {
110119
fileServerRoutesByFullPath: FileServerRoutesByFullPath
111-
fullPaths: '/studio' | '/api/demo-names'
120+
fullPaths: '/studio' | '/api/chat' | '/api/demo-names'
112121
fileServerRoutesByTo: FileServerRoutesByTo
113-
to: '/studio' | '/api/demo-names'
114-
id: '__root__' | '/studio' | '/api/demo-names'
122+
to: '/studio' | '/api/chat' | '/api/demo-names'
123+
id: '__root__' | '/studio' | '/api/chat' | '/api/demo-names'
115124
fileServerRoutesById: FileServerRoutesById
116125
}
117126
export interface RootServerRouteChildren {
118127
StudioServerRoute: typeof StudioServerRoute
128+
ApiChatServerRoute: typeof ApiChatServerRoute
119129
ApiDemoNamesServerRoute: typeof ApiDemoNamesServerRoute
120130
}
121131

@@ -167,6 +177,13 @@ declare module '@tanstack/react-start/server' {
167177
preLoaderRoute: typeof ApiDemoNamesServerRouteImport
168178
parentRoute: typeof rootServerRouteImport
169179
}
180+
'/api/chat': {
181+
id: '/api/chat'
182+
path: '/api/chat'
183+
fullPath: '/api/chat'
184+
preLoaderRoute: typeof ApiChatServerRouteImport
185+
parentRoute: typeof rootServerRouteImport
186+
}
170187
}
171188
}
172189

@@ -181,6 +198,7 @@ export const routeTree = rootRouteImport
181198
._addFileTypes<FileRouteTypes>()
182199
const rootServerRouteChildren: RootServerRouteChildren = {
183200
StudioServerRoute: StudioServerRoute,
201+
ApiChatServerRoute: ApiChatServerRoute,
184202
ApiDemoNamesServerRoute: ApiDemoNamesServerRoute,
185203
}
186204
export const serverRouteTree = rootServerRouteImport
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { createServerFileRoute } from '@tanstack/react-start/server'
2+
import { openai } from '@ai-sdk/openai'
3+
import { convertToModelMessages, streamText, type UIMessage } from 'ai'
4+
5+
export const ServerRoute = createServerFileRoute('/api/chat').methods({
6+
POST: async ({ request }) => {
7+
const { messages }: { messages: UIMessage[] } = await request.json()
8+
9+
const result = streamText({
10+
model: openai('gpt-4.1'),
11+
system: 'You are a helpful assistant.',
12+
messages: convertToModelMessages(messages),
13+
})
14+
15+
return result.toUIMessageStreamResponse()
16+
},
17+
})

examples/react/start/src/routes/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createFileRoute } from '@tanstack/react-router'
22
import logo from '../logo.svg'
3+
import Chat from '@/components/chat'
34

45
export const Route = createFileRoute('/')({
56
component: App,
@@ -44,6 +45,7 @@ export const Route = createFileRoute('/')({
4445
function App() {
4546
return (
4647
<div className="text-center">
48+
<Chat />
4749
<header className="min-h-screen flex flex-col items-center justify-center bg-[#282c34] text-white text-[calc(10px+2vmin)]">
4850
<img
4951
src={logo}

0 commit comments

Comments
 (0)