Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 4 additions & 0 deletions examples/react/start/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
"build": "vite build"
},
"dependencies": {
"@ai-sdk-tools/devtools": "^0.6.0",
"@ai-sdk-tools/store": "^0.1.0",
"@ai-sdk/openai": "^2.0.30",
"@prisma/client": "^6.13.0",
"@prisma/extension-accelerate": "^2.0.2",
"@prisma/studio-core": "^0.5.1",
Expand All @@ -29,6 +32,7 @@
"@tanstack/react-router-with-query": "^1.130.2",
"@tanstack/react-start": "^1.131.2",
"@tanstack/router-plugin": "^1.121.2",
"ai": "^5.0.44",
"prisma": "^6.13.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
Expand Down
73 changes: 73 additions & 0 deletions examples/react/start/src/components/chat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { useChat } from '@ai-sdk-tools/store'
import { DefaultChatTransport } from 'ai'
import { useState, useRef, useEffect } from 'react'

export default function Chat() {
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({
api: '/api/chat',
}),
})
const [input, setInput] = useState('')
const messagesEndRef = useRef<HTMLDivElement | null>(null)

// Scroll to bottom when new messages arrive
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}, [messages])

return (
<div className="max-w-md mx-auto bg-white rounded-lg shadow-md p-6 flex flex-col h-[70vh]">
<div className="flex-1 overflow-y-auto space-y-4 mb-4">
{messages.map((message) => (
<div
key={message.id}
className={`flex ${
message.role === 'user' ? 'justify-end' : 'justify-start'
}`}
>
<div
className={`px-4 py-2 rounded-lg max-w-xs break-words ${
message.role === 'user'
? 'bg-blue-500 text-white'
: 'bg-gray-200 text-gray-900'
}`}
>
{message.parts.map((part, index) =>
part.type === 'text' ? (
<span key={index}>{part.text}</span>
) : null,
)}
</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
<form
className="flex gap-2"
onSubmit={(e) => {
e.preventDefault()
if (input.trim()) {
sendMessage({ text: input })
setInput('')
}
}}
>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
disabled={status !== 'ready'}
placeholder="Say something..."
className="flex-1 px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-400 disabled:bg-gray-100"
/>
<button
type="submit"
disabled={status !== 'ready'}
className="px-4 py-2 bg-blue-500 text-white rounded disabled:bg-blue-300"
>
Submit
</button>
</form>
</div>
)
}
21 changes: 21 additions & 0 deletions examples/react/start/src/components/devtools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import { TanStackDevtools } from '@tanstack/react-devtools'
import { StudioPlugin } from './prisma-plugin'
import ClientPlugin from './client-plugin'

import { DevtoolsPanel, useAIDevtools } from '@ai-sdk-tools/devtools'

const queryClient = new QueryClient()

export default function DevtoolsExample() {
const { events, isCapturing, clearEvents, toggleCapturing } = useAIDevtools()
return (
<>
<QueryClientProvider client={queryClient}>
Expand All @@ -25,6 +28,24 @@ export default function DevtoolsExample() {
name: 'TanStack Router',
render: <TanStackRouterDevtoolsPanel />,
},
{
name: 'AI SDK',
render: (
<DevtoolsPanel
onClose={() => {}}
onTogglePosition={() => {}}
config={{
enabled: true,
maxEvents: 1000,
position: 'bottom',
}}
events={events}
isCapturing={isCapturing}
onClearEvents={clearEvents}
onToggleCapturing={toggleCapturing}
/>
),
},
{
name: 'Prisma Studio',
render: <StudioPlugin />,
Expand Down
24 changes: 21 additions & 3 deletions examples/react/start/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Route as DemoStartServerFuncsRouteImport } from './routes/demo.start.se
import { Route as DemoStartApiRequestRouteImport } from './routes/demo.start.api-request'
import { ServerRoute as StudioServerRouteImport } from './routes/studio'
import { ServerRoute as ApiDemoNamesServerRouteImport } from './routes/api.demo-names'
import { ServerRoute as ApiChatServerRouteImport } from './routes/api.chat'

const rootServerRouteImport = createServerRootRoute()

Expand Down Expand Up @@ -50,6 +51,11 @@ const ApiDemoNamesServerRoute = ApiDemoNamesServerRouteImport.update({
path: '/api/demo-names',
getParentRoute: () => rootServerRouteImport,
} as any)
const ApiChatServerRoute = ApiChatServerRouteImport.update({
id: '/api/chat',
path: '/api/chat',
getParentRoute: () => rootServerRouteImport,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
Expand Down Expand Up @@ -95,27 +101,31 @@ export interface RootRouteChildren {
}
export interface FileServerRoutesByFullPath {
'/studio': typeof StudioServerRoute
'/api/chat': typeof ApiChatServerRoute
'/api/demo-names': typeof ApiDemoNamesServerRoute
}
export interface FileServerRoutesByTo {
'/studio': typeof StudioServerRoute
'/api/chat': typeof ApiChatServerRoute
'/api/demo-names': typeof ApiDemoNamesServerRoute
}
export interface FileServerRoutesById {
__root__: typeof rootServerRouteImport
'/studio': typeof StudioServerRoute
'/api/chat': typeof ApiChatServerRoute
'/api/demo-names': typeof ApiDemoNamesServerRoute
}
export interface FileServerRouteTypes {
fileServerRoutesByFullPath: FileServerRoutesByFullPath
fullPaths: '/studio' | '/api/demo-names'
fullPaths: '/studio' | '/api/chat' | '/api/demo-names'
fileServerRoutesByTo: FileServerRoutesByTo
to: '/studio' | '/api/demo-names'
id: '__root__' | '/studio' | '/api/demo-names'
to: '/studio' | '/api/chat' | '/api/demo-names'
id: '__root__' | '/studio' | '/api/chat' | '/api/demo-names'
fileServerRoutesById: FileServerRoutesById
}
export interface RootServerRouteChildren {
StudioServerRoute: typeof StudioServerRoute
ApiChatServerRoute: typeof ApiChatServerRoute
ApiDemoNamesServerRoute: typeof ApiDemoNamesServerRoute
}

Expand Down Expand Up @@ -167,6 +177,13 @@ declare module '@tanstack/react-start/server' {
preLoaderRoute: typeof ApiDemoNamesServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/api/chat': {
id: '/api/chat'
path: '/api/chat'
fullPath: '/api/chat'
preLoaderRoute: typeof ApiChatServerRouteImport
parentRoute: typeof rootServerRouteImport
}
}
}

Expand All @@ -181,6 +198,7 @@ export const routeTree = rootRouteImport
._addFileTypes<FileRouteTypes>()
const rootServerRouteChildren: RootServerRouteChildren = {
StudioServerRoute: StudioServerRoute,
ApiChatServerRoute: ApiChatServerRoute,
ApiDemoNamesServerRoute: ApiDemoNamesServerRoute,
}
export const serverRouteTree = rootServerRouteImport
Expand Down
17 changes: 17 additions & 0 deletions examples/react/start/src/routes/api.chat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createServerFileRoute } from '@tanstack/react-start/server'
import { openai } from '@ai-sdk/openai'
import { convertToModelMessages, streamText, type UIMessage } from 'ai'

export const ServerRoute = createServerFileRoute('/api/chat').methods({
POST: async ({ request }) => {
const { messages }: { messages: UIMessage[] } = await request.json()

const result = streamText({
model: openai('gpt-4.1'),
system: 'You are a helpful assistant.',
messages: convertToModelMessages(messages),
})

return result.toUIMessageStreamResponse()
},
})
2 changes: 2 additions & 0 deletions examples/react/start/src/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createFileRoute } from '@tanstack/react-router'
import logo from '../logo.svg'
import Chat from '@/components/chat'

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