@tanstack/create 0.49.2 → 0.49.3
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/CHANGELOG.md +6 -0
- package/dist/frameworks/react/add-ons/ai/assets/src/data/demo-guitars.ts +93 -0
- package/dist/frameworks/react/add-ons/ai/assets/src/hooks/demo-useAudioRecorder.ts +85 -0
- package/dist/frameworks/react/add-ons/ai/assets/src/hooks/demo-useTTS.ts +78 -0
- package/dist/frameworks/react/add-ons/ai/assets/src/lib/demo-ai-hook.ts +22 -0
- package/dist/frameworks/react/add-ons/ai/assets/src/lib/demo-guitar-tools.ts +40 -0
- package/dist/frameworks/react/add-ons/ai/assets/src/routes/demo/api.ai.chat.ts +99 -0
- package/dist/frameworks/react/add-ons/ai/assets/src/routes/demo/api.ai.image.ts +72 -0
- package/dist/frameworks/react/add-ons/ai/assets/src/routes/demo/api.ai.structured.ts +136 -0
- package/dist/frameworks/react/add-ons/ai/assets/src/routes/demo/api.ai.transcription.ts +89 -0
- package/dist/frameworks/react/add-ons/ai/assets/src/routes/demo/api.ai.tts.ts +81 -0
- package/dist/frameworks/react/add-ons/better-auth/assets/src/lib/auth-client.ts +3 -0
- package/dist/frameworks/react/add-ons/better-auth/assets/src/lib/auth.ts +9 -0
- package/dist/frameworks/react/add-ons/better-auth/assets/src/routes/api/auth/$.ts +11 -0
- package/dist/frameworks/react/add-ons/convex/assets/convex/schema.ts +14 -0
- package/dist/frameworks/react/add-ons/convex/assets/convex/todos.ts +43 -0
- package/dist/frameworks/react/add-ons/db/assets/src/db-collections/index.ts +20 -0
- package/dist/frameworks/react/add-ons/db/assets/src/hooks/demo.useChat.ts +62 -0
- package/dist/frameworks/react/add-ons/db/assets/src/routes/demo/db-chat-api.ts +83 -0
- package/dist/frameworks/react/add-ons/form/assets/src/hooks/demo.form-context.ts +4 -0
- package/dist/frameworks/react/add-ons/form/assets/src/hooks/demo.form.ts +22 -0
- package/dist/frameworks/react/add-ons/mcp/assets/src/mcp-todos.ts +51 -0
- package/dist/frameworks/react/add-ons/mcp/assets/src/routes/demo/api.mcp-todos.ts +37 -0
- package/dist/frameworks/react/add-ons/mcp/assets/src/routes/mcp.ts +53 -0
- package/dist/frameworks/react/add-ons/mcp/assets/src/utils/mcp-handler.ts +61 -0
- package/dist/frameworks/react/add-ons/neon/assets/neon-vite-plugin.ts +10 -0
- package/dist/frameworks/react/add-ons/neon/assets/src/db.ts +13 -0
- package/dist/frameworks/react/add-ons/oRPC/assets/src/orpc/client.ts +29 -0
- package/dist/frameworks/react/add-ons/oRPC/assets/src/orpc/router/index.ts +6 -0
- package/dist/frameworks/react/add-ons/oRPC/assets/src/orpc/router/todos.ts +20 -0
- package/dist/frameworks/react/add-ons/oRPC/assets/src/orpc/schema.ts +6 -0
- package/dist/frameworks/react/add-ons/oRPC/assets/src/polyfill.ts +21 -0
- package/dist/frameworks/react/add-ons/oRPC/assets/src/routes/api.$.ts +77 -0
- package/dist/frameworks/react/add-ons/oRPC/assets/src/routes/api.rpc.$.ts +29 -0
- package/dist/frameworks/react/add-ons/shadcn/assets/src/lib/utils.ts +6 -0
- package/dist/frameworks/react/add-ons/start/assets/src/data/demo.punk-songs.ts +13 -0
- package/dist/frameworks/react/add-ons/start/assets/src/routes/demo/api.names.ts +10 -0
- package/dist/frameworks/react/add-ons/store/assets/src/lib/demo-store.ts +13 -0
- package/dist/frameworks/react/add-ons/storybook/assets/_dot_storybook/main.ts +17 -0
- package/dist/frameworks/react/add-ons/storybook/assets/_dot_storybook/preview.ts +15 -0
- package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/button.stories.ts +67 -0
- package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/index.ts +14 -0
- package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/input.stories.ts +43 -0
- package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/radio-group.stories.ts +53 -0
- package/dist/frameworks/react/add-ons/storybook/assets/src/components/storybook/slider.stories.ts +55 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/lib/strapiClient.ts +7 -0
- package/dist/frameworks/react/add-ons/t3env/assets/src/env.ts +39 -0
- package/dist/frameworks/react/add-ons/tRPC/assets/src/integrations/trpc/init.ts +9 -0
- package/dist/frameworks/react/add-ons/tRPC/assets/src/integrations/trpc/react.ts +4 -0
- package/dist/frameworks/react/add-ons/tRPC/assets/src/integrations/trpc/router.ts +27 -0
- package/dist/frameworks/react/add-ons/table/assets/src/data/demo-table-data.ts +50 -0
- package/dist/frameworks/react/examples/events/assets/content-collections.ts +56 -0
- package/dist/frameworks/react/examples/events/assets/src/lib/conference-ai-hook.ts +26 -0
- package/dist/frameworks/react/examples/events/assets/src/lib/conference-tools.ts +210 -0
- package/dist/frameworks/react/examples/events/assets/src/lib/utils.ts +6 -0
- package/dist/frameworks/react/examples/events/assets/src/routes/api.remy-chat.ts +121 -0
- package/dist/frameworks/react/examples/resume/assets/content-collections.ts +36 -0
- package/dist/frameworks/react/examples/resume/assets/src/lib/resume-ai-hook.ts +21 -0
- package/dist/frameworks/react/examples/resume/assets/src/lib/resume-tools.ts +165 -0
- package/dist/frameworks/react/examples/resume/assets/src/lib/utils.ts +6 -0
- package/dist/frameworks/react/examples/resume/assets/src/routes/api.resume-chat.ts +110 -0
- package/dist/frameworks/solid/add-ons/better-auth/assets/src/lib/auth-client.ts +3 -0
- package/dist/frameworks/solid/add-ons/better-auth/assets/src/lib/auth.ts +9 -0
- package/dist/frameworks/solid/add-ons/better-auth/assets/src/routes/api/auth/$.ts +11 -0
- package/dist/frameworks/solid/add-ons/convex/assets/convex/schema.ts +14 -0
- package/dist/frameworks/solid/add-ons/convex/assets/convex/todos.ts +43 -0
- package/dist/frameworks/solid/add-ons/solid-ui/assets/src/lib/utils.ts +6 -0
- package/dist/frameworks/solid/add-ons/store/assets/src/lib/demo-store.ts +13 -0
- package/dist/frameworks/solid/add-ons/strapi/assets/src/lib/strapiClient.ts +7 -0
- package/dist/frameworks/solid/add-ons/t3env/assets/src/env.ts +39 -0
- package/dist/frameworks/solid/examples/tanchat/assets/ai-streaming-server/src/index.ts +102 -0
- package/dist/frameworks/solid/examples/tanchat/assets/src/lib/demo-store.ts +13 -0
- package/dist/frameworks/solid/examples/tanchat/assets/src/store/demo.hooks.ts +17 -0
- package/dist/frameworks/solid/examples/tanchat/assets/src/store/demo.store.ts +133 -0
- package/package.json +2 -2
- package/tests/copy-assets.test.ts +53 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { defineCollection, defineConfig } from '@content-collections/core'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
const speakers = defineCollection({
|
|
5
|
+
name: 'speakers',
|
|
6
|
+
directory: 'content/speakers',
|
|
7
|
+
include: '**/*.md',
|
|
8
|
+
schema: z.object({
|
|
9
|
+
name: z.string(),
|
|
10
|
+
title: z.string(),
|
|
11
|
+
specialty: z.string(),
|
|
12
|
+
restaurant: z.string(),
|
|
13
|
+
location: z.string(),
|
|
14
|
+
headshot: z.string(),
|
|
15
|
+
awards: z.array(z.string()).optional(),
|
|
16
|
+
content: z.string(),
|
|
17
|
+
}),
|
|
18
|
+
transform: async (doc) => {
|
|
19
|
+
return {
|
|
20
|
+
...doc,
|
|
21
|
+
slug: doc.name
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.replace(/[^\w-]+/g, '-')
|
|
24
|
+
.replace(/-+/g, '-')
|
|
25
|
+
.replace(/^-|-$/g, ''),
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const talks = defineCollection({
|
|
31
|
+
name: 'talks',
|
|
32
|
+
directory: 'content/talks',
|
|
33
|
+
include: '**/*.md',
|
|
34
|
+
schema: z.object({
|
|
35
|
+
title: z.string(),
|
|
36
|
+
speaker: z.string(),
|
|
37
|
+
duration: z.string(),
|
|
38
|
+
image: z.string(),
|
|
39
|
+
topics: z.array(z.string()),
|
|
40
|
+
content: z.string(),
|
|
41
|
+
}),
|
|
42
|
+
transform: async (doc) => {
|
|
43
|
+
return {
|
|
44
|
+
...doc,
|
|
45
|
+
slug: doc.title
|
|
46
|
+
.toLowerCase()
|
|
47
|
+
.replace(/[^\w-]+/g, '-')
|
|
48
|
+
.replace(/-+/g, '-')
|
|
49
|
+
.replace(/^-|-$/g, ''),
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
export default defineConfig({
|
|
55
|
+
collections: [speakers, talks],
|
|
56
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fetchServerSentEvents,
|
|
3
|
+
useChat,
|
|
4
|
+
createChatClientOptions,
|
|
5
|
+
} from '@tanstack/ai-react'
|
|
6
|
+
import type { InferChatMessages } from '@tanstack/ai-react'
|
|
7
|
+
|
|
8
|
+
// Default chat options for type inference
|
|
9
|
+
const defaultChatOptions = createChatClientOptions({
|
|
10
|
+
connection: fetchServerSentEvents('/api/remy-chat'),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export type ConferenceChatMessages = InferChatMessages<typeof defaultChatOptions>
|
|
14
|
+
|
|
15
|
+
export const useConferenceChat = (speakerSlug?: string, talkSlug?: string) => {
|
|
16
|
+
const chatOptions = createChatClientOptions({
|
|
17
|
+
connection: fetchServerSentEvents('/api/remy-chat', {
|
|
18
|
+
body: {
|
|
19
|
+
speakerSlug,
|
|
20
|
+
talkSlug,
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return useChat(chatOptions)
|
|
26
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { toolDefinition } from '@tanstack/ai'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
import { allSpeakers, allTalks } from 'content-collections'
|
|
5
|
+
|
|
6
|
+
// Tool definition for getting a speaker by slug
|
|
7
|
+
export const getSpeakerBySlugToolDef = toolDefinition({
|
|
8
|
+
name: 'getSpeakerBySlug',
|
|
9
|
+
description:
|
|
10
|
+
'Get the full profile and bio of a specific speaker. Use this when asked about a particular speaker.',
|
|
11
|
+
inputSchema: z.object({
|
|
12
|
+
slug: z.string().describe('The slug of the speaker'),
|
|
13
|
+
}),
|
|
14
|
+
outputSchema: z.object({
|
|
15
|
+
name: z.string(),
|
|
16
|
+
title: z.string(),
|
|
17
|
+
specialty: z.string(),
|
|
18
|
+
restaurant: z.string(),
|
|
19
|
+
location: z.string(),
|
|
20
|
+
bio: z.string(),
|
|
21
|
+
awards: z.array(z.string()),
|
|
22
|
+
}),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// Server implementation
|
|
26
|
+
export const getSpeakerBySlug = getSpeakerBySlugToolDef.server(({ slug }) => {
|
|
27
|
+
const speaker = allSpeakers.find((s) => s.slug === slug)
|
|
28
|
+
if (!speaker) {
|
|
29
|
+
return {
|
|
30
|
+
name: 'Speaker not found',
|
|
31
|
+
title: '',
|
|
32
|
+
specialty: '',
|
|
33
|
+
restaurant: '',
|
|
34
|
+
location: '',
|
|
35
|
+
bio: 'The requested speaker was not found.',
|
|
36
|
+
awards: [],
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
name: speaker.name,
|
|
41
|
+
title: speaker.title,
|
|
42
|
+
specialty: speaker.specialty,
|
|
43
|
+
restaurant: speaker.restaurant,
|
|
44
|
+
location: speaker.location,
|
|
45
|
+
bio: speaker.content,
|
|
46
|
+
awards: speaker.awards || [],
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// Tool definition for getting a talk by slug
|
|
51
|
+
export const getTalkBySlugToolDef = toolDefinition({
|
|
52
|
+
name: 'getTalkBySlug',
|
|
53
|
+
description:
|
|
54
|
+
'Get the full details of a specific session/talk. Use this when asked about a particular session.',
|
|
55
|
+
inputSchema: z.object({
|
|
56
|
+
slug: z.string().describe('The slug of the talk'),
|
|
57
|
+
}),
|
|
58
|
+
outputSchema: z.object({
|
|
59
|
+
title: z.string(),
|
|
60
|
+
speaker: z.string(),
|
|
61
|
+
duration: z.string(),
|
|
62
|
+
topics: z.array(z.string()),
|
|
63
|
+
description: z.string(),
|
|
64
|
+
}),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// Server implementation
|
|
68
|
+
export const getTalkBySlug = getTalkBySlugToolDef.server(({ slug }) => {
|
|
69
|
+
const talk = allTalks.find((t) => t.slug === slug)
|
|
70
|
+
if (!talk) {
|
|
71
|
+
return {
|
|
72
|
+
title: 'Session not found',
|
|
73
|
+
speaker: '',
|
|
74
|
+
duration: '',
|
|
75
|
+
topics: [],
|
|
76
|
+
description: 'The requested session was not found.',
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
title: talk.title,
|
|
81
|
+
speaker: talk.speaker,
|
|
82
|
+
duration: talk.duration,
|
|
83
|
+
topics: talk.topics,
|
|
84
|
+
description: talk.content,
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Tool definition for listing all speakers
|
|
89
|
+
export const getAllSpeakersToolDef = toolDefinition({
|
|
90
|
+
name: 'getAllSpeakers',
|
|
91
|
+
description:
|
|
92
|
+
'Get a list of all speakers at the conference with their names, specialties, and restaurants.',
|
|
93
|
+
inputSchema: z.object({}),
|
|
94
|
+
outputSchema: z.array(
|
|
95
|
+
z.object({
|
|
96
|
+
slug: z.string(),
|
|
97
|
+
name: z.string(),
|
|
98
|
+
specialty: z.string(),
|
|
99
|
+
restaurant: z.string(),
|
|
100
|
+
location: z.string(),
|
|
101
|
+
}),
|
|
102
|
+
),
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// Server implementation
|
|
106
|
+
export const getAllSpeakers = getAllSpeakersToolDef.server(() => {
|
|
107
|
+
return allSpeakers.map((speaker) => ({
|
|
108
|
+
slug: speaker.slug,
|
|
109
|
+
name: speaker.name,
|
|
110
|
+
specialty: speaker.specialty,
|
|
111
|
+
restaurant: speaker.restaurant,
|
|
112
|
+
location: speaker.location,
|
|
113
|
+
}))
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// Tool definition for listing all talks
|
|
117
|
+
export const getAllTalksToolDef = toolDefinition({
|
|
118
|
+
name: 'getAllTalks',
|
|
119
|
+
description:
|
|
120
|
+
'Get a list of all sessions/talks at the conference with their titles, speakers, and topics.',
|
|
121
|
+
inputSchema: z.object({}),
|
|
122
|
+
outputSchema: z.array(
|
|
123
|
+
z.object({
|
|
124
|
+
slug: z.string(),
|
|
125
|
+
title: z.string(),
|
|
126
|
+
speaker: z.string(),
|
|
127
|
+
duration: z.string(),
|
|
128
|
+
topics: z.array(z.string()),
|
|
129
|
+
}),
|
|
130
|
+
),
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// Server implementation
|
|
134
|
+
export const getAllTalks = getAllTalksToolDef.server(() => {
|
|
135
|
+
return allTalks.map((talk) => ({
|
|
136
|
+
slug: talk.slug,
|
|
137
|
+
title: talk.title,
|
|
138
|
+
speaker: talk.speaker,
|
|
139
|
+
duration: talk.duration,
|
|
140
|
+
topics: talk.topics,
|
|
141
|
+
}))
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
// Tool definition for searching conference content
|
|
145
|
+
export const searchConferenceToolDef = toolDefinition({
|
|
146
|
+
name: 'searchConference',
|
|
147
|
+
description:
|
|
148
|
+
'Search for speakers or sessions by keyword. Use this to find content matching user queries about topics, techniques, or names.',
|
|
149
|
+
inputSchema: z.object({
|
|
150
|
+
query: z.string().describe('The search query'),
|
|
151
|
+
}),
|
|
152
|
+
outputSchema: z.object({
|
|
153
|
+
speakers: z.array(
|
|
154
|
+
z.object({
|
|
155
|
+
slug: z.string(),
|
|
156
|
+
name: z.string(),
|
|
157
|
+
specialty: z.string(),
|
|
158
|
+
restaurant: z.string(),
|
|
159
|
+
}),
|
|
160
|
+
),
|
|
161
|
+
talks: z.array(
|
|
162
|
+
z.object({
|
|
163
|
+
slug: z.string(),
|
|
164
|
+
title: z.string(),
|
|
165
|
+
speaker: z.string(),
|
|
166
|
+
topics: z.array(z.string()),
|
|
167
|
+
}),
|
|
168
|
+
),
|
|
169
|
+
}),
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// Server implementation
|
|
173
|
+
export const searchConference = searchConferenceToolDef.server(({ query }) => {
|
|
174
|
+
const queryLower = query.toLowerCase()
|
|
175
|
+
|
|
176
|
+
const matchingSpeakers = allSpeakers
|
|
177
|
+
.filter(
|
|
178
|
+
(speaker) =>
|
|
179
|
+
speaker.name.toLowerCase().includes(queryLower) ||
|
|
180
|
+
speaker.specialty.toLowerCase().includes(queryLower) ||
|
|
181
|
+
speaker.restaurant.toLowerCase().includes(queryLower) ||
|
|
182
|
+
speaker.content.toLowerCase().includes(queryLower),
|
|
183
|
+
)
|
|
184
|
+
.map((speaker) => ({
|
|
185
|
+
slug: speaker.slug,
|
|
186
|
+
name: speaker.name,
|
|
187
|
+
specialty: speaker.specialty,
|
|
188
|
+
restaurant: speaker.restaurant,
|
|
189
|
+
}))
|
|
190
|
+
|
|
191
|
+
const matchingTalks = allTalks
|
|
192
|
+
.filter(
|
|
193
|
+
(talk) =>
|
|
194
|
+
talk.title.toLowerCase().includes(queryLower) ||
|
|
195
|
+
talk.speaker.toLowerCase().includes(queryLower) ||
|
|
196
|
+
talk.topics.some((topic) => topic.toLowerCase().includes(queryLower)) ||
|
|
197
|
+
talk.content.toLowerCase().includes(queryLower),
|
|
198
|
+
)
|
|
199
|
+
.map((talk) => ({
|
|
200
|
+
slug: talk.slug,
|
|
201
|
+
title: talk.title,
|
|
202
|
+
speaker: talk.speaker,
|
|
203
|
+
topics: talk.topics,
|
|
204
|
+
}))
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
speakers: matchingSpeakers,
|
|
208
|
+
talks: matchingTalks,
|
|
209
|
+
}
|
|
210
|
+
})
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { chat, maxIterations, toServerSentEventsResponse } from '@tanstack/ai'
|
|
3
|
+
import { anthropicText } from '@tanstack/ai-anthropic'
|
|
4
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
5
|
+
import { geminiText } from '@tanstack/ai-gemini'
|
|
6
|
+
import { ollamaText } from '@tanstack/ai-ollama'
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
getSpeakerBySlug,
|
|
10
|
+
getTalkBySlug,
|
|
11
|
+
getAllSpeakers,
|
|
12
|
+
getAllTalks,
|
|
13
|
+
searchConference,
|
|
14
|
+
} from '@/lib/conference-tools'
|
|
15
|
+
|
|
16
|
+
export const Route = createFileRoute('/api/remy-chat')({
|
|
17
|
+
server: {
|
|
18
|
+
handlers: {
|
|
19
|
+
POST: async ({ request }) => {
|
|
20
|
+
const requestSignal = request.signal
|
|
21
|
+
|
|
22
|
+
if (requestSignal.aborted) {
|
|
23
|
+
return new Response(null, { status: 499 })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const abortController = new AbortController()
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const body = await request.json()
|
|
30
|
+
const { messages, speakerSlug, talkSlug } = body
|
|
31
|
+
const data = body.data || {}
|
|
32
|
+
|
|
33
|
+
const SYSTEM_PROMPT = `You are Remy, a charming and knowledgeable culinary assistant for the Haute Pâtisserie 2026 conference in Paris. You have a warm, enthusiastic personality and deep appreciation for the art of pastry and baking.
|
|
34
|
+
|
|
35
|
+
PERSONALITY:
|
|
36
|
+
- Speak with warmth and a touch of French flair (occasional "magnifique!", "c'est parfait!", etc.)
|
|
37
|
+
- Be genuinely passionate about pastry, bread, and culinary arts
|
|
38
|
+
- Knowledgeable about techniques, ingredients, and the history of baking
|
|
39
|
+
- Helpful and encouraging to both novices and professionals
|
|
40
|
+
|
|
41
|
+
CAPABILITIES:
|
|
42
|
+
1. Use getSpeakerBySlug to get detailed information about a specific speaker
|
|
43
|
+
2. Use getTalkBySlug to get detailed information about a specific session
|
|
44
|
+
3. Use getAllSpeakers to see the complete speaker lineup
|
|
45
|
+
4. Use getAllTalks to see all available sessions
|
|
46
|
+
5. Use searchConference to find speakers or sessions matching a topic or keyword
|
|
47
|
+
|
|
48
|
+
INSTRUCTIONS:
|
|
49
|
+
- When asked about the conference, speakers, or sessions, use your tools to provide accurate information
|
|
50
|
+
- Help attendees find sessions that match their interests
|
|
51
|
+
- Share enthusiasm about the speakers and their expertise
|
|
52
|
+
- If asked about pastry techniques, you can provide general knowledge while recommending relevant sessions
|
|
53
|
+
- Keep responses conversational but informative
|
|
54
|
+
- When recommending sessions, explain why they might be interesting based on the user's query
|
|
55
|
+
|
|
56
|
+
${speakerSlug ? `CONTEXT: The user is viewing the profile of the speaker with slug "${speakerSlug}".` : ''}
|
|
57
|
+
${talkSlug ? `CONTEXT: The user is viewing the session with slug "${talkSlug}".` : ''}
|
|
58
|
+
|
|
59
|
+
Remember: You are the friendly face of Haute Pâtisserie 2026. Make every attendee feel welcome and excited about the culinary journey ahead!`
|
|
60
|
+
|
|
61
|
+
// Determine the best available provider
|
|
62
|
+
let provider: string = 'ollama'
|
|
63
|
+
let model: string = 'mistral:7b'
|
|
64
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
65
|
+
provider = 'anthropic'
|
|
66
|
+
model = 'claude-haiku-4-5'
|
|
67
|
+
} else if (process.env.OPENAI_API_KEY) {
|
|
68
|
+
provider = 'openai'
|
|
69
|
+
model = 'gpt-4o'
|
|
70
|
+
} else if (process.env.GEMINI_API_KEY) {
|
|
71
|
+
provider = 'gemini'
|
|
72
|
+
model = 'gemini-2.0-flash-exp'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Adapter factory pattern for multi-vendor support
|
|
76
|
+
const adapterConfig = {
|
|
77
|
+
anthropic: () =>
|
|
78
|
+
anthropicText((model || 'claude-haiku-4-5') as any),
|
|
79
|
+
openai: () => openaiText((model || 'gpt-4o') as any),
|
|
80
|
+
gemini: () => geminiText((model || 'gemini-2.0-flash-exp') as any),
|
|
81
|
+
ollama: () => ollamaText((model || 'mistral:7b') as any),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const adapter = adapterConfig[provider]()
|
|
85
|
+
|
|
86
|
+
const stream = chat({
|
|
87
|
+
adapter,
|
|
88
|
+
tools: [
|
|
89
|
+
getSpeakerBySlug,
|
|
90
|
+
getTalkBySlug,
|
|
91
|
+
getAllSpeakers,
|
|
92
|
+
getAllTalks,
|
|
93
|
+
searchConference,
|
|
94
|
+
],
|
|
95
|
+
systemPrompts: [SYSTEM_PROMPT],
|
|
96
|
+
agentLoopStrategy: maxIterations(5),
|
|
97
|
+
messages,
|
|
98
|
+
abortController,
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
return toServerSentEventsResponse(stream, { abortController })
|
|
102
|
+
} catch (error: any) {
|
|
103
|
+
console.error('Remy chat error:', error)
|
|
104
|
+
if (error.name === 'AbortError' || abortController.signal.aborted) {
|
|
105
|
+
return new Response(null, { status: 499 })
|
|
106
|
+
}
|
|
107
|
+
return new Response(
|
|
108
|
+
JSON.stringify({
|
|
109
|
+
error: 'Failed to process chat request',
|
|
110
|
+
message: error.message,
|
|
111
|
+
}),
|
|
112
|
+
{
|
|
113
|
+
status: 500,
|
|
114
|
+
headers: { 'Content-Type': 'application/json' },
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { defineCollection, defineConfig } from '@content-collections/core'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
const jobs = defineCollection({
|
|
5
|
+
name: 'jobs',
|
|
6
|
+
directory: 'content/jobs',
|
|
7
|
+
include: '**/*.md',
|
|
8
|
+
schema: z.object({
|
|
9
|
+
jobTitle: z.string(),
|
|
10
|
+
summary: z.string(),
|
|
11
|
+
startDate: z.string(),
|
|
12
|
+
endDate: z.string().optional(),
|
|
13
|
+
company: z.string(),
|
|
14
|
+
location: z.string(),
|
|
15
|
+
tags: z.array(z.string()),
|
|
16
|
+
content: z.string(),
|
|
17
|
+
}),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const education = defineCollection({
|
|
21
|
+
name: 'education',
|
|
22
|
+
directory: 'content/education',
|
|
23
|
+
include: '**/*.md',
|
|
24
|
+
schema: z.object({
|
|
25
|
+
school: z.string(),
|
|
26
|
+
summary: z.string(),
|
|
27
|
+
startDate: z.string(),
|
|
28
|
+
endDate: z.string().optional(),
|
|
29
|
+
tags: z.array(z.string()),
|
|
30
|
+
content: z.string(),
|
|
31
|
+
}),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export default defineConfig({
|
|
35
|
+
collections: [jobs, education],
|
|
36
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fetchServerSentEvents,
|
|
3
|
+
useChat,
|
|
4
|
+
createChatClientOptions,
|
|
5
|
+
} from '@tanstack/ai-react'
|
|
6
|
+
import type { InferChatMessages } from '@tanstack/ai-react'
|
|
7
|
+
|
|
8
|
+
// Default chat options for type inference
|
|
9
|
+
const defaultChatOptions = createChatClientOptions({
|
|
10
|
+
connection: fetchServerSentEvents('/api/resume-chat'),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export type ResumeChatMessages = InferChatMessages<typeof defaultChatOptions>
|
|
14
|
+
|
|
15
|
+
export const useResumeChat = () => {
|
|
16
|
+
const chatOptions = createChatClientOptions({
|
|
17
|
+
connection: fetchServerSentEvents('/api/resume-chat'),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
return useChat(chatOptions)
|
|
21
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { toolDefinition } from '@tanstack/ai'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
import { allJobs, allEducations } from 'content-collections'
|
|
5
|
+
|
|
6
|
+
// Tool definition for getting jobs by skill
|
|
7
|
+
export const getJobsBySkillToolDef = toolDefinition({
|
|
8
|
+
name: 'getJobsBySkill',
|
|
9
|
+
description:
|
|
10
|
+
'Find all jobs where the candidate used a specific technology or skill. Use this to check if the candidate has experience with particular technologies.',
|
|
11
|
+
inputSchema: z.object({
|
|
12
|
+
skill: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe(
|
|
15
|
+
'The skill or technology to search for (e.g., "React", "TypeScript", "Leadership")',
|
|
16
|
+
),
|
|
17
|
+
}),
|
|
18
|
+
outputSchema: z.array(
|
|
19
|
+
z.object({
|
|
20
|
+
jobTitle: z.string(),
|
|
21
|
+
company: z.string(),
|
|
22
|
+
location: z.string(),
|
|
23
|
+
startDate: z.string(),
|
|
24
|
+
endDate: z.string().optional(),
|
|
25
|
+
summary: z.string(),
|
|
26
|
+
tags: z.array(z.string()),
|
|
27
|
+
content: z.string(),
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Server implementation
|
|
33
|
+
export const getJobsBySkill = getJobsBySkillToolDef.server(({ skill }) => {
|
|
34
|
+
return allJobs.filter((job) =>
|
|
35
|
+
job.tags.some((tag) => tag.toLowerCase().includes(skill.toLowerCase())),
|
|
36
|
+
)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// Tool definition for getting all jobs
|
|
40
|
+
export const getAllJobsToolDef = toolDefinition({
|
|
41
|
+
name: 'getAllJobs',
|
|
42
|
+
description:
|
|
43
|
+
'Get a complete list of all work experience with full details including job titles, companies, dates, summaries, and skills. Use this to get an overview of the candidate\'s entire work history.',
|
|
44
|
+
inputSchema: z.object({}),
|
|
45
|
+
outputSchema: z.array(
|
|
46
|
+
z.object({
|
|
47
|
+
jobTitle: z.string(),
|
|
48
|
+
company: z.string(),
|
|
49
|
+
location: z.string(),
|
|
50
|
+
startDate: z.string(),
|
|
51
|
+
endDate: z.string().optional(),
|
|
52
|
+
summary: z.string(),
|
|
53
|
+
tags: z.array(z.string()),
|
|
54
|
+
content: z.string(),
|
|
55
|
+
}),
|
|
56
|
+
),
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// Server implementation
|
|
60
|
+
export const getAllJobs = getAllJobsToolDef.server(() => {
|
|
61
|
+
return allJobs.map((job) => ({
|
|
62
|
+
jobTitle: job.jobTitle,
|
|
63
|
+
company: job.company,
|
|
64
|
+
location: job.location,
|
|
65
|
+
startDate: job.startDate,
|
|
66
|
+
endDate: job.endDate,
|
|
67
|
+
summary: job.summary,
|
|
68
|
+
tags: job.tags,
|
|
69
|
+
content: job.content,
|
|
70
|
+
}))
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Tool definition for getting all education
|
|
74
|
+
export const getAllEducationToolDef = toolDefinition({
|
|
75
|
+
name: 'getAllEducation',
|
|
76
|
+
description:
|
|
77
|
+
'Get a complete list of all education history including schools, programs, dates, and skills learned. Use this to understand the candidate\'s educational background.',
|
|
78
|
+
inputSchema: z.object({}),
|
|
79
|
+
outputSchema: z.array(
|
|
80
|
+
z.object({
|
|
81
|
+
school: z.string(),
|
|
82
|
+
summary: z.string(),
|
|
83
|
+
startDate: z.string(),
|
|
84
|
+
endDate: z.string().optional(),
|
|
85
|
+
tags: z.array(z.string()),
|
|
86
|
+
content: z.string(),
|
|
87
|
+
}),
|
|
88
|
+
),
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
// Server implementation
|
|
92
|
+
export const getAllEducation = getAllEducationToolDef.server(() => {
|
|
93
|
+
return allEducations.map((education) => ({
|
|
94
|
+
school: education.school,
|
|
95
|
+
summary: education.summary,
|
|
96
|
+
startDate: education.startDate,
|
|
97
|
+
endDate: education.endDate,
|
|
98
|
+
tags: education.tags,
|
|
99
|
+
content: education.content,
|
|
100
|
+
}))
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// Tool definition for searching experience
|
|
104
|
+
export const searchExperienceToolDef = toolDefinition({
|
|
105
|
+
name: 'searchExperience',
|
|
106
|
+
description:
|
|
107
|
+
'Search for jobs by keywords in the job title, company name, summary, or content. Use this to find specific types of experience or roles.',
|
|
108
|
+
inputSchema: z.object({
|
|
109
|
+
query: z
|
|
110
|
+
.string()
|
|
111
|
+
.describe(
|
|
112
|
+
'The search query (e.g., "senior", "lead", "frontend", "startup")',
|
|
113
|
+
),
|
|
114
|
+
}),
|
|
115
|
+
outputSchema: z.array(
|
|
116
|
+
z.object({
|
|
117
|
+
jobTitle: z.string(),
|
|
118
|
+
company: z.string(),
|
|
119
|
+
location: z.string(),
|
|
120
|
+
startDate: z.string(),
|
|
121
|
+
endDate: z.string().optional(),
|
|
122
|
+
summary: z.string(),
|
|
123
|
+
tags: z.array(z.string()),
|
|
124
|
+
matchedIn: z
|
|
125
|
+
.array(z.string())
|
|
126
|
+
.describe('Which fields matched the search'),
|
|
127
|
+
}),
|
|
128
|
+
),
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// Server implementation
|
|
132
|
+
export const searchExperience = searchExperienceToolDef.server(({ query }) => {
|
|
133
|
+
const lowerQuery = query.toLowerCase()
|
|
134
|
+
|
|
135
|
+
return allJobs
|
|
136
|
+
.map((job) => {
|
|
137
|
+
const matchedIn: string[] = []
|
|
138
|
+
|
|
139
|
+
if (job.jobTitle.toLowerCase().includes(lowerQuery)) {
|
|
140
|
+
matchedIn.push('job title')
|
|
141
|
+
}
|
|
142
|
+
if (job.company.toLowerCase().includes(lowerQuery)) {
|
|
143
|
+
matchedIn.push('company')
|
|
144
|
+
}
|
|
145
|
+
if (job.summary.toLowerCase().includes(lowerQuery)) {
|
|
146
|
+
matchedIn.push('summary')
|
|
147
|
+
}
|
|
148
|
+
if (job.content.toLowerCase().includes(lowerQuery)) {
|
|
149
|
+
matchedIn.push('description')
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { job, matchedIn }
|
|
153
|
+
})
|
|
154
|
+
.filter(({ matchedIn }) => matchedIn.length > 0)
|
|
155
|
+
.map(({ job, matchedIn }) => ({
|
|
156
|
+
jobTitle: job.jobTitle,
|
|
157
|
+
company: job.company,
|
|
158
|
+
location: job.location,
|
|
159
|
+
startDate: job.startDate,
|
|
160
|
+
endDate: job.endDate,
|
|
161
|
+
summary: job.summary,
|
|
162
|
+
tags: job.tags,
|
|
163
|
+
matchedIn,
|
|
164
|
+
}))
|
|
165
|
+
})
|