@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.
- package/{examples/tanchat/assets/src/components/example-AIAssistant.tsx → add-ons/ai/assets/src/components/demo-AIAssistant.tsx} +6 -8
- package/{examples/tanchat/assets/src/components/example-GuitarRecommendation.tsx → add-ons/ai/assets/src/components/demo-GuitarRecommendation.tsx} +2 -2
- package/{examples/tanchat/assets/src/lib/example.ai-hook.ts → add-ons/ai/assets/src/lib/demo-ai-hook.ts} +9 -9
- package/{examples/tanchat/assets/src/lib/example.guitar-tools.ts → add-ons/ai/assets/src/lib/demo-guitar-tools.ts} +1 -1
- package/{examples/tanchat/assets/src/routes/demo/tanchat.tsx → add-ons/ai/assets/src/routes/demo/ai-chat.tsx} +28 -148
- package/{examples/tanchat/assets/src/routes/demo/image.tsx → add-ons/ai/assets/src/routes/demo/ai-image.tsx} +2 -50
- package/add-ons/ai/assets/src/routes/demo/ai-structured.tsx +310 -0
- package/{examples/tanchat/assets/src/routes/demo/api.tanchat.ts → add-ons/ai/assets/src/routes/demo/api.ai.chat.ts} +16 -6
- package/{examples/tanchat/assets/src/routes/demo/api.image.ts → add-ons/ai/assets/src/routes/demo/api.ai.image.ts} +3 -5
- package/add-ons/ai/assets/src/routes/demo/api.ai.structured.ts +136 -0
- package/add-ons/ai/assets/src/routes/demo/api.ai.transcription.ts +89 -0
- package/{examples/tanchat/assets/src/routes/demo/api.tts.ts → add-ons/ai/assets/src/routes/demo/api.ai.tts.ts} +1 -1
- package/{examples/tanchat/assets/src/routes/example.guitars → add-ons/ai/assets/src/routes/demo/guitars}/$guitarId.tsx +3 -2
- package/{examples/tanchat/assets/src/routes/example.guitars → add-ons/ai/assets/src/routes/demo/guitars}/index.tsx +3 -2
- package/add-ons/ai/info.json +46 -0
- package/{examples/tanchat → add-ons/ai}/package.json +1 -1
- package/examples/events/README.md +110 -0
- package/examples/events/assets/content/speakers/andre-costa.md +22 -0
- package/examples/events/assets/content/speakers/hans-mueller.md +22 -0
- package/examples/events/assets/content/speakers/isabella-martinez.md +22 -0
- package/examples/events/assets/content/speakers/kenji-nakamura.md +22 -0
- package/examples/events/assets/content/speakers/marie-dubois.md +20 -0
- package/examples/events/assets/content/speakers/priya-sharma.md +22 -0
- package/examples/events/assets/content/talks/croissant-lamination-secrets.md +39 -0
- package/examples/events/assets/content/talks/french-macaron-mastery.md +39 -0
- package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md +39 -0
- package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md +39 -0
- package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md +36 -0
- package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md +32 -0
- package/examples/events/assets/content/talks/the-science-of-sugar.md +39 -0
- package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md +39 -0
- package/examples/events/assets/content-collections.ts +56 -0
- package/examples/events/assets/public/background-1.jpg +0 -0
- package/examples/events/assets/public/background-2.jpg +0 -0
- package/examples/events/assets/public/background-3.jpg +0 -0
- package/examples/events/assets/public/background-4.jpg +0 -0
- package/examples/events/assets/public/conference-logo.png +0 -0
- package/examples/events/assets/public/favicon.ico +0 -0
- package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
- package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
- package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
- package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
- package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
- package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
- package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
- package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
- package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
- package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
- package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
- package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
- package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
- package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
- package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
- package/examples/events/assets/public/tanstack-word-logo-white.svg +1 -0
- package/examples/events/assets/src/components/HeroCarousel.tsx +61 -0
- package/examples/events/assets/src/components/RemyAssistant.tsx +207 -0
- package/examples/events/assets/src/components/RemyButton.tsx +18 -0
- package/examples/events/assets/src/components/SpeakerCard.tsx +67 -0
- package/examples/events/assets/src/components/TalkCard.tsx +77 -0
- package/examples/events/assets/src/components/ui/card.tsx +92 -0
- package/examples/events/assets/src/lib/conference-ai-hook.ts +26 -0
- package/examples/events/assets/src/lib/conference-tools.ts +210 -0
- package/examples/events/assets/src/lib/utils.ts +6 -0
- package/examples/events/assets/src/routes/api.remy-chat.ts +121 -0
- package/examples/events/assets/src/routes/index.tsx +192 -0
- package/examples/events/assets/src/routes/schedule.index.tsx +274 -0
- package/examples/events/assets/src/routes/speakers.$slug.tsx +122 -0
- package/examples/events/assets/src/routes/speakers.index.tsx +40 -0
- package/examples/events/assets/src/routes/talks.$slug.tsx +116 -0
- package/examples/events/assets/src/routes/talks.index.tsx +40 -0
- package/examples/events/assets/src/styles.css +194 -0
- package/examples/events/info.json +63 -0
- package/examples/events/package.json +23 -0
- package/package.json +2 -2
- package/examples/tanchat/assets/src/lib/model-selection.ts +0 -78
- package/examples/tanchat/assets/src/lib/vendor-capabilities.ts +0 -55
- package/examples/tanchat/assets/src/routes/demo/api.available-providers.ts +0 -35
- package/examples/tanchat/assets/src/routes/demo/api.structured.ts +0 -168
- package/examples/tanchat/assets/src/routes/demo/api.transcription.ts +0 -89
- package/examples/tanchat/assets/src/routes/demo/structured.tsx +0 -460
- package/examples/tanchat/info.json +0 -46
- /package/{examples/tanchat → add-ons/ai}/README.md +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/_dot_env.local.append +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-flowers.jpg +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-motherboard.jpg +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-racing.jpg +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-steamer-trunk.jpg +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-superhero.jpg +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-traveling.jpg +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-guitar-video-games.jpg +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/public/example-ukelele-tanstack.jpg +0 -0
- /package/{examples/tanchat/assets/src/data/example-guitars.ts → add-ons/ai/assets/src/data/demo-guitars.ts} +0 -0
- /package/{examples/tanchat/assets/src/hooks/useAudioRecorder.ts → add-ons/ai/assets/src/hooks/demo-useAudioRecorder.ts} +0 -0
- /package/{examples/tanchat/assets/src/hooks/useTTS.ts → add-ons/ai/assets/src/hooks/demo-useTTS.ts} +0 -0
- /package/{examples/tanchat → add-ons/ai}/assets/src/lib/ai-devtools.tsx +0 -0
- /package/{examples/tanchat/assets/src/routes/demo/tanchat.css → add-ons/ai/assets/src/routes/demo/ai-chat.css} +0 -0
- /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.
|
|
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.
|
|
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
|
-
});
|