@tanstack/cta-framework-react-cra 0.17.4 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/add-ons/db/assets/src/components/demo.chat-area.tsx +1 -8
- package/add-ons/store/assets/src/lib/demo-store-devtools.tsx +64 -0
- package/add-ons/store/info.json +7 -0
- package/add-ons/store/package.json +3 -0
- package/add-ons/tanstack-query/assets/src/integrations/tanstack-query/devtools.tsx +6 -0
- package/add-ons/tanstack-query/info.json +3 -3
- package/add-ons/tanstack-query/package.json +1 -1
- package/examples/tanchat/assets/src/components/example-AIAssistant.tsx +59 -58
- package/examples/tanchat/assets/src/routes/api.demo-chat.ts +43 -0
- package/examples/tanchat/assets/src/routes/example.chat.tsx +54 -30
- package/examples/tanchat/assets/src/utils/demo.tools.ts +8 -2
- package/examples/tanchat/package.json +3 -3
- package/package.json +2 -2
- package/project/base/package.json +2 -1
- package/project/base/src/routes/__root.tsx.ejs +28 -5
- package/tests/snapshots/react-cra/cr-js-form-npm.json +2 -2
- package/tests/snapshots/react-cra/cr-js-npm.json +2 -2
- package/tests/snapshots/react-cra/cr-ts-npm.json +2 -2
- package/tests/snapshots/react-cra/cr-ts-start-npm.json +2 -2
- package/tests/snapshots/react-cra/cr-ts-start-tanstack-query-npm.json +3 -3
- package/tests/snapshots/react-cra/fr-ts-biome-npm.json +2 -2
- package/tests/snapshots/react-cra/fr-ts-npm.json +2 -2
- package/tests/snapshots/react-cra/fr-ts-tw-npm.json +2 -2
- package/add-ons/tanstack-query/assets/src/integrations/tanstack-query/layout.tsx +0 -5
- package/examples/tanchat/assets/src/utils/demo.ai.ts +0 -62
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { useChat, useMessages } from '@/hooks/demo.useChat'
|
|
4
4
|
|
|
5
5
|
import Messages from './demo.messages'
|
|
6
6
|
|
|
7
7
|
export default function ChatArea() {
|
|
8
|
-
const messagesEndRef = useRef<HTMLDivElement>(null)
|
|
9
|
-
|
|
10
8
|
const { sendMessage } = useChat()
|
|
11
9
|
|
|
12
10
|
const messages = useMessages()
|
|
@@ -14,10 +12,6 @@ export default function ChatArea() {
|
|
|
14
12
|
const [message, setMessage] = useState('')
|
|
15
13
|
const [user, setUser] = useState('Alice')
|
|
16
14
|
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|
19
|
-
}, [messages])
|
|
20
|
-
|
|
21
15
|
const postMessage = () => {
|
|
22
16
|
if (message.trim().length) {
|
|
23
17
|
sendMessage(message, user)
|
|
@@ -35,7 +29,6 @@ export default function ChatArea() {
|
|
|
35
29
|
<>
|
|
36
30
|
<div className="px-4 py-6 space-y-4">
|
|
37
31
|
<Messages messages={messages} user={user} />
|
|
38
|
-
<div ref={messagesEndRef} />
|
|
39
32
|
</div>
|
|
40
33
|
|
|
41
34
|
<div className="bg-white border-t border-gray-200 px-4 py-4">
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { EventClient } from '@tanstack/devtools-event-client'
|
|
2
|
+
import { useState, useEffect } from 'react'
|
|
3
|
+
|
|
4
|
+
import { store, fullName } from './demo-store'
|
|
5
|
+
|
|
6
|
+
type EventMap = {
|
|
7
|
+
'store-devtools:state': {
|
|
8
|
+
firstName: string
|
|
9
|
+
lastName: string
|
|
10
|
+
fullName: string
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class StoreDevtoolsEventClient extends EventClient<EventMap> {
|
|
15
|
+
constructor() {
|
|
16
|
+
super({
|
|
17
|
+
pluginId: 'store-devtools',
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const sdec = new StoreDevtoolsEventClient()
|
|
23
|
+
|
|
24
|
+
store.subscribe(() => {
|
|
25
|
+
sdec.emit('state', {
|
|
26
|
+
firstName: store.state.firstName,
|
|
27
|
+
lastName: store.state.lastName,
|
|
28
|
+
fullName: fullName.state,
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
function DevtoolPanel() {
|
|
33
|
+
const [state, setState] = useState<EventMap['store-devtools:state']>(() => ({
|
|
34
|
+
firstName: store.state.firstName,
|
|
35
|
+
lastName: store.state.lastName,
|
|
36
|
+
fullName: fullName.state,
|
|
37
|
+
}))
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
return sdec.on('state', (e) => setState(e.payload))
|
|
41
|
+
}, [])
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="p-4 grid gap-4 grid-cols-[1fr_10fr]">
|
|
45
|
+
<div className="text-sm font-bold text-gray-500 whitespace-nowrap">
|
|
46
|
+
First Name
|
|
47
|
+
</div>
|
|
48
|
+
<div className="text-sm">{state?.firstName}</div>
|
|
49
|
+
<div className="text-sm font-bold text-gray-500 whitespace-nowrap">
|
|
50
|
+
Last Name
|
|
51
|
+
</div>
|
|
52
|
+
<div className="text-sm">{state?.lastName}</div>
|
|
53
|
+
<div className="text-sm font-bold text-gray-500 whitespace-nowrap">
|
|
54
|
+
Full Name
|
|
55
|
+
</div>
|
|
56
|
+
<div className="text-sm">{state?.fullName}</div>
|
|
57
|
+
</div>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default {
|
|
62
|
+
name: 'TanStack Store',
|
|
63
|
+
render: <DevtoolPanel />,
|
|
64
|
+
}
|
package/add-ons/store/info.json
CHANGED
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"jsName": "TanStackQueryProvider"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
|
-
"type": "
|
|
24
|
-
"path": "src/integrations/tanstack-query/
|
|
25
|
-
"jsName": "
|
|
23
|
+
"type": "devtools",
|
|
24
|
+
"path": "src/integrations/tanstack-query/devtools.tsx",
|
|
25
|
+
"jsName": "TanStackQueryDevtools"
|
|
26
26
|
}
|
|
27
27
|
]
|
|
28
28
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react'
|
|
1
|
+
import { useEffect, useRef, useState } from 'react'
|
|
2
2
|
import { useStore } from '@tanstack/react-store'
|
|
3
3
|
import { Send, X } from 'lucide-react'
|
|
4
4
|
import ReactMarkdown from 'react-markdown'
|
|
@@ -7,7 +7,7 @@ import rehypeSanitize from 'rehype-sanitize'
|
|
|
7
7
|
import rehypeHighlight from 'rehype-highlight'
|
|
8
8
|
import remarkGfm from 'remark-gfm'
|
|
9
9
|
import { useChat } from '@ai-sdk/react'
|
|
10
|
-
import {
|
|
10
|
+
import { DefaultChatTransport } from 'ai'
|
|
11
11
|
|
|
12
12
|
import { showAIAssistant } from '../store/example-assistant'
|
|
13
13
|
import GuitarRecommendation from './example-GuitarRecommendation'
|
|
@@ -34,7 +34,7 @@ function Messages({ messages }: { messages: Array<UIMessage> }) {
|
|
|
34
34
|
|
|
35
35
|
return (
|
|
36
36
|
<div ref={messagesContainerRef} className="flex-1 overflow-y-auto">
|
|
37
|
-
{messages.map(({ id, role,
|
|
37
|
+
{messages.map(({ id, role, parts }) => (
|
|
38
38
|
<div
|
|
39
39
|
key={id}
|
|
40
40
|
className={`py-3 ${
|
|
@@ -43,45 +43,49 @@ function Messages({ messages }: { messages: Array<UIMessage> }) {
|
|
|
43
43
|
: 'bg-transparent'
|
|
44
44
|
}`}
|
|
45
45
|
>
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<div className="
|
|
50
|
-
|
|
46
|
+
{parts.map((part) => {
|
|
47
|
+
if (part.type === 'text') {
|
|
48
|
+
return (
|
|
49
|
+
<div className="flex items-start gap-2 px-4">
|
|
50
|
+
{role === 'assistant' ? (
|
|
51
|
+
<div className="w-6 h-6 rounded-lg bg-gradient-to-r from-orange-500 to-red-600 flex items-center justify-center text-xs font-medium text-white flex-shrink-0">
|
|
52
|
+
AI
|
|
53
|
+
</div>
|
|
54
|
+
) : (
|
|
55
|
+
<div className="w-6 h-6 rounded-lg bg-gray-700 flex items-center justify-center text-xs font-medium text-white flex-shrink-0">
|
|
56
|
+
Y
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
59
|
+
<div className="flex-1 min-w-0">
|
|
60
|
+
<ReactMarkdown
|
|
61
|
+
className="prose dark:prose-invert max-w-none prose-sm"
|
|
62
|
+
rehypePlugins={[
|
|
63
|
+
rehypeRaw,
|
|
64
|
+
rehypeSanitize,
|
|
65
|
+
rehypeHighlight,
|
|
66
|
+
remarkGfm,
|
|
67
|
+
]}
|
|
68
|
+
>
|
|
69
|
+
{part.text}
|
|
70
|
+
</ReactMarkdown>
|
|
71
|
+
</div>
|
|
51
72
|
</div>
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
if (
|
|
76
|
+
part.type === 'tool-recommendGuitar' &&
|
|
77
|
+
part.state === 'output-available' &&
|
|
78
|
+
(part.output as { id: string })?.id
|
|
79
|
+
) {
|
|
80
|
+
return (
|
|
81
|
+
<div key={id} className="max-w-[80%] mx-auto">
|
|
82
|
+
<GuitarRecommendation
|
|
83
|
+
id={(part.output as { id: string })?.id}
|
|
84
|
+
/>
|
|
55
85
|
</div>
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
className="prose dark:prose-invert max-w-none prose-sm"
|
|
60
|
-
rehypePlugins={[
|
|
61
|
-
rehypeRaw,
|
|
62
|
-
rehypeSanitize,
|
|
63
|
-
rehypeHighlight,
|
|
64
|
-
remarkGfm,
|
|
65
|
-
]}
|
|
66
|
-
>
|
|
67
|
-
{content}
|
|
68
|
-
</ReactMarkdown>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
)}
|
|
72
|
-
{parts
|
|
73
|
-
.filter((part) => part.type === 'tool-invocation')
|
|
74
|
-
.filter(
|
|
75
|
-
(part) => part.toolInvocation.toolName === 'recommendGuitar',
|
|
76
|
-
)
|
|
77
|
-
.map((toolCall) => (
|
|
78
|
-
<div
|
|
79
|
-
key={toolCall.toolInvocation.toolName}
|
|
80
|
-
className="max-w-[80%] mx-auto"
|
|
81
|
-
>
|
|
82
|
-
<GuitarRecommendation id={toolCall.toolInvocation.args.id} />
|
|
83
|
-
</div>
|
|
84
|
-
))}
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
})}
|
|
85
89
|
</div>
|
|
86
90
|
))}
|
|
87
91
|
</div>
|
|
@@ -90,22 +94,12 @@ function Messages({ messages }: { messages: Array<UIMessage> }) {
|
|
|
90
94
|
|
|
91
95
|
export default function AIAssistant() {
|
|
92
96
|
const isOpen = useStore(showAIAssistant)
|
|
93
|
-
const { messages,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return genAIResponse({
|
|
98
|
-
data: {
|
|
99
|
-
messages,
|
|
100
|
-
},
|
|
101
|
-
})
|
|
102
|
-
},
|
|
103
|
-
onToolCall: (call) => {
|
|
104
|
-
if (call.toolCall.toolName === 'recommendGuitar') {
|
|
105
|
-
return 'Handled by the UI'
|
|
106
|
-
}
|
|
107
|
-
},
|
|
97
|
+
const { messages, sendMessage } = useChat({
|
|
98
|
+
transport: new DefaultChatTransport({
|
|
99
|
+
api: '/api/demo-chat',
|
|
100
|
+
}),
|
|
108
101
|
})
|
|
102
|
+
const [input, setInput] = useState('')
|
|
109
103
|
|
|
110
104
|
return (
|
|
111
105
|
<div className="relative z-50">
|
|
@@ -134,11 +128,17 @@ export default function AIAssistant() {
|
|
|
134
128
|
<Messages messages={messages} />
|
|
135
129
|
|
|
136
130
|
<div className="p-3 border-t border-orange-500/20">
|
|
137
|
-
<form
|
|
131
|
+
<form
|
|
132
|
+
onSubmit={(e) => {
|
|
133
|
+
e.preventDefault()
|
|
134
|
+
sendMessage({ text: input })
|
|
135
|
+
setInput('')
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
138
|
<div className="relative">
|
|
139
139
|
<textarea
|
|
140
140
|
value={input}
|
|
141
|
-
onChange={
|
|
141
|
+
onChange={(e) => setInput(e.target.value)}
|
|
142
142
|
placeholder="Type your message..."
|
|
143
143
|
className="w-full rounded-lg border border-orange-500/20 bg-gray-800/50 pl-3 pr-10 py-2 text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-orange-500/50 focus:border-transparent resize-none overflow-hidden"
|
|
144
144
|
rows={1}
|
|
@@ -152,7 +152,8 @@ export default function AIAssistant() {
|
|
|
152
152
|
onKeyDown={(e) => {
|
|
153
153
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
154
154
|
e.preventDefault()
|
|
155
|
-
|
|
155
|
+
sendMessage({ text: input })
|
|
156
|
+
setInput('')
|
|
156
157
|
}
|
|
157
158
|
}}
|
|
158
159
|
/>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createServerFileRoute } from '@tanstack/react-start/server'
|
|
2
|
+
import { anthropic } from '@ai-sdk/anthropic'
|
|
3
|
+
import { convertToModelMessages, stepCountIs, streamText } from 'ai'
|
|
4
|
+
|
|
5
|
+
import getTools from '@/utils/demo.tools'
|
|
6
|
+
|
|
7
|
+
const SYSTEM_PROMPT = `You are a helpful assistant for a store that sells guitars.
|
|
8
|
+
|
|
9
|
+
You can use the following tools to help the user:
|
|
10
|
+
|
|
11
|
+
- getGuitars: Get all guitars from the database
|
|
12
|
+
- recommendGuitar: Recommend a guitar to the user
|
|
13
|
+
`
|
|
14
|
+
|
|
15
|
+
export const ServerRoute = createServerFileRoute('/api/demo-chat').methods({
|
|
16
|
+
POST: async ({ request }) => {
|
|
17
|
+
try {
|
|
18
|
+
const { messages } = await request.json()
|
|
19
|
+
|
|
20
|
+
const tools = await getTools()
|
|
21
|
+
|
|
22
|
+
const result = await streamText({
|
|
23
|
+
model: anthropic('claude-3-5-sonnet-latest'),
|
|
24
|
+
messages: convertToModelMessages(messages),
|
|
25
|
+
temperature: 0.7,
|
|
26
|
+
stopWhen: stepCountIs(5),
|
|
27
|
+
system: SYSTEM_PROMPT,
|
|
28
|
+
tools,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return result.toUIMessageStreamResponse()
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error('Chat API error:', error)
|
|
34
|
+
return new Response(
|
|
35
|
+
JSON.stringify({ error: 'Failed to process chat request' }),
|
|
36
|
+
{
|
|
37
|
+
status: 500,
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react'
|
|
1
2
|
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
-
import { useEffect, useRef } from 'react'
|
|
3
3
|
import { Send } from 'lucide-react'
|
|
4
4
|
import ReactMarkdown from 'react-markdown'
|
|
5
5
|
import rehypeRaw from 'rehype-raw'
|
|
@@ -7,11 +7,12 @@ import rehypeSanitize from 'rehype-sanitize'
|
|
|
7
7
|
import rehypeHighlight from 'rehype-highlight'
|
|
8
8
|
import remarkGfm from 'remark-gfm'
|
|
9
9
|
import { useChat } from '@ai-sdk/react'
|
|
10
|
-
|
|
11
|
-
import { genAIResponse } from '../utils/demo.ai'
|
|
10
|
+
import { DefaultChatTransport } from 'ai'
|
|
12
11
|
|
|
13
12
|
import type { UIMessage } from 'ai'
|
|
14
13
|
|
|
14
|
+
import GuitarRecommendation from '@/components/example-GuitarRecommendation'
|
|
15
|
+
|
|
15
16
|
import '../demo.index.css'
|
|
16
17
|
|
|
17
18
|
function InitalLayout({ children }: { children: React.ReactNode }) {
|
|
@@ -56,10 +57,10 @@ function Messages({ messages }: { messages: Array<UIMessage> }) {
|
|
|
56
57
|
return (
|
|
57
58
|
<div ref={messagesContainerRef} className="flex-1 overflow-y-auto pb-24">
|
|
58
59
|
<div className="max-w-3xl mx-auto w-full px-4">
|
|
59
|
-
{messages.map(({ id, role,
|
|
60
|
+
{messages.map(({ id, role, parts }) => (
|
|
60
61
|
<div
|
|
61
62
|
key={id}
|
|
62
|
-
className={`
|
|
63
|
+
className={`p-4 ${
|
|
63
64
|
role === 'assistant'
|
|
64
65
|
? 'bg-gradient-to-r from-orange-500/5 to-red-600/5'
|
|
65
66
|
: 'bg-transparent'
|
|
@@ -75,18 +76,39 @@ function Messages({ messages }: { messages: Array<UIMessage> }) {
|
|
|
75
76
|
Y
|
|
76
77
|
</div>
|
|
77
78
|
)}
|
|
78
|
-
<div className="flex-1
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
<div className="flex-1">
|
|
80
|
+
{parts.map((part, index) => {
|
|
81
|
+
if (part.type === 'text') {
|
|
82
|
+
return (
|
|
83
|
+
<div className="flex-1 min-w-0" key={index}>
|
|
84
|
+
<ReactMarkdown
|
|
85
|
+
className="prose dark:prose-invert max-w-none"
|
|
86
|
+
rehypePlugins={[
|
|
87
|
+
rehypeRaw,
|
|
88
|
+
rehypeSanitize,
|
|
89
|
+
rehypeHighlight,
|
|
90
|
+
remarkGfm,
|
|
91
|
+
]}
|
|
92
|
+
>
|
|
93
|
+
{part.text}
|
|
94
|
+
</ReactMarkdown>
|
|
95
|
+
</div>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
if (
|
|
99
|
+
part.type === 'tool-recommendGuitar' &&
|
|
100
|
+
part.state === 'output-available' &&
|
|
101
|
+
(part.output as { id: string })?.id
|
|
102
|
+
) {
|
|
103
|
+
return (
|
|
104
|
+
<div key={index} className="max-w-[80%] mx-auto">
|
|
105
|
+
<GuitarRecommendation
|
|
106
|
+
id={(part.output as { id: string })?.id}
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
})}
|
|
90
112
|
</div>
|
|
91
113
|
</div>
|
|
92
114
|
</div>
|
|
@@ -97,17 +119,12 @@ function Messages({ messages }: { messages: Array<UIMessage> }) {
|
|
|
97
119
|
}
|
|
98
120
|
|
|
99
121
|
function ChatPage() {
|
|
100
|
-
const { messages,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return genAIResponse({
|
|
105
|
-
data: {
|
|
106
|
-
messages,
|
|
107
|
-
},
|
|
108
|
-
})
|
|
109
|
-
},
|
|
122
|
+
const { messages, sendMessage } = useChat({
|
|
123
|
+
transport: new DefaultChatTransport({
|
|
124
|
+
api: '/api/demo-chat',
|
|
125
|
+
}),
|
|
110
126
|
})
|
|
127
|
+
const [input, setInput] = useState('')
|
|
111
128
|
|
|
112
129
|
const Layout = messages.length ? ChattingLayout : InitalLayout
|
|
113
130
|
|
|
@@ -117,11 +134,17 @@ function ChatPage() {
|
|
|
117
134
|
<Messages messages={messages} />
|
|
118
135
|
|
|
119
136
|
<Layout>
|
|
120
|
-
<form
|
|
137
|
+
<form
|
|
138
|
+
onSubmit={(e) => {
|
|
139
|
+
e.preventDefault()
|
|
140
|
+
sendMessage({ text: input })
|
|
141
|
+
setInput('')
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
121
144
|
<div className="relative max-w-xl mx-auto">
|
|
122
145
|
<textarea
|
|
123
146
|
value={input}
|
|
124
|
-
onChange={
|
|
147
|
+
onChange={(e) => setInput(e.target.value)}
|
|
125
148
|
placeholder="Type something clever (or don't, we won't judge)..."
|
|
126
149
|
className="w-full rounded-lg border border-orange-500/20 bg-gray-800/50 pl-4 pr-12 py-3 text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-orange-500/50 focus:border-transparent resize-none overflow-hidden shadow-lg"
|
|
127
150
|
rows={1}
|
|
@@ -135,7 +158,8 @@ function ChatPage() {
|
|
|
135
158
|
onKeyDown={(e) => {
|
|
136
159
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
137
160
|
e.preventDefault()
|
|
138
|
-
|
|
161
|
+
sendMessage({ text: input })
|
|
162
|
+
setInput('')
|
|
139
163
|
}
|
|
140
164
|
}}
|
|
141
165
|
/>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { experimental_createMCPClient, tool } from 'ai'
|
|
2
2
|
//import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
3
|
import { z } from 'zod'
|
|
4
|
+
|
|
4
5
|
import guitars from '../data/example-guitars'
|
|
5
6
|
|
|
6
7
|
// Example of using an SSE MCP server
|
|
@@ -24,7 +25,7 @@ import guitars from '../data/example-guitars'
|
|
|
24
25
|
|
|
25
26
|
const getGuitars = tool({
|
|
26
27
|
description: 'Get all products from the database',
|
|
27
|
-
|
|
28
|
+
inputSchema: z.object({}),
|
|
28
29
|
execute: async () => {
|
|
29
30
|
return Promise.resolve(guitars)
|
|
30
31
|
},
|
|
@@ -32,9 +33,14 @@ const getGuitars = tool({
|
|
|
32
33
|
|
|
33
34
|
const recommendGuitar = tool({
|
|
34
35
|
description: 'Use this tool to recommend a guitar to the user',
|
|
35
|
-
|
|
36
|
+
inputSchema: z.object({
|
|
36
37
|
id: z.string().describe('The id of the guitar to recommend'),
|
|
37
38
|
}),
|
|
39
|
+
execute: async ({ id }) => {
|
|
40
|
+
return {
|
|
41
|
+
id,
|
|
42
|
+
}
|
|
43
|
+
},
|
|
38
44
|
})
|
|
39
45
|
|
|
40
46
|
export default async function getTools() {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
-
"@ai-sdk/anthropic": "^
|
|
4
|
-
"@ai-sdk/react": "^
|
|
3
|
+
"@ai-sdk/anthropic": "^2.0.1",
|
|
4
|
+
"@ai-sdk/react": "^2.0.8",
|
|
5
5
|
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
6
|
-
"ai": "^
|
|
6
|
+
"ai": "^5.0.8",
|
|
7
7
|
"highlight.js": "^11.11.1",
|
|
8
8
|
"react-markdown": "^9.0.1",
|
|
9
9
|
"rehype-highlight": "^7.0.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/cta-framework-react-cra",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "CTA Framework for React (Create React App)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"author": "Jack Herrington <jherr@pobox.com>",
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@tanstack/cta-engine": "0.
|
|
26
|
+
"@tanstack/cta-engine": "0.19.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^22.13.4",
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
"test": "vitest run"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
+
"@tanstack/react-devtools": "^0.2.2",
|
|
13
14
|
"@tanstack/react-router": "^1.130.2",
|
|
14
|
-
"@tanstack/react-router-devtools": "^1.
|
|
15
|
+
"@tanstack/react-router-devtools": "^1.131.5",
|
|
15
16
|
"react": "^19.0.0",
|
|
16
17
|
"react-dom": "^19.0.0"
|
|
17
18
|
},
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<% if (!fileRouter) { ignoreFile() } %>import { <% if (addOnEnabled.start) { %>
|
|
2
2
|
HeadContent<% } else { %>Outlet<% } %><% if (addOnEnabled.start) { %>, Scripts<% } %>, <% if (addOnEnabled["tanstack-query"]) { %>createRootRouteWithContext<% } else { %>createRootRoute<% } %> } from '@tanstack/react-router'
|
|
3
|
-
import {
|
|
3
|
+
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
|
|
4
|
+
import { TanstackDevtools } from '@tanstack/react-devtools'
|
|
4
5
|
<% if (addOns.length) { %>
|
|
5
6
|
import Header from '../components/Header'
|
|
6
|
-
<% } %><% for(const integration of integrations.filter(i => i.type === 'layout' || i.type === 'provider')) { %>
|
|
7
|
-
import <%= integration.jsName %> from
|
|
7
|
+
<% } %><% for(const integration of integrations.filter(i => i.type === 'layout' || i.type === 'provider' || i.type === 'devtools')) { %>
|
|
8
|
+
import <%= integration.jsName %> from '<%= relativePath(integration.path, true) %>'
|
|
8
9
|
<% } %>
|
|
9
10
|
<% if (addOnEnabled.start) { %>
|
|
10
11
|
import appCss from '../styles.css?url'
|
|
@@ -53,7 +54,18 @@ export const Route = <% if (addOnEnabled["tanstack-query"]) { %>createRootRouteW
|
|
|
53
54
|
<% for(const integration of integrations.filter(i => i.type === 'provider')) { %><<%= integration.jsName %>>
|
|
54
55
|
<% } %><% if (addOns.length) { %><Header />
|
|
55
56
|
<% } %><Outlet />
|
|
56
|
-
|
|
57
|
+
<TanstackDevtools
|
|
58
|
+
config={{
|
|
59
|
+
position: 'bottom-left',
|
|
60
|
+
}}
|
|
61
|
+
plugins={[
|
|
62
|
+
{
|
|
63
|
+
name: 'Tanstack Router',
|
|
64
|
+
render: <TanStackRouterDevtoolsPanel />,
|
|
65
|
+
},
|
|
66
|
+
<% for(const integration of integrations.filter(i => i.type === 'devtools')) { %><%= integration.jsName %>,<% } %>
|
|
67
|
+
]}
|
|
68
|
+
/>
|
|
57
69
|
<% for(const integration of integrations.filter(i => i.type === 'layout')) { %><<%= integration.jsName %> />
|
|
58
70
|
<% } %><% for(const integration of integrations.filter(i => i.type === 'provider').reverse()) { %></<%= integration.jsName %>>
|
|
59
71
|
<% } %>
|
|
@@ -72,7 +84,18 @@ function RootDocument({ children }: { children: React.ReactNode }) {
|
|
|
72
84
|
<% for(const integration of integrations.filter(i => i.type === 'provider')) { %><<%= integration.jsName %>>
|
|
73
85
|
<% } %><% if (addOns.length) { %><Header />
|
|
74
86
|
<% } %>{children}
|
|
75
|
-
<
|
|
87
|
+
<TanstackDevtools
|
|
88
|
+
config={{
|
|
89
|
+
position: 'bottom-left',
|
|
90
|
+
}}
|
|
91
|
+
plugins={[
|
|
92
|
+
{
|
|
93
|
+
name: 'Tanstack Router',
|
|
94
|
+
render: <TanStackRouterDevtoolsPanel />,
|
|
95
|
+
},
|
|
96
|
+
<% for(const integration of integrations.filter(i => i.type === 'devtools')) { %><%= integration.jsName %>,<% } %>
|
|
97
|
+
]}
|
|
98
|
+
/>
|
|
76
99
|
<% for(const integration of integrations.filter(i => i.type === 'layout')) { %><<%= integration.jsName %> />
|
|
77
100
|
<% } %><% for(const integration of integrations.filter(i => i.type === 'provider').reverse()) { %></<%= integration.jsName %>>
|
|
78
101
|
<% } %><Scripts />
|