@tanstack/cta-framework-react-cra 0.44.3 → 0.45.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.
Files changed (97) 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} +28 -148
  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/package.json +2 -2
  75. package/examples/tanchat/assets/src/lib/model-selection.ts +0 -78
  76. package/examples/tanchat/assets/src/lib/vendor-capabilities.ts +0 -55
  77. package/examples/tanchat/assets/src/routes/demo/api.available-providers.ts +0 -35
  78. package/examples/tanchat/assets/src/routes/demo/api.structured.ts +0 -168
  79. package/examples/tanchat/assets/src/routes/demo/api.transcription.ts +0 -89
  80. package/examples/tanchat/assets/src/routes/demo/structured.tsx +0 -460
  81. package/examples/tanchat/info.json +0 -46
  82. /package/{examples/tanchat → add-ons/ai}/README.md +0 -0
  83. /package/{examples/tanchat → add-ons/ai}/assets/_dot_env.local.append +0 -0
  84. /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-flowers.jpg +0 -0
  85. /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-motherboard.jpg +0 -0
  86. /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-racing.jpg +0 -0
  87. /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-steamer-trunk.jpg +0 -0
  88. /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-superhero.jpg +0 -0
  89. /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-traveling.jpg +0 -0
  90. /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-video-games.jpg +0 -0
  91. /package/{examples/tanchat → add-ons/ai}/assets/public/example-ukelele-tanstack.jpg +0 -0
  92. /package/{examples/tanchat/assets/src/data/example-guitars.ts → add-ons/ai/assets/src/data/demo-guitars.ts} +0 -0
  93. /package/{examples/tanchat/assets/src/hooks/useAudioRecorder.ts → add-ons/ai/assets/src/hooks/demo-useAudioRecorder.ts} +0 -0
  94. /package/{examples/tanchat/assets/src/hooks/useTTS.ts → add-ons/ai/assets/src/hooks/demo-useTTS.ts} +0 -0
  95. /package/{examples/tanchat → add-ons/ai}/assets/src/lib/ai-devtools.tsx +0 -0
  96. /package/{examples/tanchat/assets/src/routes/demo/tanchat.css → add-ons/ai/assets/src/routes/demo/ai-chat.css} +0 -0
  97. /package/{examples/tanchat → add-ons/ai}/small-logo.svg +0 -0
@@ -0,0 +1,194 @@
1
+ @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;0,800;0,900;1,400;1,500;1,600;1,700&family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500&display=swap');
2
+ @import 'tailwindcss';
3
+
4
+ @plugin "tailwindcss-animate";
5
+
6
+ @custom-variant dark (&:is(.dark *));
7
+
8
+ body {
9
+ @apply m-0;
10
+ font-family: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
11
+ -webkit-font-smoothing: antialiased;
12
+ -moz-osx-font-smoothing: grayscale;
13
+ }
14
+
15
+ code {
16
+ font-family:
17
+ source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
18
+ }
19
+
20
+ /* Haute Patisserie Dark Theme */
21
+ :root {
22
+ /* Core colors - Dark elegant bakery */
23
+ --background: oklch(0.13 0.01 60);
24
+ --foreground: oklch(0.93 0.02 85);
25
+
26
+ /* Warm cream for cards */
27
+ --card: oklch(0.17 0.015 55);
28
+ --card-foreground: oklch(0.93 0.02 85);
29
+
30
+ /* Copper accent */
31
+ --primary: oklch(0.65 0.14 55);
32
+ --primary-foreground: oklch(0.13 0.01 60);
33
+
34
+ /* Antique gold */
35
+ --secondary: oklch(0.72 0.12 85);
36
+ --secondary-foreground: oklch(0.13 0.01 60);
37
+
38
+ /* Muted warm tones */
39
+ --muted: oklch(0.22 0.02 55);
40
+ --muted-foreground: oklch(0.65 0.03 85);
41
+
42
+ /* Accent - rich copper */
43
+ --accent: oklch(0.55 0.15 45);
44
+ --accent-foreground: oklch(0.95 0.02 85);
45
+
46
+ /* Destructive */
47
+ --destructive: oklch(0.55 0.2 25);
48
+ --destructive-foreground: oklch(0.95 0.02 85);
49
+
50
+ /* Borders and inputs */
51
+ --border: oklch(0.28 0.03 55);
52
+ --input: oklch(0.22 0.02 55);
53
+ --ring: oklch(0.65 0.14 55);
54
+
55
+ /* Chart colors - pastry inspired */
56
+ --chart-1: oklch(0.65 0.14 55);
57
+ --chart-2: oklch(0.72 0.12 85);
58
+ --chart-3: oklch(0.55 0.15 45);
59
+ --chart-4: oklch(0.6 0.1 30);
60
+ --chart-5: oklch(0.5 0.08 150);
61
+
62
+ --radius: 0.625rem;
63
+
64
+ /* Popover */
65
+ --popover: oklch(0.17 0.015 55);
66
+ --popover-foreground: oklch(0.93 0.02 85);
67
+
68
+ /* Sidebar */
69
+ --sidebar: oklch(0.15 0.01 55);
70
+ --sidebar-foreground: oklch(0.93 0.02 85);
71
+ --sidebar-primary: oklch(0.65 0.14 55);
72
+ --sidebar-primary-foreground: oklch(0.95 0.02 85);
73
+ --sidebar-accent: oklch(0.22 0.02 55);
74
+ --sidebar-accent-foreground: oklch(0.93 0.02 85);
75
+ --sidebar-border: oklch(0.28 0.03 55);
76
+ --sidebar-ring: oklch(0.65 0.14 55);
77
+ }
78
+
79
+ @theme inline {
80
+ --font-display: 'Playfair Display', Georgia, serif;
81
+ --font-body: 'Cormorant Garamond', Georgia, serif;
82
+
83
+ --color-background: var(--background);
84
+ --color-foreground: var(--foreground);
85
+ --color-card: var(--card);
86
+ --color-card-foreground: var(--card-foreground);
87
+ --color-popover: var(--popover);
88
+ --color-popover-foreground: var(--popover-foreground);
89
+ --color-primary: var(--primary);
90
+ --color-primary-foreground: var(--primary-foreground);
91
+ --color-secondary: var(--secondary);
92
+ --color-secondary-foreground: var(--secondary-foreground);
93
+ --color-muted: var(--muted);
94
+ --color-muted-foreground: var(--muted-foreground);
95
+ --color-accent: var(--accent);
96
+ --color-accent-foreground: var(--accent-foreground);
97
+ --color-destructive: var(--destructive);
98
+ --color-destructive-foreground: var(--destructive-foreground);
99
+ --color-border: var(--border);
100
+ --color-input: var(--input);
101
+ --color-ring: var(--ring);
102
+ --color-chart-1: var(--chart-1);
103
+ --color-chart-2: var(--chart-2);
104
+ --color-chart-3: var(--chart-3);
105
+ --color-chart-4: var(--chart-4);
106
+ --color-chart-5: var(--chart-5);
107
+ --radius-sm: calc(var(--radius) - 4px);
108
+ --radius-md: calc(var(--radius) - 2px);
109
+ --radius-lg: var(--radius);
110
+ --radius-xl: calc(var(--radius) + 4px);
111
+ --color-sidebar: var(--sidebar);
112
+ --color-sidebar-foreground: var(--sidebar-foreground);
113
+ --color-sidebar-primary: var(--sidebar-primary);
114
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
115
+ --color-sidebar-accent: var(--sidebar-accent);
116
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
117
+ --color-sidebar-border: var(--sidebar-border);
118
+ --color-sidebar-ring: var(--sidebar-ring);
119
+
120
+ /* Custom colors for the theme */
121
+ --color-copper: oklch(0.65 0.14 55);
122
+ --color-copper-light: oklch(0.75 0.12 55);
123
+ --color-copper-dark: oklch(0.5 0.15 50);
124
+ --color-gold: oklch(0.72 0.12 85);
125
+ --color-gold-light: oklch(0.82 0.1 85);
126
+ --color-cream: oklch(0.93 0.02 85);
127
+ --color-cream-dark: oklch(0.85 0.03 80);
128
+ --color-charcoal: oklch(0.13 0.01 60);
129
+ --color-charcoal-light: oklch(0.2 0.015 55);
130
+ }
131
+
132
+ @layer base {
133
+ * {
134
+ @apply border-border outline-ring/50;
135
+ }
136
+ body {
137
+ @apply bg-background text-foreground;
138
+ }
139
+ }
140
+
141
+ /* Custom utilities for the theme */
142
+ .font-display {
143
+ font-family: 'Playfair Display', Georgia, serif;
144
+ }
145
+
146
+ .font-body {
147
+ font-family: 'Cormorant Garamond', Georgia, serif;
148
+ }
149
+
150
+ /* Elegant grain texture overlay */
151
+ .grain-texture {
152
+ position: relative;
153
+ }
154
+
155
+ .grain-texture::after {
156
+ content: '';
157
+ position: absolute;
158
+ inset: 0;
159
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%' height='100%' filter='url(%23noise)' opacity='0.08'/%3E%3C/svg%3E");
160
+ pointer-events: none;
161
+ opacity: 0.4;
162
+ }
163
+
164
+ /* Golden border accent */
165
+ .border-gold-accent {
166
+ border-image: linear-gradient(
167
+ 135deg,
168
+ oklch(0.72 0.12 85),
169
+ oklch(0.65 0.14 55),
170
+ oklch(0.72 0.12 85)
171
+ )
172
+ 1;
173
+ }
174
+
175
+ /* Copper glow effect */
176
+ .glow-copper {
177
+ box-shadow:
178
+ 0 0 20px oklch(0.65 0.14 55 / 0.3),
179
+ 0 0 40px oklch(0.65 0.14 55 / 0.1);
180
+ }
181
+
182
+ /* Elegant card hover effect */
183
+ .card-hover {
184
+ transition:
185
+ transform 0.3s ease,
186
+ box-shadow 0.3s ease;
187
+ }
188
+
189
+ .card-hover:hover {
190
+ transform: translateY(-4px);
191
+ box-shadow:
192
+ 0 12px 40px oklch(0 0 0 / 0.4),
193
+ 0 0 20px oklch(0.65 0.14 55 / 0.15);
194
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "Events",
3
+ "description": "A pastry conference website with speakers, sessions, schedule, and AI assistant built with content-collections and TanStack Start for Netlify.",
4
+ "phase": "example",
5
+ "modes": ["file-router"],
6
+ "type": "example",
7
+ "priority": 10,
8
+ "link": "",
9
+ "routes": [
10
+ {
11
+ "url": "/",
12
+ "path": "src/routes/index.tsx",
13
+ "jsName": "EventsHome"
14
+ },
15
+ {
16
+ "url": "/schedule",
17
+ "path": "src/routes/schedule.index.tsx",
18
+ "name": "Schedule",
19
+ "jsName": "SchedulePage"
20
+ },
21
+ {
22
+ "url": "/speakers",
23
+ "path": "src/routes/speakers.index.tsx",
24
+ "name": "Speakers",
25
+ "jsName": "SpeakersPage"
26
+ },
27
+ {
28
+ "url": "/speakers/$slug",
29
+ "path": "src/routes/speakers.$slug.tsx",
30
+ "jsName": "SpeakerDetail"
31
+ },
32
+ {
33
+ "url": "/talks",
34
+ "path": "src/routes/talks.index.tsx",
35
+ "name": "Sessions",
36
+ "jsName": "TalksPage"
37
+ },
38
+ {
39
+ "url": "/talks/$slug",
40
+ "path": "src/routes/talks.$slug.tsx",
41
+ "jsName": "TalkDetail"
42
+ },
43
+ {
44
+ "url": "/api/remy-chat",
45
+ "path": "src/routes/api.remy-chat.ts",
46
+ "jsName": "RemyChatAPI"
47
+ }
48
+ ],
49
+ "integrations": [
50
+ {
51
+ "type": "header-user",
52
+ "path": "src/components/RemyButton",
53
+ "jsName": "RemyButton"
54
+ },
55
+ {
56
+ "type": "vite-plugin",
57
+ "import": "import contentCollections from '@content-collections/vite'",
58
+ "code": "contentCollections()"
59
+ }
60
+ ],
61
+ "dependsOn": [],
62
+ "variables": []
63
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "dependencies": {
3
+ "@tanstack/ai": "latest",
4
+ "@tanstack/ai-anthropic": "latest",
5
+ "@tanstack/ai-client": "latest",
6
+ "@tanstack/ai-gemini": "latest",
7
+ "@tanstack/ai-ollama": "latest",
8
+ "@tanstack/ai-openai": "latest",
9
+ "@tanstack/ai-react": "latest",
10
+ "@tanstack/store": "^0.7.0",
11
+ "class-variance-authority": "^0.7.1",
12
+ "clsx": "^2.1.1",
13
+ "marked": "^15.0.8",
14
+ "streamdown": "^1.6.5",
15
+ "tailwind-merge": "^3.0.2",
16
+ "tailwindcss-animate": "^1.0.7",
17
+ "zod": "^4.3.5"
18
+ },
19
+ "devDependencies": {
20
+ "@content-collections/core": "^0.13.1",
21
+ "@content-collections/vite": "^0.2.8"
22
+ }
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/cta-framework-react-cra",
3
- "version": "0.44.3",
3
+ "version": "0.45.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.44.3"
26
+ "@tanstack/cta-engine": "0.45.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^24.6.0",
@@ -1,78 +0,0 @@
1
- export type Provider = 'openai' | 'anthropic' | 'gemini' | 'ollama'
2
-
3
- export interface ModelOption {
4
- provider: Provider
5
- model: string
6
- label: string
7
- }
8
-
9
- export const MODEL_OPTIONS: Array<ModelOption> = [
10
- // OpenAI models
11
- { provider: 'openai', model: 'gpt-4o', label: 'OpenAI - GPT-4o' },
12
- { provider: 'openai', model: 'gpt-4o-mini', label: 'OpenAI - GPT-4o Mini' },
13
-
14
- // Anthropic models
15
- {
16
- provider: 'anthropic',
17
- model: 'claude-haiku-4-5',
18
- label: 'Anthropic - Claude Haiku 4.5',
19
- },
20
- {
21
- provider: 'anthropic',
22
- model: 'claude-sonnet-4-5-20250929',
23
- label: 'Anthropic - Claude Sonnet 4.5',
24
- },
25
-
26
- // Gemini models
27
- {
28
- provider: 'gemini',
29
- model: 'gemini-2.0-flash-exp',
30
- label: 'Gemini - 2.0 Flash',
31
- },
32
-
33
- // Ollama models
34
- { provider: 'ollama', model: 'mistral:7b', label: 'Ollama - Mistral 7B' },
35
- ]
36
-
37
- const STORAGE_KEY = 'tanstack-ai-model-preference'
38
-
39
- export function getStoredModelPreference(): ModelOption | null {
40
- if (typeof window === 'undefined') return null
41
- try {
42
- const stored = localStorage.getItem(STORAGE_KEY)
43
- if (stored) {
44
- const parsed = JSON.parse(stored)
45
- // Validate that the stored option still exists in MODEL_OPTIONS
46
- const found = MODEL_OPTIONS.find(
47
- (o) => o.provider === parsed.provider && o.model === parsed.model,
48
- )
49
- if (found) return found
50
- }
51
- } catch {
52
- // Ignore storage errors
53
- }
54
- return null
55
- }
56
-
57
- export function setStoredModelPreference(option: ModelOption): void {
58
- if (typeof window === 'undefined') return
59
- try {
60
- localStorage.setItem(STORAGE_KEY, JSON.stringify(option))
61
- } catch {
62
- // Ignore storage errors
63
- }
64
- }
65
-
66
- export function getDefaultModelOption(): ModelOption {
67
- return getStoredModelPreference() || MODEL_OPTIONS[0]
68
- }
69
-
70
- export function getModelOptionsForProvider(provider: Provider): ModelOption[] {
71
- return MODEL_OPTIONS.filter((o) => o.provider === provider)
72
- }
73
-
74
- export function getAvailableModelOptions(
75
- availableProviders: Provider[],
76
- ): ModelOption[] {
77
- return MODEL_OPTIONS.filter((o) => availableProviders.includes(o.provider))
78
- }
@@ -1,55 +0,0 @@
1
- import type { Provider } from './model-selection'
2
-
3
- export interface VendorCapabilities {
4
- chat: boolean
5
- structured: boolean
6
- image: boolean
7
- transcription: boolean
8
- tts: boolean
9
- }
10
-
11
- export const VENDOR_CAPABILITIES: Record<Provider, VendorCapabilities> = {
12
- openai: {
13
- chat: true,
14
- structured: true,
15
- image: true,
16
- transcription: true,
17
- tts: true,
18
- },
19
- anthropic: {
20
- chat: true,
21
- structured: true,
22
- image: false,
23
- transcription: false,
24
- tts: false,
25
- },
26
- gemini: {
27
- chat: true,
28
- structured: true,
29
- image: false,
30
- transcription: false,
31
- tts: false,
32
- },
33
- ollama: {
34
- chat: true,
35
- structured: true,
36
- image: false,
37
- transcription: false,
38
- tts: false,
39
- },
40
- }
41
-
42
- export function hasCapability(
43
- provider: Provider,
44
- capability: keyof VendorCapabilities,
45
- ): boolean {
46
- return VENDOR_CAPABILITIES[provider]?.[capability] ?? false
47
- }
48
-
49
- export function getProvidersWithCapability(
50
- capability: keyof VendorCapabilities,
51
- ): Provider[] {
52
- return (Object.keys(VENDOR_CAPABILITIES) as Provider[]).filter(
53
- (provider) => VENDOR_CAPABILITIES[provider][capability],
54
- )
55
- }
@@ -1,35 +0,0 @@
1
- import { createFileRoute } from '@tanstack/react-router'
2
- import type { Provider } from '@/lib/model-selection'
3
-
4
- export const Route = createFileRoute('/demo/api/available-providers')({
5
- server: {
6
- handlers: {
7
- GET: async () => {
8
- const available: Provider[] = []
9
-
10
- if (process.env.OPENAI_API_KEY) {
11
- available.push('openai')
12
- }
13
- if (process.env.ANTHROPIC_API_KEY) {
14
- available.push('anthropic')
15
- }
16
- if (process.env.GEMINI_API_KEY) {
17
- available.push('gemini')
18
- }
19
- // Ollama is always available (local, no key needed)
20
- available.push('ollama')
21
-
22
- return new Response(
23
- JSON.stringify({
24
- providers: available,
25
- hasOpenAI: available.includes('openai'),
26
- }),
27
- {
28
- status: 200,
29
- headers: { 'Content-Type': 'application/json' },
30
- },
31
- )
32
- },
33
- },
34
- },
35
- })
@@ -1,168 +0,0 @@
1
- import { createFileRoute } from "@tanstack/react-router";
2
- import { chat, createChatOptions } from "@tanstack/ai";
3
- import { anthropicText } from "@tanstack/ai-anthropic";
4
- import { geminiText } from "@tanstack/ai-gemini";
5
- import { openaiText } from "@tanstack/ai-openai";
6
- import { ollamaText } from "@tanstack/ai-ollama";
7
- import { z } from "zod";
8
-
9
- import type { Provider } from "@/lib/model-selection";
10
-
11
- // Schema for structured recipe output
12
- const RecipeSchema = z.object({
13
- name: z.string().describe("The name of the recipe"),
14
- description: z.string().describe("A brief description of the dish"),
15
- prepTime: z.string().describe('Preparation time (e.g., "15 minutes")'),
16
- cookTime: z.string().describe('Cooking time (e.g., "30 minutes")'),
17
- servings: z.number().describe("Number of servings"),
18
- difficulty: z.enum(["easy", "medium", "hard"]).describe("Difficulty level"),
19
- ingredients: z
20
- .array(
21
- z.object({
22
- item: z.string().describe("Ingredient name"),
23
- amount: z.string().describe('Amount needed (e.g., "2 cups")'),
24
- notes: z.string().optional().describe("Optional preparation notes"),
25
- })
26
- )
27
- .describe("List of ingredients"),
28
- instructions: z
29
- .array(z.string())
30
- .describe("Step-by-step cooking instructions"),
31
- tips: z.array(z.string()).optional().describe("Optional cooking tips"),
32
- nutritionPerServing: z
33
- .object({
34
- calories: z.number().optional(),
35
- protein: z.string().optional(),
36
- carbs: z.string().optional(),
37
- fat: z.string().optional(),
38
- })
39
- .optional()
40
- .describe("Nutritional information per serving"),
41
- });
42
-
43
- export type Recipe = z.infer<typeof RecipeSchema>;
44
-
45
- export const Route = createFileRoute("/demo/api/structured")({
46
- server: {
47
- handlers: {
48
- POST: async ({ request }) => {
49
- const body = await request.json();
50
- const { recipeName, mode = "structured" } = body;
51
- const data = body.data || {};
52
- const provider: Provider = data.provider || body.provider || "openai";
53
- const model: string = data.model || body.model || "gpt-4o";
54
-
55
- if (!recipeName || recipeName.trim().length === 0) {
56
- return new Response(
57
- JSON.stringify({
58
- error: "Recipe name is required",
59
- }),
60
- {
61
- status: 400,
62
- headers: { "Content-Type": "application/json" },
63
- }
64
- );
65
- }
66
-
67
- try {
68
- // Pre-define typed adapter configurations
69
- const adapterConfig = {
70
- anthropic: () =>
71
- createChatOptions({
72
- adapter: anthropicText(
73
- (model || "claude-sonnet-4-5-20250929") as any
74
- ),
75
- }),
76
- gemini: () =>
77
- createChatOptions({
78
- adapter: geminiText((model || "gemini-2.0-flash-exp") as any),
79
- }),
80
- ollama: () =>
81
- createChatOptions({
82
- adapter: ollamaText((model || "mistral:7b") as any),
83
- }),
84
- openai: () =>
85
- createChatOptions({
86
- adapter: openaiText((model || "gpt-4o") as any),
87
- }),
88
- };
89
-
90
- const options = adapterConfig[provider]();
91
-
92
- if (mode === "structured") {
93
- // Structured output mode - returns validated object
94
- const result = await chat({
95
- ...options,
96
- messages: [
97
- {
98
- role: "user",
99
- content: `Generate a complete recipe for: ${recipeName}. Include all ingredients with amounts, step-by-step instructions, prep/cook times, and difficulty level.`,
100
- },
101
- ],
102
- outputSchema: RecipeSchema,
103
- } as any);
104
-
105
- return new Response(
106
- JSON.stringify({
107
- mode: "structured",
108
- recipe: result,
109
- provider,
110
- model,
111
- }),
112
- {
113
- status: 200,
114
- headers: { "Content-Type": "application/json" },
115
- }
116
- );
117
- } else {
118
- // One-shot markdown mode - returns text
119
- const markdown = await chat({
120
- ...options,
121
- stream: false,
122
- messages: [
123
- {
124
- role: "user",
125
- content: `Generate a complete recipe for: ${recipeName}.
126
-
127
- Format the recipe in beautiful markdown with:
128
- - A title with the recipe name
129
- - A brief description
130
- - Prep time, cook time, and servings
131
- - Ingredients list with amounts
132
- - Numbered step-by-step instructions
133
- - Optional tips section
134
- - Nutritional info if applicable
135
-
136
- Make it detailed and easy to follow.`,
137
- },
138
- ],
139
- } as any);
140
-
141
- return new Response(
142
- JSON.stringify({
143
- mode: "oneshot",
144
- markdown,
145
- provider,
146
- model,
147
- }),
148
- {
149
- status: 200,
150
- headers: { "Content-Type": "application/json" },
151
- }
152
- );
153
- }
154
- } catch (error: any) {
155
- return new Response(
156
- JSON.stringify({
157
- error: error.message || "An error occurred",
158
- }),
159
- {
160
- status: 500,
161
- headers: { "Content-Type": "application/json" },
162
- }
163
- );
164
- }
165
- },
166
- },
167
- },
168
- });