@tanstack/cta-framework-react-cra 0.44.3 → 0.46.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.
Files changed (120) hide show
  1. package/{examples/tanchat/assets/src/components/example-AIAssistant.tsx → add-ons/ai/assets/src/components/demo-AIAssistant.tsx} +6 -8
  2. package/{examples/tanchat/assets/src/components/example-GuitarRecommendation.tsx → add-ons/ai/assets/src/components/demo-GuitarRecommendation.tsx} +2 -2
  3. package/{examples/tanchat/assets/src/lib/example.ai-hook.ts → add-ons/ai/assets/src/lib/demo-ai-hook.ts} +9 -9
  4. package/{examples/tanchat/assets/src/lib/example.guitar-tools.ts → add-ons/ai/assets/src/lib/demo-guitar-tools.ts} +1 -1
  5. package/{examples/tanchat/assets/src/routes/demo/tanchat.tsx → add-ons/ai/assets/src/routes/demo/ai-chat.tsx} +30 -150
  6. package/{examples/tanchat/assets/src/routes/demo/image.tsx → add-ons/ai/assets/src/routes/demo/ai-image.tsx} +2 -50
  7. package/add-ons/ai/assets/src/routes/demo/ai-structured.tsx +310 -0
  8. package/{examples/tanchat/assets/src/routes/demo/api.tanchat.ts → add-ons/ai/assets/src/routes/demo/api.ai.chat.ts} +16 -6
  9. package/{examples/tanchat/assets/src/routes/demo/api.image.ts → add-ons/ai/assets/src/routes/demo/api.ai.image.ts} +3 -5
  10. package/add-ons/ai/assets/src/routes/demo/api.ai.structured.ts +136 -0
  11. package/add-ons/ai/assets/src/routes/demo/api.ai.transcription.ts +89 -0
  12. package/{examples/tanchat/assets/src/routes/demo/api.tts.ts → add-ons/ai/assets/src/routes/demo/api.ai.tts.ts} +1 -1
  13. package/{examples/tanchat/assets/src/routes/example.guitars → add-ons/ai/assets/src/routes/demo/guitars}/$guitarId.tsx +3 -2
  14. package/{examples/tanchat/assets/src/routes/example.guitars → add-ons/ai/assets/src/routes/demo/guitars}/index.tsx +3 -2
  15. package/add-ons/ai/info.json +46 -0
  16. package/{examples/tanchat → add-ons/ai}/package.json +1 -1
  17. package/examples/events/README.md +110 -0
  18. package/examples/events/assets/content/speakers/andre-costa.md +22 -0
  19. package/examples/events/assets/content/speakers/hans-mueller.md +22 -0
  20. package/examples/events/assets/content/speakers/isabella-martinez.md +22 -0
  21. package/examples/events/assets/content/speakers/kenji-nakamura.md +22 -0
  22. package/examples/events/assets/content/speakers/marie-dubois.md +20 -0
  23. package/examples/events/assets/content/speakers/priya-sharma.md +22 -0
  24. package/examples/events/assets/content/talks/croissant-lamination-secrets.md +39 -0
  25. package/examples/events/assets/content/talks/french-macaron-mastery.md +39 -0
  26. package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md +39 -0
  27. package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md +39 -0
  28. package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md +36 -0
  29. package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md +32 -0
  30. package/examples/events/assets/content/talks/the-science-of-sugar.md +39 -0
  31. package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md +39 -0
  32. package/examples/events/assets/content-collections.ts +56 -0
  33. package/examples/events/assets/public/background-1.jpg +0 -0
  34. package/examples/events/assets/public/background-2.jpg +0 -0
  35. package/examples/events/assets/public/background-3.jpg +0 -0
  36. package/examples/events/assets/public/background-4.jpg +0 -0
  37. package/examples/events/assets/public/conference-logo.png +0 -0
  38. package/examples/events/assets/public/favicon.ico +0 -0
  39. package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
  40. package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
  41. package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
  42. package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
  43. package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
  44. package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
  45. package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
  46. package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
  47. package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
  48. package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
  49. package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
  50. package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
  51. package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
  52. package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
  53. package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
  54. package/examples/events/assets/public/tanstack-word-logo-white.svg +1 -0
  55. package/examples/events/assets/src/components/HeroCarousel.tsx +61 -0
  56. package/examples/events/assets/src/components/RemyAssistant.tsx +207 -0
  57. package/examples/events/assets/src/components/RemyButton.tsx +18 -0
  58. package/examples/events/assets/src/components/SpeakerCard.tsx +67 -0
  59. package/examples/events/assets/src/components/TalkCard.tsx +77 -0
  60. package/examples/events/assets/src/components/ui/card.tsx +92 -0
  61. package/examples/events/assets/src/lib/conference-ai-hook.ts +26 -0
  62. package/examples/events/assets/src/lib/conference-tools.ts +210 -0
  63. package/examples/events/assets/src/lib/utils.ts +6 -0
  64. package/examples/events/assets/src/routes/api.remy-chat.ts +121 -0
  65. package/examples/events/assets/src/routes/index.tsx +192 -0
  66. package/examples/events/assets/src/routes/schedule.index.tsx +274 -0
  67. package/examples/events/assets/src/routes/speakers.$slug.tsx +122 -0
  68. package/examples/events/assets/src/routes/speakers.index.tsx +40 -0
  69. package/examples/events/assets/src/routes/talks.$slug.tsx +116 -0
  70. package/examples/events/assets/src/routes/talks.index.tsx +40 -0
  71. package/examples/events/assets/src/styles.css +194 -0
  72. package/examples/events/info.json +63 -0
  73. package/examples/events/package.json +23 -0
  74. package/examples/resume/README.md +82 -0
  75. package/examples/resume/assets/content/education/code-school.md +17 -0
  76. package/examples/resume/assets/content/jobs/freelance.md +13 -0
  77. package/examples/resume/assets/content/jobs/initech-junior.md +20 -0
  78. package/examples/resume/assets/content/jobs/initech-lead.md +29 -0
  79. package/examples/resume/assets/content/jobs/initrode-senior.md +28 -0
  80. package/examples/resume/assets/content-collections.ts +36 -0
  81. package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
  82. package/examples/resume/assets/src/components/ResumeAssistant.tsx +193 -0
  83. package/examples/resume/assets/src/components/ResumeAssistantButton.tsx +20 -0
  84. package/examples/resume/assets/src/components/ui/badge.tsx +46 -0
  85. package/examples/resume/assets/src/components/ui/card.tsx +92 -0
  86. package/examples/resume/assets/src/components/ui/checkbox.tsx +30 -0
  87. package/examples/resume/assets/src/components/ui/hover-card.tsx +44 -0
  88. package/examples/resume/assets/src/components/ui/separator.tsx +26 -0
  89. package/examples/resume/assets/src/lib/resume-ai-hook.ts +21 -0
  90. package/examples/resume/assets/src/lib/resume-tools.ts +165 -0
  91. package/examples/resume/assets/src/lib/utils.ts +6 -0
  92. package/examples/resume/assets/src/routes/api.resume-chat.ts +110 -0
  93. package/examples/resume/assets/src/routes/index.tsx +220 -0
  94. package/examples/resume/assets/src/styles.css +138 -0
  95. package/examples/resume/info.json +30 -0
  96. package/examples/resume/package.json +27 -0
  97. package/package.json +2 -2
  98. package/examples/tanchat/assets/src/lib/model-selection.ts +0 -78
  99. package/examples/tanchat/assets/src/lib/vendor-capabilities.ts +0 -55
  100. package/examples/tanchat/assets/src/routes/demo/api.available-providers.ts +0 -35
  101. package/examples/tanchat/assets/src/routes/demo/api.structured.ts +0 -168
  102. package/examples/tanchat/assets/src/routes/demo/api.transcription.ts +0 -89
  103. package/examples/tanchat/assets/src/routes/demo/structured.tsx +0 -460
  104. package/examples/tanchat/info.json +0 -46
  105. /package/{examples/tanchat → add-ons/ai}/README.md +0 -0
  106. /package/{examples/tanchat → add-ons/ai}/assets/_dot_env.local.append +0 -0
  107. /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-flowers.jpg +0 -0
  108. /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-motherboard.jpg +0 -0
  109. /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-racing.jpg +0 -0
  110. /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-steamer-trunk.jpg +0 -0
  111. /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-superhero.jpg +0 -0
  112. /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-traveling.jpg +0 -0
  113. /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-video-games.jpg +0 -0
  114. /package/{examples/tanchat → add-ons/ai}/assets/public/example-ukelele-tanstack.jpg +0 -0
  115. /package/{examples/tanchat/assets/src/data/example-guitars.ts → add-ons/ai/assets/src/data/demo-guitars.ts} +0 -0
  116. /package/{examples/tanchat/assets/src/hooks/useAudioRecorder.ts → add-ons/ai/assets/src/hooks/demo-useAudioRecorder.ts} +0 -0
  117. /package/{examples/tanchat/assets/src/hooks/useTTS.ts → add-ons/ai/assets/src/hooks/demo-useTTS.ts} +0 -0
  118. /package/{examples/tanchat → add-ons/ai}/assets/src/lib/ai-devtools.tsx +0 -0
  119. /package/{examples/tanchat/assets/src/routes/demo/tanchat.css → add-ons/ai/assets/src/routes/demo/ai-chat.css} +0 -0
  120. /package/{examples/tanchat → add-ons/ai}/small-logo.svg +0 -0
@@ -2,13 +2,13 @@ import { useEffect, useRef, useState } from 'react'
2
2
  import { useStore } from '@tanstack/react-store'
3
3
  import { Store } from '@tanstack/store'
4
4
 
5
- import { Send, X, ChevronRight } from 'lucide-react'
5
+ import { Send, X, ChevronRight, BotIcon } from 'lucide-react'
6
6
  import { Streamdown } from 'streamdown'
7
7
 
8
- import { useGuitarRecommendationChat } from '@/lib/example.ai-hook'
9
- import type { ChatMessages } from '@/lib/example.ai-hook'
8
+ import { useGuitarRecommendationChat } from '@/lib/demo-ai-hook'
9
+ import type { ChatMessages } from '@/lib/demo-ai-hook'
10
10
 
11
- import GuitarRecommendation from './example-GuitarRecommendation'
11
+ import GuitarRecommendation from './demo-GuitarRecommendation'
12
12
 
13
13
  export const showAIAssistant = new Store(false)
14
14
 
@@ -87,12 +87,10 @@ export default function AIAssistant() {
87
87
  <div className="relative z-50">
88
88
  <button
89
89
  onClick={() => showAIAssistant.setState((state) => !state)}
90
- className="w-full flex items-center justify-between px-4 py-2.5 rounded-lg bg-linear-to-r from-orange-500 to-red-600 text-white hover:opacity-90 transition-opacity"
90
+ className="w-full flex items-center justify-between px-4 py-2.5 rounded-lg bg-linear-to-r from-green-700 to-green-900 text-white hover:opacity-90 transition-opacity"
91
91
  >
92
92
  <div className="flex items-center gap-2">
93
- <div className="w-5 h-5 rounded-lg bg-white/20 flex items-center justify-center text-xs font-medium">
94
- AI
95
- </div>
93
+ <BotIcon size={24} />
96
94
  <span className="font-medium">AI Assistant</span>
97
95
  </div>
98
96
  <ChevronRight className="w-4 h-4" />
@@ -1,8 +1,8 @@
1
1
  import { useNavigate } from '@tanstack/react-router'
2
2
 
3
- import { showAIAssistant } from './example-AIAssistant'
3
+ import { showAIAssistant } from './demo-AIAssistant'
4
4
 
5
- import guitars from '../data/example-guitars'
5
+ import guitars from '@/data/demo-guitars'
6
6
 
7
7
  export default function GuitarRecommendation({ id }: { id: string }) {
8
8
  const navigate = useNavigate()
@@ -2,21 +2,21 @@ import {
2
2
  fetchServerSentEvents,
3
3
  useChat,
4
4
  createChatClientOptions,
5
- } from "@tanstack/ai-react";
6
- import type { InferChatMessages } from "@tanstack/ai-react";
7
- import { clientTools } from "@tanstack/ai-client";
5
+ } from '@tanstack/ai-react'
6
+ import type { InferChatMessages } from '@tanstack/ai-react'
7
+ import { clientTools } from '@tanstack/ai-client'
8
8
 
9
- import { recommendGuitarToolDef } from "@/lib/example.guitar-tools";
9
+ import { recommendGuitarToolDef } from '@/lib/demo-guitar-tools'
10
10
 
11
11
  const recommendGuitarToolClient = recommendGuitarToolDef.client(({ id }) => ({
12
12
  id: +id,
13
- }));
13
+ }))
14
14
 
15
15
  const chatOptions = createChatClientOptions({
16
- connection: fetchServerSentEvents("/demo/api/tanchat"),
16
+ connection: fetchServerSentEvents('/demo/api/ai/chat'),
17
17
  tools: clientTools(recommendGuitarToolClient),
18
- });
18
+ })
19
19
 
20
- export type ChatMessages = InferChatMessages<typeof chatOptions>;
20
+ export type ChatMessages = InferChatMessages<typeof chatOptions>
21
21
 
22
- export const useGuitarRecommendationChat = () => useChat(chatOptions);
22
+ export const useGuitarRecommendationChat = () => useChat(chatOptions)
@@ -1,6 +1,6 @@
1
1
  import { toolDefinition } from '@tanstack/ai'
2
2
  import { z } from 'zod'
3
- import guitars from '@/data/example-guitars'
3
+ import guitars from '@/data/demo-guitars'
4
4
 
5
5
  // Tool definition for getting guitars
6
6
  export const getGuitarsToolDef = toolDefinition({
@@ -11,24 +11,16 @@ import {
11
11
  } from 'lucide-react'
12
12
  import { Streamdown } from 'streamdown'
13
13
 
14
- import { useGuitarRecommendationChat } from '@/lib/example.ai-hook'
15
- import type { ChatMessages } from '@/lib/example.ai-hook'
16
- import {
17
- MODEL_OPTIONS,
18
- getAvailableModelOptions,
19
- getStoredModelPreference,
20
- setStoredModelPreference,
21
- } from '@/lib/model-selection'
22
- import type { Provider, ModelOption } from '@/lib/model-selection'
23
- import { hasCapability } from '@/lib/vendor-capabilities'
24
- import { useAudioRecorder } from '@/hooks/useAudioRecorder'
25
- import { useTTS } from '@/hooks/useTTS'
14
+ import { useGuitarRecommendationChat } from '@/lib/demo-ai-hook'
15
+ import type { ChatMessages } from '@/lib/demo-ai-hook'
16
+ import { useAudioRecorder } from '@/hooks/demo-useAudioRecorder'
17
+ import { useTTS } from '@/hooks/demo-useTTS'
26
18
 
27
- import GuitarRecommendation from '@/components/example-GuitarRecommendation'
19
+ import GuitarRecommendation from '@/components/demo-GuitarRecommendation'
28
20
 
29
- import './tanchat.css'
21
+ import './ai-chat.css'
30
22
 
31
- function InitalLayout({ children }: { children: React.ReactNode }) {
23
+ function InitialLayout({ children }: { children: React.ReactNode }) {
32
24
  return (
33
25
  <div className="flex-1 flex items-center justify-center px-4">
34
26
  <div className="text-center max-w-3xl mx-auto w-full">
@@ -58,13 +50,11 @@ function Messages({
58
50
  playingId,
59
51
  onSpeak,
60
52
  onStopSpeak,
61
- canPlayTTS,
62
53
  }: {
63
54
  messages: ChatMessages
64
55
  playingId: string | null
65
56
  onSpeak: (text: string, id: string) => void
66
57
  onStopSpeak: () => void
67
- canPlayTTS: boolean
68
58
  }) {
69
59
  const messagesContainerRef = useRef<HTMLDivElement>(null)
70
60
 
@@ -148,7 +138,7 @@ function Messages({
148
138
  })}
149
139
  </div>
150
140
  {/* TTS button for assistant messages */}
151
- {message.role === 'assistant' && textContent && canPlayTTS && (
141
+ {message.role === 'assistant' && textContent && (
152
142
  <button
153
143
  onClick={() =>
154
144
  isPlaying
@@ -176,69 +166,13 @@ function Messages({
176
166
 
177
167
  function ChatPage() {
178
168
  const [input, setInput] = useState('')
179
- const [availableProviders, setAvailableProviders] = useState<Provider[]>([])
180
- const [selectedModel, setSelectedModel] = useState<ModelOption | null>(null)
181
- const [isCheckingProviders, setIsCheckingProviders] = useState(true)
182
169
 
183
- // Audio hooks
184
170
  const { isRecording, isTranscribing, startRecording, stopRecording } =
185
171
  useAudioRecorder()
186
172
  const { playingId, speak, stop: stopTTS } = useTTS()
187
173
 
188
- // Fetch available providers on mount
189
- useEffect(() => {
190
- fetch('/demo/api/available-providers')
191
- .then((res) => res.json())
192
- .then((data) => {
193
- setAvailableProviders(data.providers)
194
-
195
- // Set default model based on stored preference or first available
196
- const storedPref = getStoredModelPreference()
197
- const availableOptions = getAvailableModelOptions(data.providers)
198
-
199
- if (
200
- storedPref &&
201
- availableOptions.some((o) => o.model === storedPref.model)
202
- ) {
203
- setSelectedModel(storedPref)
204
- } else if (availableOptions.length > 0) {
205
- setSelectedModel(availableOptions[0])
206
- }
207
-
208
- setIsCheckingProviders(false)
209
- })
210
- .catch(() => {
211
- // Fallback to Ollama if can't reach API
212
- setAvailableProviders(['ollama'])
213
- const ollamaOption = MODEL_OPTIONS.find((m) => m.provider === 'ollama')
214
- if (ollamaOption) setSelectedModel(ollamaOption)
215
- setIsCheckingProviders(false)
216
- })
217
- }, [])
218
-
219
- const availableModelOptions = getAvailableModelOptions(availableProviders)
220
-
221
- // Check if current provider supports TTS (only OpenAI)
222
- const canPlayTTS =
223
- selectedModel && hasCapability(selectedModel.provider, 'tts')
224
- const canRecordAudio =
225
- selectedModel && hasCapability(selectedModel.provider, 'transcription')
226
-
227
174
  const { messages, sendMessage, isLoading, stop } =
228
- useGuitarRecommendationChat(
229
- selectedModel?.provider || 'anthropic',
230
- selectedModel?.model || 'claude-haiku-4-5',
231
- )
232
-
233
- const handleModelChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
234
- const selected = availableModelOptions.find(
235
- (m) => `${m.provider}:${m.model}` === e.target.value,
236
- )
237
- if (selected) {
238
- setSelectedModel(selected)
239
- setStoredModelPreference(selected)
240
- }
241
- }
175
+ useGuitarRecommendationChat()
242
176
 
243
177
  const handleMicClick = async () => {
244
178
  if (isRecording) {
@@ -253,34 +187,7 @@ function ChatPage() {
253
187
  }
254
188
  }
255
189
 
256
- const Layout = messages.length ? ChattingLayout : InitalLayout
257
-
258
- if (isCheckingProviders) {
259
- return (
260
- <div className="relative flex h-[calc(100vh-80px)] bg-gray-900 items-center justify-center">
261
- <Loader2 className="w-8 h-8 text-orange-500 animate-spin" />
262
- </div>
263
- )
264
- }
265
-
266
- if (availableModelOptions.length === 0) {
267
- return (
268
- <div className="relative flex h-[calc(100vh-80px)] bg-gray-900">
269
- <div className="flex-1 flex items-center justify-center px-4">
270
- <div className="text-center max-w-xl">
271
- <h1 className="text-2xl font-bold text-white mb-4">
272
- No AI Providers Available
273
- </h1>
274
- <p className="text-gray-400 mb-4">
275
- Please configure at least one AI provider in your{' '}
276
- <code className="text-orange-400">.env.local</code> file, or
277
- ensure Ollama is running locally.
278
- </p>
279
- </div>
280
- </div>
281
- </div>
282
- )
283
- }
190
+ const Layout = messages.length ? ChattingLayout : InitialLayout
284
191
 
285
192
  return (
286
193
  <div className="relative flex h-[calc(100vh-80px)] bg-gray-900">
@@ -290,34 +197,10 @@ function ChatPage() {
290
197
  playingId={playingId}
291
198
  onSpeak={speak}
292
199
  onStopSpeak={stopTTS}
293
- canPlayTTS={!!canPlayTTS}
294
200
  />
295
201
 
296
202
  <Layout>
297
203
  <div className="space-y-3">
298
- {/* Model Selector */}
299
- <div className="flex justify-center">
300
- <select
301
- value={
302
- selectedModel
303
- ? `${selectedModel.provider}:${selectedModel.model}`
304
- : ''
305
- }
306
- onChange={handleModelChange}
307
- disabled={isLoading}
308
- className="rounded-lg border border-orange-500/20 bg-gray-800/50 px-3 py-1.5 text-xs text-gray-300 focus:outline-none focus:ring-2 focus:ring-orange-500/50"
309
- >
310
- {availableModelOptions.map((option) => (
311
- <option
312
- key={`${option.provider}:${option.model}`}
313
- value={`${option.provider}:${option.model}`}
314
- >
315
- {option.label}
316
- </option>
317
- ))}
318
- </select>
319
- </div>
320
-
321
204
  {isLoading && (
322
205
  <div className="flex items-center justify-center">
323
206
  <button
@@ -339,28 +222,25 @@ function ChatPage() {
339
222
  }}
340
223
  >
341
224
  <div className="relative max-w-xl mx-auto flex items-center gap-2">
342
- {/* Mic button - only show if OpenAI is available */}
343
- {canRecordAudio && (
344
- <button
345
- type="button"
346
- onClick={handleMicClick}
347
- disabled={isLoading || isTranscribing}
348
- className={`p-3 rounded-lg transition-colors ${
349
- isRecording
350
- ? 'bg-red-600 hover:bg-red-700 text-white'
351
- : 'bg-gray-800/50 text-gray-400 hover:text-orange-400 border border-orange-500/20'
352
- } disabled:opacity-50`}
353
- title={isRecording ? 'Stop recording' : 'Start recording'}
354
- >
355
- {isTranscribing ? (
356
- <Loader2 className="w-4 h-4 animate-spin" />
357
- ) : isRecording ? (
358
- <MicOff className="w-4 h-4" />
359
- ) : (
360
- <Mic className="w-4 h-4" />
361
- )}
362
- </button>
363
- )}
225
+ <button
226
+ type="button"
227
+ onClick={handleMicClick}
228
+ disabled={isLoading || isTranscribing}
229
+ className={`p-3 rounded-lg transition-colors ${
230
+ isRecording
231
+ ? 'bg-red-600 hover:bg-red-700 text-white'
232
+ : 'bg-gray-800/50 text-gray-400 hover:text-orange-400 border border-orange-500/20'
233
+ } disabled:opacity-50`}
234
+ title={isRecording ? 'Stop recording' : 'Start recording'}
235
+ >
236
+ {isTranscribing ? (
237
+ <Loader2 className="w-4 h-4 animate-spin" />
238
+ ) : isRecording ? (
239
+ <MicOff className="w-4 h-4" />
240
+ ) : (
241
+ <Mic className="w-4 h-4" />
242
+ )}
243
+ </button>
364
244
 
365
245
  <div className="relative flex-1">
366
246
  <textarea
@@ -402,6 +282,6 @@ function ChatPage() {
402
282
  )
403
283
  }
404
284
 
405
- export const Route = createFileRoute('/demo/tanchat')({
285
+ export const Route = createFileRoute('/demo/ai/chat')({
406
286
  component: ChatPage,
407
287
  })
@@ -19,16 +19,6 @@ function ImagePage() {
19
19
  const [images, setImages] = useState<Array<GeneratedImage>>([])
20
20
  const [isLoading, setIsLoading] = useState(false)
21
21
  const [error, setError] = useState<string | null>(null)
22
- const [usedModel, setUsedModel] = useState<string | null>(null)
23
- const [hasOpenAI, setHasOpenAI] = useState<boolean | null>(null)
24
-
25
- // Check if OpenAI is available
26
- useEffect(() => {
27
- fetch('/demo/api/available-providers')
28
- .then((res) => res.json())
29
- .then((data) => setHasOpenAI(data.hasOpenAI))
30
- .catch(() => setHasOpenAI(false))
31
- }, [])
32
22
 
33
23
  const handleGenerate = async () => {
34
24
  setIsLoading(true)
@@ -36,7 +26,7 @@ function ImagePage() {
36
26
  setImages([])
37
27
 
38
28
  try {
39
- const response = await fetch('/demo/api/image', {
29
+ const response = await fetch('/demo/api/ai/image', {
40
30
  method: 'POST',
41
31
  headers: { 'Content-Type': 'application/json' },
42
32
  body: JSON.stringify({ prompt, size, numberOfImages }),
@@ -49,7 +39,6 @@ function ImagePage() {
49
39
  }
50
40
 
51
41
  setImages(data.images)
52
- setUsedModel(data.model)
53
42
  } catch (err: any) {
54
43
  setError(err.message)
55
44
  } finally {
@@ -83,34 +72,6 @@ function ImagePage() {
83
72
  }
84
73
  }
85
74
 
86
- // Show loading state while checking for OpenAI
87
- if (hasOpenAI === null) {
88
- return (
89
- <div className="min-h-[calc(100vh-80px)] bg-gray-900 p-6 flex items-center justify-center">
90
- <Loader2 className="w-8 h-8 text-orange-500 animate-spin" />
91
- </div>
92
- )
93
- }
94
-
95
- // Show message if OpenAI is not available
96
- if (!hasOpenAI) {
97
- return (
98
- <div className="min-h-[calc(100vh-80px)] bg-gray-900 p-6">
99
- <div className="max-w-2xl mx-auto text-center py-16">
100
- <ImageIcon className="w-16 h-16 text-gray-600 mx-auto mb-4" />
101
- <h1 className="text-2xl font-bold text-white mb-4">
102
- Image Generation Unavailable
103
- </h1>
104
- <p className="text-gray-400 mb-4">
105
- Image generation requires an OpenAI API key. Please add your{' '}
106
- <code className="text-orange-400">OPENAI_API_KEY</code> to your{' '}
107
- <code className="text-orange-400">.env.local</code> file.
108
- </p>
109
- </div>
110
- </div>
111
- )
112
- }
113
-
114
75
  return (
115
76
  <div className="min-h-[calc(100vh-80px)] bg-gray-900 p-6">
116
77
  <div className="max-w-6xl mx-auto">
@@ -227,15 +188,6 @@ function ImagePage() {
227
188
  </div>
228
189
  ))}
229
190
  </div>
230
- <div className="pt-4 border-t border-gray-700 text-sm text-gray-400">
231
- <p>
232
- Provider:{' '}
233
- <span className="text-orange-400">OpenAI</span>
234
- </p>
235
- <p>
236
- Model: <span className="text-orange-400">{usedModel}</span>
237
- </p>
238
- </div>
239
191
  </div>
240
192
  ) : !error && !isLoading ? (
241
193
  <div className="flex flex-col items-center justify-center h-64 text-gray-500">
@@ -252,6 +204,6 @@ function ImagePage() {
252
204
  )
253
205
  }
254
206
 
255
- export const Route = createFileRoute('/demo/image')({
207
+ export const Route = createFileRoute('/demo/ai/image')({
256
208
  component: ImagePage,
257
209
  })