@tanstack/cta-framework-react-cra 0.40.1 → 0.41.1
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/dist/checksum.js +1 -1
- package/dist/types/checksum.d.ts +1 -1
- package/examples/tanchat/assets/public/example-guitar-flowers.jpg +0 -0
- package/examples/tanchat/assets/public/example-guitar-motherboard.jpg +0 -0
- package/examples/tanchat/assets/public/example-guitar-racing.jpg +0 -0
- package/examples/tanchat/assets/public/example-guitar-steamer-trunk.jpg +0 -0
- package/examples/tanchat/assets/public/example-guitar-superhero.jpg +0 -0
- package/examples/tanchat/assets/public/example-guitar-traveling.jpg +0 -0
- package/examples/tanchat/assets/public/example-guitar-video-games.jpg +0 -0
- package/examples/tanchat/assets/public/example-ukelele-tanstack.jpg +0 -0
- package/examples/tanchat/assets/src/components/example-AIAssistant.tsx +26 -21
- package/examples/tanchat/assets/src/data/example-guitars.ts +16 -6
- package/examples/tanchat/assets/src/lib/example.guitar-tools.ts +40 -0
- package/examples/tanchat/assets/src/routes/demo/api.tanchat.ts.ejs +44 -29
- package/examples/tanchat/assets/src/routes/demo/tanchat.tsx +83 -62
- package/examples/tanchat/package.json +4 -5
- package/package.json +2 -2
- package/src/checksum.ts +1 -1
- package/examples/tanchat/assets/src/utils/demo.tools.ts +0 -54
package/dist/checksum.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// This file is auto-generated. Do not edit manually.
|
|
2
2
|
// Generated from add-ons, examples, hosts, project, and toolchains directories
|
|
3
|
-
export const contentChecksum = '
|
|
3
|
+
export const contentChecksum = 'd7d41081b1bcca2817f39f2d535d21ebd80c3dd2ec04560d0cd3a0663eb04058';
|
package/dist/types/checksum.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const contentChecksum = "
|
|
1
|
+
export declare const contentChecksum = "d7d41081b1bcca2817f39f2d535d21ebd80c3dd2ec04560d0cd3a0663eb04058";
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -5,12 +5,18 @@ import { Store } from '@tanstack/store'
|
|
|
5
5
|
import { Send, X, ChevronRight } from 'lucide-react'
|
|
6
6
|
import { Streamdown } from 'streamdown'
|
|
7
7
|
|
|
8
|
-
import { useChat } from '@ai-
|
|
9
|
-
import {
|
|
8
|
+
import { fetchServerSentEvents, useChat } from '@tanstack/ai-react'
|
|
9
|
+
import { clientTools } from '@tanstack/ai-client'
|
|
10
|
+
import type { UIMessage } from '@tanstack/ai-react'
|
|
10
11
|
|
|
11
12
|
import GuitarRecommendation from './example-GuitarRecommendation'
|
|
13
|
+
import { recommendGuitarToolDef } from '@/lib/example.guitar-tools'
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
const recommendGuitarToolClient = recommendGuitarToolDef.client(({ id }) => ({
|
|
16
|
+
id: +id,
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
const tools = clientTools(recommendGuitarToolClient)
|
|
14
20
|
|
|
15
21
|
export const showAIAssistant = new Store(false)
|
|
16
22
|
|
|
@@ -43,10 +49,10 @@ function Messages({ messages }: { messages: Array<UIMessage> }) {
|
|
|
43
49
|
: 'bg-transparent'
|
|
44
50
|
}`}
|
|
45
51
|
>
|
|
46
|
-
{parts.map((part) => {
|
|
47
|
-
if (part.type === 'text') {
|
|
52
|
+
{parts.map((part, index) => {
|
|
53
|
+
if (part.type === 'text' && part.content) {
|
|
48
54
|
return (
|
|
49
|
-
<div className="flex items-start gap-2 px-4">
|
|
55
|
+
<div key={index} className="flex items-start gap-2 px-4">
|
|
50
56
|
{role === 'assistant' ? (
|
|
51
57
|
<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
58
|
AI
|
|
@@ -57,21 +63,19 @@ function Messages({ messages }: { messages: Array<UIMessage> }) {
|
|
|
57
63
|
</div>
|
|
58
64
|
)}
|
|
59
65
|
<div className="flex-1 min-w-0 text-white prose dark:prose-invert max-w-none prose-sm">
|
|
60
|
-
<Streamdown>{part.
|
|
66
|
+
<Streamdown>{part.content}</Streamdown>
|
|
61
67
|
</div>
|
|
62
68
|
</div>
|
|
63
69
|
)
|
|
64
70
|
}
|
|
65
71
|
if (
|
|
66
|
-
part.type === 'tool-
|
|
67
|
-
part.
|
|
68
|
-
|
|
72
|
+
part.type === 'tool-call' &&
|
|
73
|
+
part.name === 'recommendGuitar' &&
|
|
74
|
+
part.output
|
|
69
75
|
) {
|
|
70
76
|
return (
|
|
71
|
-
<div key={id} className="max-w-[80%] mx-auto">
|
|
72
|
-
<GuitarRecommendation
|
|
73
|
-
id={(part.output as { id: string })?.id}
|
|
74
|
-
/>
|
|
77
|
+
<div key={part.id} className="max-w-[80%] mx-auto">
|
|
78
|
+
<GuitarRecommendation id={String(part.output?.id)} />
|
|
75
79
|
</div>
|
|
76
80
|
)
|
|
77
81
|
}
|
|
@@ -85,9 +89,8 @@ function Messages({ messages }: { messages: Array<UIMessage> }) {
|
|
|
85
89
|
export default function AIAssistant() {
|
|
86
90
|
const isOpen = useStore(showAIAssistant)
|
|
87
91
|
const { messages, sendMessage } = useChat({
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}),
|
|
92
|
+
connection: fetchServerSentEvents('/demo/api/tanchat'),
|
|
93
|
+
tools,
|
|
91
94
|
})
|
|
92
95
|
const [input, setInput] = useState('')
|
|
93
96
|
|
|
@@ -124,8 +127,10 @@ export default function AIAssistant() {
|
|
|
124
127
|
<form
|
|
125
128
|
onSubmit={(e) => {
|
|
126
129
|
e.preventDefault()
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
if (input.trim()) {
|
|
131
|
+
sendMessage(input)
|
|
132
|
+
setInput('')
|
|
133
|
+
}
|
|
129
134
|
}}
|
|
130
135
|
>
|
|
131
136
|
<div className="relative">
|
|
@@ -143,9 +148,9 @@ export default function AIAssistant() {
|
|
|
143
148
|
Math.min(target.scrollHeight, 120) + 'px'
|
|
144
149
|
}}
|
|
145
150
|
onKeyDown={(e) => {
|
|
146
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
151
|
+
if (e.key === 'Enter' && !e.shiftKey && input.trim()) {
|
|
147
152
|
e.preventDefault()
|
|
148
|
-
sendMessage(
|
|
153
|
+
sendMessage(input)
|
|
149
154
|
setInput('')
|
|
150
155
|
}
|
|
151
156
|
}}
|
|
@@ -10,6 +10,16 @@ export interface Guitar {
|
|
|
10
10
|
const guitars: Array<Guitar> = [
|
|
11
11
|
{
|
|
12
12
|
id: 1,
|
|
13
|
+
name: 'TanStack Ukelele',
|
|
14
|
+
image: '/example-ukelele-tanstack.jpg',
|
|
15
|
+
description:
|
|
16
|
+
"Introducing the TanStack Signature Ukulele—a beautifully handcrafted concert ukulele that combines exceptional sound quality with distinctive style. Featuring a warm, resonant koa-wood body with natural grain patterns, this instrument delivers the rich, mellow tones Hawaii is famous for. The exclusive TanStack palm tree inlay on the soundhole adds a unique touch of island flair, while the matching branded headstock makes this a true collector's piece for developers and musicians alike. Whether you're a beginner looking for a quality starter instrument or an experienced player wanting something special, the TanStack Ukulele brings together craftsmanship, character, and that unmistakable tropical spirit.",
|
|
17
|
+
shortDescription:
|
|
18
|
+
'Premium koa-wood ukulele featuring exclusive TanStack branding, perfect for beach vibes and island-inspired melodies.',
|
|
19
|
+
price: 299,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 2,
|
|
13
23
|
name: 'Video Game Guitar',
|
|
14
24
|
image: '/example-guitar-video-games.jpg',
|
|
15
25
|
description:
|
|
@@ -19,7 +29,7 @@ const guitars: Array<Guitar> = [
|
|
|
19
29
|
price: 699,
|
|
20
30
|
},
|
|
21
31
|
{
|
|
22
|
-
id:
|
|
32
|
+
id: 3,
|
|
23
33
|
name: 'Superhero Guitar',
|
|
24
34
|
image: '/example-guitar-superhero.jpg',
|
|
25
35
|
description:
|
|
@@ -29,7 +39,7 @@ const guitars: Array<Guitar> = [
|
|
|
29
39
|
price: 699,
|
|
30
40
|
},
|
|
31
41
|
{
|
|
32
|
-
id:
|
|
42
|
+
id: 4,
|
|
33
43
|
name: 'Motherboard Guitar',
|
|
34
44
|
image: '/example-guitar-motherboard.jpg',
|
|
35
45
|
description:
|
|
@@ -39,7 +49,7 @@ const guitars: Array<Guitar> = [
|
|
|
39
49
|
price: 649,
|
|
40
50
|
},
|
|
41
51
|
{
|
|
42
|
-
id:
|
|
52
|
+
id: 5,
|
|
43
53
|
name: 'Racing Guitar',
|
|
44
54
|
image: '/example-guitar-racing.jpg',
|
|
45
55
|
description:
|
|
@@ -49,7 +59,7 @@ const guitars: Array<Guitar> = [
|
|
|
49
59
|
price: 679,
|
|
50
60
|
},
|
|
51
61
|
{
|
|
52
|
-
id:
|
|
62
|
+
id: 6,
|
|
53
63
|
name: 'Steamer Trunk Guitar',
|
|
54
64
|
image: '/example-guitar-steamer-trunk.jpg',
|
|
55
65
|
description:
|
|
@@ -59,7 +69,7 @@ const guitars: Array<Guitar> = [
|
|
|
59
69
|
price: 629,
|
|
60
70
|
},
|
|
61
71
|
{
|
|
62
|
-
id:
|
|
72
|
+
id: 7,
|
|
63
73
|
name: "Travelin' Man Guitar",
|
|
64
74
|
image: '/example-guitar-traveling.jpg',
|
|
65
75
|
description:
|
|
@@ -69,7 +79,7 @@ const guitars: Array<Guitar> = [
|
|
|
69
79
|
price: 499,
|
|
70
80
|
},
|
|
71
81
|
{
|
|
72
|
-
id:
|
|
82
|
+
id: 8,
|
|
73
83
|
name: 'Flowerly Love Guitar',
|
|
74
84
|
image: '/example-guitar-flowers.jpg',
|
|
75
85
|
description:
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { toolDefinition } from '@tanstack/ai'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import guitars from '@/data/example-guitars'
|
|
4
|
+
|
|
5
|
+
// Tool definition for getting guitars
|
|
6
|
+
export const getGuitarsToolDef = toolDefinition({
|
|
7
|
+
name: 'getGuitars',
|
|
8
|
+
description: 'Get all products from the database',
|
|
9
|
+
inputSchema: z.object({}),
|
|
10
|
+
outputSchema: z.array(
|
|
11
|
+
z.object({
|
|
12
|
+
id: z.number(),
|
|
13
|
+
name: z.string(),
|
|
14
|
+
image: z.string(),
|
|
15
|
+
description: z.string(),
|
|
16
|
+
shortDescription: z.string(),
|
|
17
|
+
price: z.number(),
|
|
18
|
+
}),
|
|
19
|
+
),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// Server implementation
|
|
23
|
+
export const getGuitars = getGuitarsToolDef.server(() => guitars)
|
|
24
|
+
|
|
25
|
+
// Tool definition for guitar recommendation
|
|
26
|
+
export const recommendGuitarToolDef = toolDefinition({
|
|
27
|
+
name: 'recommendGuitar',
|
|
28
|
+
description:
|
|
29
|
+
'REQUIRED tool to display a guitar recommendation to the user. This tool MUST be used whenever recommending a guitar - do NOT write recommendations yourself. This displays the guitar in a special appealing format with a buy button.',
|
|
30
|
+
inputSchema: z.object({
|
|
31
|
+
id: z
|
|
32
|
+
.union([z.string(), z.number()])
|
|
33
|
+
.describe(
|
|
34
|
+
'The ID of the guitar to recommend (from the getGuitars results)',
|
|
35
|
+
),
|
|
36
|
+
}),
|
|
37
|
+
outputSchema: z.object({
|
|
38
|
+
id: z.number(),
|
|
39
|
+
}),
|
|
40
|
+
})
|
|
@@ -1,48 +1,63 @@
|
|
|
1
1
|
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
<% } %>import { convertToModelMessages, stepCountIs, streamText } from 'ai'
|
|
2
|
+
import { chat, maxIterations, toStreamResponse } from '@tanstack/ai'
|
|
3
|
+
import { anthropic } from '@tanstack/ai-anthropic'
|
|
5
4
|
|
|
6
|
-
import
|
|
5
|
+
import { getGuitars, recommendGuitarToolDef } from '@/lib/example.guitar-tools'
|
|
7
6
|
|
|
8
7
|
const SYSTEM_PROMPT = `You are a helpful assistant for a store that sells guitars.
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
CRITICAL INSTRUCTIONS - YOU MUST FOLLOW THIS EXACT WORKFLOW:
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
When a user asks for a guitar recommendation:
|
|
12
|
+
1. FIRST: Use the getGuitars tool (no parameters needed)
|
|
13
|
+
2. SECOND: Use the recommendGuitar tool with the ID of the guitar you want to recommend
|
|
14
|
+
3. NEVER write a recommendation directly - ALWAYS use the recommendGuitar tool
|
|
15
|
+
|
|
16
|
+
IMPORTANT:
|
|
17
|
+
- The recommendGuitar tool will display the guitar in a special, appealing format
|
|
18
|
+
- You MUST use recommendGuitar for ANY guitar recommendation
|
|
19
|
+
- ONLY recommend guitars from our inventory (use getGuitars first)
|
|
20
|
+
- The recommendGuitar tool has a buy button - this is how customers purchase
|
|
21
|
+
- Do NOT describe the guitar yourself - let the recommendGuitar tool do it
|
|
14
22
|
`
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
? `${process.env.ANTHROPIC_BASE_URL}/v1`
|
|
18
|
-
: undefined,
|
|
19
|
-
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
20
|
-
headers: {
|
|
21
|
-
'user-agent': 'anthropic/',
|
|
22
|
-
},
|
|
23
|
-
})
|
|
24
|
-
<% } %>
|
|
25
|
-
export const Route = createFileRoute('/api/demo-chat')({
|
|
23
|
+
|
|
24
|
+
export const Route = createFileRoute('/demo/api/tanchat')({
|
|
26
25
|
server: {
|
|
27
26
|
handlers: {
|
|
28
27
|
POST: async ({ request }) => {
|
|
28
|
+
// Capture request signal before reading body (it may be aborted after body is consumed)
|
|
29
|
+
const requestSignal = request.signal
|
|
30
|
+
|
|
31
|
+
// If request is already aborted, return early
|
|
32
|
+
if (requestSignal.aborted) {
|
|
33
|
+
return new Response(null, { status: 499 }) // 499 = Client Closed Request
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const abortController = new AbortController()
|
|
37
|
+
|
|
29
38
|
try {
|
|
30
39
|
const { messages } = await request.json()
|
|
31
40
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
const stream = chat({
|
|
42
|
+
adapter: anthropic(),
|
|
43
|
+
model: 'claude-haiku-4-5',
|
|
44
|
+
tools: [
|
|
45
|
+
getGuitars, // Server tool
|
|
46
|
+
recommendGuitarToolDef, // No server execute - client will handle
|
|
47
|
+
],
|
|
48
|
+
systemPrompts: [SYSTEM_PROMPT],
|
|
49
|
+
agentLoopStrategy: maxIterations(5),
|
|
50
|
+
messages,
|
|
51
|
+
abortController,
|
|
41
52
|
})
|
|
42
53
|
|
|
43
|
-
return
|
|
44
|
-
} catch (error) {
|
|
54
|
+
return toStreamResponse(stream, { abortController })
|
|
55
|
+
} catch (error: any) {
|
|
45
56
|
console.error('Chat API error:', error)
|
|
57
|
+
// If request was aborted, return early (don't send error response)
|
|
58
|
+
if (error.name === 'AbortError' || abortController.signal.aborted) {
|
|
59
|
+
return new Response(null, { status: 499 }) // 499 = Client Closed Request
|
|
60
|
+
}
|
|
46
61
|
return new Response(
|
|
47
62
|
JSON.stringify({ error: 'Failed to process chat request' }),
|
|
48
63
|
{
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react'
|
|
2
2
|
import { createFileRoute } from '@tanstack/react-router'
|
|
3
|
-
import { Send } from 'lucide-react'
|
|
3
|
+
import { Send, Square } from 'lucide-react'
|
|
4
4
|
import { Streamdown } from 'streamdown'
|
|
5
|
-
import { useChat } from '@ai-
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
import type { UIMessage } from 'ai'
|
|
5
|
+
import { fetchServerSentEvents, useChat } from '@tanstack/ai-react'
|
|
6
|
+
import { clientTools } from '@tanstack/ai-client'
|
|
7
|
+
import type { UIMessage } from '@tanstack/ai-react'
|
|
9
8
|
|
|
10
9
|
import GuitarRecommendation from '@/components/example-GuitarRecommendation'
|
|
10
|
+
import { recommendGuitarToolDef } from '@/lib/example.guitar-tools'
|
|
11
11
|
|
|
12
12
|
import './tanchat.css'
|
|
13
13
|
|
|
14
|
+
const recommendGuitarToolClient = recommendGuitarToolDef.client(({ id }) => ({
|
|
15
|
+
id: +id,
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
const tools = clientTools(recommendGuitarToolClient)
|
|
19
|
+
|
|
14
20
|
function InitalLayout({ children }: { children: React.ReactNode }) {
|
|
15
21
|
return (
|
|
16
22
|
<div className="flex-1 flex items-center justify-center px-4">
|
|
@@ -56,17 +62,17 @@ function Messages({ messages }: { messages: Array<UIMessage> }) {
|
|
|
56
62
|
className="flex-1 overflow-y-auto pb-4 min-h-0"
|
|
57
63
|
>
|
|
58
64
|
<div className="max-w-3xl mx-auto w-full px-4">
|
|
59
|
-
{messages.map((
|
|
65
|
+
{messages.map((message) => (
|
|
60
66
|
<div
|
|
61
|
-
key={id}
|
|
67
|
+
key={message.id}
|
|
62
68
|
className={`p-4 ${
|
|
63
|
-
role === 'assistant'
|
|
69
|
+
message.role === 'assistant'
|
|
64
70
|
? 'bg-gradient-to-r from-orange-500/5 to-red-600/5'
|
|
65
71
|
: 'bg-transparent'
|
|
66
72
|
}`}
|
|
67
73
|
>
|
|
68
74
|
<div className="flex items-start gap-4 max-w-3xl mx-auto w-full">
|
|
69
|
-
{role === 'assistant' ? (
|
|
75
|
+
{message.role === 'assistant' ? (
|
|
70
76
|
<div className="w-8 h-8 rounded-lg bg-gradient-to-r from-orange-500 to-red-600 mt-2 flex items-center justify-center text-sm font-medium text-white flex-shrink-0">
|
|
71
77
|
AI
|
|
72
78
|
</div>
|
|
@@ -75,31 +81,31 @@ function Messages({ messages }: { messages: Array<UIMessage> }) {
|
|
|
75
81
|
Y
|
|
76
82
|
</div>
|
|
77
83
|
)}
|
|
78
|
-
<div className="flex-1">
|
|
79
|
-
{parts.map((part, index) => {
|
|
80
|
-
if (part.type === 'text') {
|
|
84
|
+
<div className="flex-1 min-w-0">
|
|
85
|
+
{message.parts.map((part, index) => {
|
|
86
|
+
if (part.type === 'text' && part.content) {
|
|
81
87
|
return (
|
|
82
88
|
<div
|
|
83
89
|
className="flex-1 min-w-0 prose dark:prose-invert max-w-none prose-sm"
|
|
84
90
|
key={index}
|
|
85
91
|
>
|
|
86
|
-
<Streamdown>{part.
|
|
92
|
+
<Streamdown>{part.content}</Streamdown>
|
|
87
93
|
</div>
|
|
88
94
|
)
|
|
89
95
|
}
|
|
96
|
+
// Guitar recommendation card
|
|
90
97
|
if (
|
|
91
|
-
part.type === 'tool-
|
|
92
|
-
part.
|
|
93
|
-
|
|
98
|
+
part.type === 'tool-call' &&
|
|
99
|
+
part.name === 'recommendGuitar' &&
|
|
100
|
+
part.output
|
|
94
101
|
) {
|
|
95
102
|
return (
|
|
96
|
-
<div key={
|
|
97
|
-
<GuitarRecommendation
|
|
98
|
-
id={(part.output as { id: string })?.id}
|
|
99
|
-
/>
|
|
103
|
+
<div key={part.id} className="max-w-[80%] mx-auto">
|
|
104
|
+
<GuitarRecommendation id={String(part.output?.id)} />
|
|
100
105
|
</div>
|
|
101
106
|
)
|
|
102
107
|
}
|
|
108
|
+
return null
|
|
103
109
|
})}
|
|
104
110
|
</div>
|
|
105
111
|
</div>
|
|
@@ -111,10 +117,9 @@ function Messages({ messages }: { messages: Array<UIMessage> }) {
|
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
function ChatPage() {
|
|
114
|
-
const { messages, sendMessage } = useChat({
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}),
|
|
120
|
+
const { messages, sendMessage, isLoading, stop } = useChat({
|
|
121
|
+
connection: fetchServerSentEvents('/demo/api/tanchat'),
|
|
122
|
+
tools,
|
|
118
123
|
})
|
|
119
124
|
const [input, setInput] = useState('')
|
|
120
125
|
|
|
@@ -126,44 +131,60 @@ function ChatPage() {
|
|
|
126
131
|
<Messages messages={messages} />
|
|
127
132
|
|
|
128
133
|
<Layout>
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
134
|
+
<div className="space-y-3">
|
|
135
|
+
{isLoading && (
|
|
136
|
+
<div className="flex items-center justify-center">
|
|
137
|
+
<button
|
|
138
|
+
onClick={stop}
|
|
139
|
+
className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg text-sm font-medium transition-colors flex items-center gap-2"
|
|
140
|
+
>
|
|
141
|
+
<Square className="w-4 h-4 fill-current" />
|
|
142
|
+
Stop
|
|
143
|
+
</button>
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
<form
|
|
147
|
+
onSubmit={(e) => {
|
|
148
|
+
e.preventDefault()
|
|
149
|
+
if (input.trim()) {
|
|
150
|
+
sendMessage(input)
|
|
151
|
+
setInput('')
|
|
152
|
+
}
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<div className="relative max-w-xl mx-auto">
|
|
156
|
+
<textarea
|
|
157
|
+
value={input}
|
|
158
|
+
onChange={(e) => setInput(e.target.value)}
|
|
159
|
+
placeholder="Type something clever..."
|
|
160
|
+
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"
|
|
161
|
+
rows={1}
|
|
162
|
+
style={{ minHeight: '44px', maxHeight: '200px' }}
|
|
163
|
+
disabled={isLoading}
|
|
164
|
+
onInput={(e) => {
|
|
165
|
+
const target = e.target as HTMLTextAreaElement
|
|
166
|
+
target.style.height = 'auto'
|
|
167
|
+
target.style.height =
|
|
168
|
+
Math.min(target.scrollHeight, 200) + 'px'
|
|
169
|
+
}}
|
|
170
|
+
onKeyDown={(e) => {
|
|
171
|
+
if (e.key === 'Enter' && !e.shiftKey && input.trim()) {
|
|
172
|
+
e.preventDefault()
|
|
173
|
+
sendMessage(input)
|
|
174
|
+
setInput('')
|
|
175
|
+
}
|
|
176
|
+
}}
|
|
177
|
+
/>
|
|
178
|
+
<button
|
|
179
|
+
type="submit"
|
|
180
|
+
disabled={!input.trim() || isLoading}
|
|
181
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-orange-500 hover:text-orange-400 disabled:text-gray-500 transition-colors focus:outline-none"
|
|
182
|
+
>
|
|
183
|
+
<Send className="w-4 h-4" />
|
|
184
|
+
</button>
|
|
185
|
+
</div>
|
|
186
|
+
</form>
|
|
187
|
+
</div>
|
|
167
188
|
</Layout>
|
|
168
189
|
</div>
|
|
169
190
|
</div>
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
-
"@ai
|
|
4
|
-
"@ai-
|
|
5
|
-
"@ai-
|
|
6
|
-
"@
|
|
7
|
-
"ai": "^5.0.8",
|
|
3
|
+
"@tanstack/ai": "^0.0.1",
|
|
4
|
+
"@tanstack/ai-anthropic": "^0.0.1",
|
|
5
|
+
"@tanstack/ai-client": "^0.0.1",
|
|
6
|
+
"@tanstack/ai-react": "^0.0.1",
|
|
8
7
|
"highlight.js": "^11.11.1",
|
|
9
8
|
"streamdown": "^1.6.5",
|
|
10
9
|
"lucide-react": "^0.544.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.41.1",
|
|
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.41.1"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^24.6.0",
|
package/src/checksum.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// This file is auto-generated. Do not edit manually.
|
|
2
2
|
// Generated from add-ons, examples, hosts, project, and toolchains directories
|
|
3
|
-
export const contentChecksum = '
|
|
3
|
+
export const contentChecksum = 'd7d41081b1bcca2817f39f2d535d21ebd80c3dd2ec04560d0cd3a0663eb04058'
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { tool } from 'ai'
|
|
2
|
-
// import { experimental_createMCPClient } from '@ai-sdk/mcp'
|
|
3
|
-
// import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4
|
-
import { z } from 'zod'
|
|
5
|
-
|
|
6
|
-
import guitars from '../data/example-guitars'
|
|
7
|
-
|
|
8
|
-
// Example of using an SSE MCP server
|
|
9
|
-
// const mcpClient = await experimental_createMCPClient({
|
|
10
|
-
// transport: {
|
|
11
|
-
// type: "sse",
|
|
12
|
-
// url: "http://localhost:8081/sse",
|
|
13
|
-
// },
|
|
14
|
-
// name: "Demo Service",
|
|
15
|
-
// });
|
|
16
|
-
|
|
17
|
-
// Example of using an STDIO MCP server
|
|
18
|
-
// const mcpClient = await experimental_createMCPClient({
|
|
19
|
-
// transport: new StdioClientTransport({
|
|
20
|
-
// command: "node",
|
|
21
|
-
// args: [
|
|
22
|
-
// "stdio-server.js",
|
|
23
|
-
// ],
|
|
24
|
-
// }),
|
|
25
|
-
// });
|
|
26
|
-
|
|
27
|
-
const getGuitars = tool({
|
|
28
|
-
description: 'Get all products from the database',
|
|
29
|
-
inputSchema: z.object({}),
|
|
30
|
-
execute: async () => {
|
|
31
|
-
return Promise.resolve(guitars)
|
|
32
|
-
},
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
const recommendGuitar = tool({
|
|
36
|
-
description: 'Use this tool to recommend a guitar to the user',
|
|
37
|
-
inputSchema: z.object({
|
|
38
|
-
id: z.string().describe('The id of the guitar to recommend'),
|
|
39
|
-
}),
|
|
40
|
-
execute: async ({ id }) => {
|
|
41
|
-
return {
|
|
42
|
-
id,
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
export default async function getTools() {
|
|
48
|
-
// const mcpTools = await mcpClient.tools()
|
|
49
|
-
return {
|
|
50
|
-
// ...mcpTools,
|
|
51
|
-
getGuitars,
|
|
52
|
-
recommendGuitar,
|
|
53
|
-
}
|
|
54
|
-
}
|