kaddidlehopper 0.1.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 (183) hide show
  1. package/CONTEXT.md +139 -0
  2. package/README.md +47 -0
  3. package/add-ons/ai/README.md +34 -0
  4. package/add-ons/ai/assets/_dot_env.local.append +13 -0
  5. package/add-ons/ai/assets/src/components/AIAssistant.tsx +149 -0
  6. package/add-ons/ai/assets/src/lib/ai-hook.ts +21 -0
  7. package/add-ons/ai/assets/src/lib/weather-tools.ts +30 -0
  8. package/add-ons/ai/assets/src/routes/api.chat.ts +94 -0
  9. package/add-ons/ai/assets/src/routes/chat.css +175 -0
  10. package/add-ons/ai/assets/src/routes/chat.tsx +141 -0
  11. package/add-ons/ai/info.json +27 -0
  12. package/add-ons/ai/package.json +17 -0
  13. package/add-ons/ai/small-logo.svg +8 -0
  14. package/dist/cli.js +251 -0
  15. package/dist/index.js +33 -0
  16. package/dist/types/cli.d.ts +8 -0
  17. package/dist/types/index.d.ts +2 -0
  18. package/dist/types/types.d.ts +14 -0
  19. package/dist/types.js +1 -0
  20. package/examples/blog/README.md +60 -0
  21. package/examples/blog/assets/content/posts/beach.md +12 -0
  22. package/examples/blog/assets/content/posts/jungle.md.ejs +12 -0
  23. package/examples/blog/assets/content/posts/mountains.md.ejs +12 -0
  24. package/examples/blog/assets/content/posts/snorkeling.md.ejs +12 -0
  25. package/examples/blog/assets/content/posts/waterfall.md.ejs +12 -0
  26. package/examples/blog/assets/content-collections.ts +30 -0
  27. package/examples/blog/assets/public/beach.jpg +0 -0
  28. package/examples/blog/assets/public/jungle.jpg +0 -0
  29. package/examples/blog/assets/public/mountains.jpg +0 -0
  30. package/examples/blog/assets/public/snorkeling.jpg +0 -0
  31. package/examples/blog/assets/public/waterfall.jpg +0 -0
  32. package/examples/blog/assets/src/components/Header.tsx +52 -0
  33. package/examples/blog/assets/src/components/VacayAssistant.tsx +205 -0
  34. package/examples/blog/assets/src/components/blog-posts.tsx +78 -0
  35. package/examples/blog/assets/src/components/ui/card.tsx +92 -0
  36. package/examples/blog/assets/src/lib/blog-ai-hook.ts +25 -0
  37. package/examples/blog/assets/src/lib/blog-tools.ts +111 -0
  38. package/examples/blog/assets/src/lib/utils.ts +6 -0
  39. package/examples/blog/assets/src/routes/__root.tsx +57 -0
  40. package/examples/blog/assets/src/routes/api.blog-chat.ts +117 -0
  41. package/examples/blog/assets/src/routes/category.$category.tsx +19 -0
  42. package/examples/blog/assets/src/routes/index.tsx +19 -0
  43. package/examples/blog/assets/src/routes/posts.$slug.tsx +63 -0
  44. package/examples/blog/assets/src/styles.css +138 -0
  45. package/examples/blog/info.json +43 -0
  46. package/examples/blog/package.json +23 -0
  47. package/examples/events/README.md +110 -0
  48. package/examples/events/assets/content/speakers/andre-costa.md +22 -0
  49. package/examples/events/assets/content/speakers/hans-mueller.md.ejs +22 -0
  50. package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +22 -0
  51. package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +22 -0
  52. package/examples/events/assets/content/speakers/marie-dubois.md.ejs +20 -0
  53. package/examples/events/assets/content/speakers/priya-sharma.md.ejs +22 -0
  54. package/examples/events/assets/content/talks/croissant-lamination-secrets.md +39 -0
  55. package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +39 -0
  56. package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +39 -0
  57. package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +39 -0
  58. package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +36 -0
  59. package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +32 -0
  60. package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +39 -0
  61. package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +39 -0
  62. package/examples/events/assets/content-collections.ts +56 -0
  63. package/examples/events/assets/public/background-1.jpg +0 -0
  64. package/examples/events/assets/public/background-2.jpg +0 -0
  65. package/examples/events/assets/public/background-3.jpg +0 -0
  66. package/examples/events/assets/public/background-4.jpg +0 -0
  67. package/examples/events/assets/public/conference-logo.png +0 -0
  68. package/examples/events/assets/public/favicon.ico +0 -0
  69. package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
  70. package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
  71. package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
  72. package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
  73. package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
  74. package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
  75. package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
  76. package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
  77. package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
  78. package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
  79. package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
  80. package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
  81. package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
  82. package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
  83. package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
  84. package/examples/events/assets/public/tanstack-word-logo-white.svg +1 -0
  85. package/examples/events/assets/src/components/Header.tsx +59 -0
  86. package/examples/events/assets/src/components/HeaderNav.tsx +67 -0
  87. package/examples/events/assets/src/components/HeroCarousel.tsx +61 -0
  88. package/examples/events/assets/src/components/RemyAssistant.tsx +207 -0
  89. package/examples/events/assets/src/components/SpeakerCard.tsx +67 -0
  90. package/examples/events/assets/src/components/TalkCard.tsx +77 -0
  91. package/examples/events/assets/src/components/ui/card.tsx +92 -0
  92. package/examples/events/assets/src/lib/conference-ai-hook.ts +26 -0
  93. package/examples/events/assets/src/lib/conference-tools.ts +210 -0
  94. package/examples/events/assets/src/lib/model-selection.ts +1 -0
  95. package/examples/events/assets/src/lib/utils.ts +6 -0
  96. package/examples/events/assets/src/routes/__root.tsx +70 -0
  97. package/examples/events/assets/src/routes/api.remy-chat.ts +119 -0
  98. package/examples/events/assets/src/routes/index.tsx +192 -0
  99. package/examples/events/assets/src/routes/schedule.index.tsx +274 -0
  100. package/examples/events/assets/src/routes/speakers.$slug.tsx +122 -0
  101. package/examples/events/assets/src/routes/speakers.index.tsx +40 -0
  102. package/examples/events/assets/src/routes/talks.$slug.tsx +116 -0
  103. package/examples/events/assets/src/routes/talks.index.tsx +40 -0
  104. package/examples/events/assets/src/styles.css +182 -0
  105. package/examples/events/info.json +74 -0
  106. package/examples/events/package.json +23 -0
  107. package/examples/marketing/README.md +60 -0
  108. package/examples/marketing/assets/public/logo.png +0 -0
  109. package/examples/marketing/assets/public/motorcycle-adventure.jpg +0 -0
  110. package/examples/marketing/assets/public/motorcycle-cruiser.jpg +0 -0
  111. package/examples/marketing/assets/public/motorcycle-scooter.jpg +0 -0
  112. package/examples/marketing/assets/public/motorcycle-sport.jpg +0 -0
  113. package/examples/marketing/assets/public/motorcycle-supersport.jpg +0 -0
  114. package/examples/marketing/assets/src/components/Header.tsx +36 -0
  115. package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +162 -0
  116. package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +53 -0
  117. package/examples/marketing/assets/src/data/motorcycles.ts.ejs +77 -0
  118. package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +24 -0
  119. package/examples/marketing/assets/src/lib/motorcycle-tools.ts +42 -0
  120. package/examples/marketing/assets/src/routes/__root.tsx +57 -0
  121. package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +78 -0
  122. package/examples/marketing/assets/src/routes/index.tsx +72 -0
  123. package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +56 -0
  124. package/examples/marketing/assets/src/store/motorcycle-assistant.ts +3 -0
  125. package/examples/marketing/assets/src/styles.css +212 -0
  126. package/examples/marketing/info.json +38 -0
  127. package/examples/marketing/package.json +14 -0
  128. package/examples/resume/README.md +82 -0
  129. package/examples/resume/assets/content/education/code-school.md +17 -0
  130. package/examples/resume/assets/content/jobs/freelance.md.ejs +13 -0
  131. package/examples/resume/assets/content/jobs/initech-junior.md +20 -0
  132. package/examples/resume/assets/content/jobs/initech-lead.md.ejs +29 -0
  133. package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +28 -0
  134. package/examples/resume/assets/content-collections.ts +36 -0
  135. package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
  136. package/examples/resume/assets/src/components/Header.tsx +33 -0
  137. package/examples/resume/assets/src/components/ResumeAssistant.tsx +193 -0
  138. package/examples/resume/assets/src/components/ui/badge.tsx +46 -0
  139. package/examples/resume/assets/src/components/ui/card.tsx +92 -0
  140. package/examples/resume/assets/src/components/ui/checkbox.tsx +30 -0
  141. package/examples/resume/assets/src/components/ui/hover-card.tsx +44 -0
  142. package/examples/resume/assets/src/components/ui/separator.tsx +26 -0
  143. package/examples/resume/assets/src/lib/resume-ai-hook.ts +21 -0
  144. package/examples/resume/assets/src/lib/resume-tools.ts +165 -0
  145. package/examples/resume/assets/src/lib/utils.ts +6 -0
  146. package/examples/resume/assets/src/routes/api.resume-chat.ts +110 -0
  147. package/examples/resume/assets/src/routes/index.tsx +220 -0
  148. package/examples/resume/assets/src/styles.css +138 -0
  149. package/examples/resume/info.json +25 -0
  150. package/examples/resume/package.json +26 -0
  151. package/package.json +39 -0
  152. package/project/base/_dot_claude/skills/content-collections/SKILL.md +505 -0
  153. package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +410 -0
  154. package/project/base/_dot_claude/skills/netlify-db/SKILL.md +424 -0
  155. package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +419 -0
  156. package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +243 -0
  157. package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +372 -0
  158. package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +421 -0
  159. package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +426 -0
  160. package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +493 -0
  161. package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +430 -0
  162. package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +445 -0
  163. package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +494 -0
  164. package/project/base/_dot_gitignore +8 -0
  165. package/project/base/netlify.toml +7 -0
  166. package/project/base/package.json +33 -0
  167. package/project/base/public/favicon.ico +0 -0
  168. package/project/base/public/tanstack-circle-logo.png +0 -0
  169. package/project/base/public/tanstack-word-logo-white.svg +1 -0
  170. package/project/base/src/components/Header.tsx +17 -0
  171. package/project/base/src/components/HeaderNav.tsx.ejs +179 -0
  172. package/project/base/src/router.tsx +15 -0
  173. package/project/base/src/routes/__root.tsx +57 -0
  174. package/project/base/src/routes/index.tsx +48 -0
  175. package/project/base/src/styles.css +15 -0
  176. package/project/base/tsconfig.json +28 -0
  177. package/project/base/vite.config.ts.ejs +25 -0
  178. package/project/packages.json +22 -0
  179. package/scripts/check-outdated-packages.js +421 -0
  180. package/src/cli.ts +343 -0
  181. package/src/index.ts +49 -0
  182. package/src/types.ts +15 -0
  183. package/tsconfig.json +17 -0
package/CONTEXT.md ADDED
@@ -0,0 +1,139 @@
1
+ # Netlify CTA - Project Context
2
+
3
+ ## Overview
4
+
5
+ `netlify-cta` is a custom CLI application that creates TanStack Start applications pre-configured for Netlify deployment. It's based on the `create-rwsdk` example pattern but builds TanStack Start apps instead of Redwood SDK apps.
6
+
7
+ ## What Was Built
8
+
9
+ ### CLI Structure
10
+
11
+ ```
12
+ ./
13
+ ├── package.json # CLI package (workspace:* deps)
14
+ ├── tsconfig.json # TypeScript config
15
+ ├── src/
16
+ │ └── index.ts # CLI entry point
17
+ ├── project/
18
+ │ ├── packages.json # Conditional dependencies
19
+ │ └── base/
20
+ │ ├── package.json # Base project dependencies
21
+ │ ├── vite.config.ts # Vite + Start + Netlify + Tailwind
22
+ │ ├── tsconfig.json # TypeScript config
23
+ │ ├── netlify.toml # Netlify deployment config
24
+ │ ├── _dot_gitignore # Git ignore (becomes .gitignore)
25
+ │ ├── public/
26
+ │ │ └── favicon.ico
27
+ │ └── src/
28
+ │ ├── styles.css # Tailwind CSS imports
29
+ │ ├── router.tsx # TanStack Router config
30
+ │ └── routes/
31
+ │ ├── __root.tsx # Root layout with SSR shell
32
+ │ └── index.tsx # Home page - "Hello from Netlify"
33
+ └── add-ons/
34
+ └── _dot_gitkeep # Placeholder for future add-ons
35
+ ```
36
+
37
+ ### Key Features
38
+
39
+ 1. **TanStack Start**: Full SSR support with `@tanstack/react-start`
40
+ 2. **Netlify Deployment**: Pre-configured with `@netlify/vite-plugin-tanstack-start`
41
+ 3. **Tailwind CSS**: Installed and configured out of the box
42
+ 4. **File-based Routing**: Uses TanStack Router's file-based routing (TypeScript enforced)
43
+ 5. **Minimal Setup**: Just a home route saying "Hello from Netlify" - no demo routes
44
+
45
+ ### Framework Registration
46
+
47
+ The CLI registers a framework called "Netlify TanStack Start" with:
48
+ - ID: `netlify-start`
49
+ - Single mode: `file-router` (TypeScript enforced)
50
+ - No customized UI (uses default CTA UI)
51
+
52
+ ## Usage
53
+
54
+ ## Dependencies
55
+
56
+ ### CLI Dependencies
57
+ - `@tanstack/cta-cli`: workspace:*
58
+ - `@tanstack/cta-engine`: workspace:*
59
+
60
+ ### Generated Project Dependencies
61
+ - `@tanstack/react-start`: ^1.132.0
62
+ - `@tanstack/react-router`: ^1.132.0
63
+ - `@netlify/vite-plugin-tanstack-start`: ^1.2.3
64
+ - `tailwindcss`: ^4.0.6
65
+ - `react`: ^19.2.0
66
+ - `vite`: ^7.1.7
67
+
68
+ ## Files Reference
69
+
70
+ ### src/index.ts
71
+ Registers the framework and starts the CLI. Key points:
72
+ - Scans `project/` directory for base files
73
+ - Scans `add-ons/` directory for add-ons (currently empty)
74
+ - Registers single "file-router" mode with forced TypeScript
75
+
76
+ ### project/base/vite.config.ts
77
+ Configures Vite with:
78
+ - `@tanstack/devtools-vite` - TanStack devtools
79
+ - `vite-tsconfig-paths` - Path alias support
80
+ - `@tailwindcss/vite` - Tailwind CSS
81
+ - `@netlify/vite-plugin-tanstack-start` - Netlify deployment
82
+ - `@tanstack/react-start/plugin/vite` - TanStack Start
83
+ - `@vitejs/plugin-react` - React support
84
+
85
+ ### project/base/src/routes/__root.tsx
86
+ Root layout using TanStack Start's `shellComponent` for SSR:
87
+ - Sets up HTML document structure
88
+ - Configures head meta tags
89
+ - Includes TanStack devtools
90
+ - Renders children with Scripts for hydration
91
+
92
+ ### project/base/src/routes/index.tsx
93
+ Simple home page with:
94
+ - Teal/cyan gradient background
95
+ - "Hello from Netlify" heading
96
+ - Links to TanStack Start and Netlify docs
97
+ - Edit instruction pointing to the file
98
+
99
+ ## Examples
100
+
101
+ Four example applications are included:
102
+
103
+ ### Blog Example (`examples/blog/`)
104
+ A Hawaii adventures travel blog built with content-collections and shadcn UI components.
105
+ - Content-collections for markdown blog posts
106
+ - Category-based navigation
107
+ - Postcard-style blog cards with beautiful UI
108
+ - Routes: `/`, `/posts/$slug`, `/category/$category`
109
+
110
+ ### Events Example (`examples/events/`)
111
+ A pastry conference website ("Haute Pâtisserie 2026") with speakers, sessions, schedule, and AI assistant.
112
+ - Content-collections for speakers and talks markdown content
113
+ - Conference schedule with day-by-day timeline
114
+ - AI-powered chat assistant (Remy) for conference navigation
115
+ - Beautiful dark theme with Playfair Display font and copper/gold accents
116
+ - Routes: `/`, `/schedule`, `/speakers`, `/speakers/$slug`, `/talks`, `/talks/$slug`, `/api/remy-chat`
117
+
118
+ ### Marketing Example (`examples/marketing/`)
119
+ An AI-powered motorcycle marketing site with TanStack AI chat assistant.
120
+ - TanStack AI for intelligent chat
121
+ - TanStack Store for state management
122
+ - Motorcycle product catalog
123
+ - AI-powered product recommendations
124
+ - Routes: `/`, `/motorcycles/$motorcycleId`, `/api/motorcycle-chat`
125
+
126
+ ### Resume Example (`examples/resume/`)
127
+ A professional resume template with content-collections and shadcn UI components.
128
+ - Content-collections for jobs and education
129
+ - Interactive skills filter sidebar
130
+ - Multiple shadcn UI components (badge, card, checkbox, hover-card, separator)
131
+ - Routes: `/`
132
+
133
+ ## Future Enhancements
134
+
135
+ Potential additions:
136
+ - Add-ons for common Netlify features (Functions, Edge Functions, Blobs)
137
+ - Authentication add-ons (Netlify Identity)
138
+ - Database add-ons (Netlify DB, Supabase, etc.)
139
+ - Customized UI with Netlify branding
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # Netlify CTA
2
+
3
+ Create TanStack Start applications for Netlify.
4
+
5
+ ## Installation
6
+
7
+ Install dependencies using pnpm:
8
+
9
+ ```bash
10
+ pnpm install
11
+ ```
12
+
13
+ ## Build
14
+
15
+ Build the project by compiling TypeScript:
16
+
17
+ ```bash
18
+ pnpm build
19
+ ```
20
+
21
+ Or using npm:
22
+
23
+ ```bash
24
+ npm run build
25
+ ```
26
+
27
+ This will compile the TypeScript source files from `src/` to `dist/`.
28
+
29
+ ## Running
30
+
31
+ After building, you can run the CLI tool using:
32
+
33
+ ```bash
34
+ node dist/index.js
35
+ ```
36
+
37
+ Or if running from a different directory context:
38
+
39
+ ```bash
40
+ node ../netlify-cta/dist/index.js
41
+ ```
42
+
43
+ If you need to specify the package manager user agent:
44
+
45
+ ```bash
46
+ npm_config_user_agent=pnpm node ../netlify-cta/dist/index.js
47
+ ```
@@ -0,0 +1,34 @@
1
+ # AI Chat Add-on
2
+
3
+ This add-on provides AI chat capabilities with multi-vendor support for your application.
4
+
5
+ ## Features
6
+
7
+ - **Chat Page** (`/chat`) - Full-featured chat interface with model selection
8
+ - **AI Assistant** - Pop-up assistant available throughout the app
9
+ - **Multi-vendor Support** - Works with OpenAI, Anthropic, Gemini, and Ollama
10
+
11
+ ## Setup
12
+
13
+ Add your API keys to `.env.local`:
14
+
15
+ ```bash
16
+ # Anthropic (Claude)
17
+ ANTHROPIC_API_KEY=your-key-here
18
+
19
+ # OpenAI (GPT-4o)
20
+ OPENAI_API_KEY=your-key-here
21
+
22
+ # Google Gemini
23
+ GEMINI_API_KEY=your-key-here
24
+ ```
25
+
26
+ **Note:** Ollama is available locally without an API key. Install from [ollama.ai](https://ollama.ai).
27
+
28
+ ## Usage
29
+
30
+ The chat page is available at `/chat`. The AI Assistant button is added to the header and provides a pop-up chat interface available on any page.
31
+
32
+ ## Dependencies
33
+
34
+ This add-on requires the `start` and `store` add-ons.
@@ -0,0 +1,13 @@
1
+ # AI Provider API Keys (add the ones you want to use)
2
+ # At least one key is required for AI chat to work
3
+
4
+ # Anthropic (Claude)
5
+ ANTHROPIC_API_KEY=
6
+
7
+ # OpenAI (GPT-4o)
8
+ OPENAI_API_KEY=
9
+
10
+ # Google Gemini
11
+ GEMINI_API_KEY=
12
+
13
+ # Note: Ollama is available locally without an API key
@@ -0,0 +1,149 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { useStore } from "@tanstack/react-store";
3
+ import { Store } from "@tanstack/store";
4
+
5
+ import { Send, X, ChevronRight } from "lucide-react";
6
+ import { Streamdown } from "streamdown";
7
+
8
+ import { useAIChat } from "@/lib/ai-hook";
9
+ import type { ChatMessages } from "@/lib/ai-hook";
10
+
11
+ export const showAIAssistant = new Store(false);
12
+
13
+ function Messages({ messages }: { messages: ChatMessages }) {
14
+ const messagesContainerRef = useRef<HTMLDivElement>(null);
15
+
16
+ useEffect(() => {
17
+ if (messagesContainerRef.current) {
18
+ messagesContainerRef.current.scrollTop =
19
+ messagesContainerRef.current.scrollHeight;
20
+ }
21
+ }, [messages]);
22
+
23
+ if (!messages.length) {
24
+ return (
25
+ <div className="flex-1 flex items-center justify-center text-gray-400 text-sm">
26
+ Ask me anything! I'm here to help.
27
+ </div>
28
+ );
29
+ }
30
+
31
+ return (
32
+ <div ref={messagesContainerRef} className="flex-1 overflow-y-auto">
33
+ {messages.map(({ id, role, parts }) => (
34
+ <div
35
+ key={id}
36
+ className={`py-3 ${
37
+ role === "assistant"
38
+ ? "bg-linear-to-r from-orange-500/5 to-red-600/5"
39
+ : "bg-transparent"
40
+ }`}
41
+ >
42
+ {parts.map((part, index) => {
43
+ if (part.type === "text" && part.content) {
44
+ return (
45
+ <div key={index} className="flex items-start gap-2 px-4">
46
+ {role === "assistant" ? (
47
+ <div className="w-6 h-6 rounded-lg bg-linear-to-r from-orange-500 to-red-600 flex items-center justify-center text-xs font-medium text-white flex-shrink-0">
48
+ AI
49
+ </div>
50
+ ) : (
51
+ <div className="w-6 h-6 rounded-lg bg-gray-700 flex items-center justify-center text-xs font-medium text-white flex-shrink-0">
52
+ Y
53
+ </div>
54
+ )}
55
+ <div className="flex-1 min-w-0 text-white prose dark:prose-invert max-w-none prose-sm">
56
+ <Streamdown>{part.content}</Streamdown>
57
+ </div>
58
+ </div>
59
+ );
60
+ }
61
+ return null;
62
+ })}
63
+ </div>
64
+ ))}
65
+ </div>
66
+ );
67
+ }
68
+
69
+ export default function AIAssistant() {
70
+ const isOpen = useStore(showAIAssistant);
71
+ const { messages, sendMessage } = useAIChat();
72
+ const [input, setInput] = useState("");
73
+
74
+ return (
75
+ <div className="relative z-50">
76
+ <button
77
+ onClick={() => showAIAssistant.setState((state) => !state)}
78
+ 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"
79
+ >
80
+ <div className="flex items-center gap-2">
81
+ <div className="w-5 h-5 rounded-lg bg-white/20 flex items-center justify-center text-xs font-medium">
82
+ AI
83
+ </div>
84
+ <span className="font-medium">AI Assistant</span>
85
+ </div>
86
+ <ChevronRight className="w-4 h-4" />
87
+ </button>
88
+
89
+ {isOpen && (
90
+ <div className="absolute bottom-0 left-full ml-2 w-[700px] h-[600px] bg-gray-900 rounded-lg shadow-xl border border-orange-500/20 flex flex-col">
91
+ <div className="flex items-center justify-between p-3 border-b border-orange-500/20">
92
+ <h3 className="font-semibold text-white">AI Assistant</h3>
93
+ <button
94
+ onClick={() => showAIAssistant.setState((state) => !state)}
95
+ className="text-gray-400 hover:text-white transition-colors"
96
+ >
97
+ <X className="w-4 h-4" />
98
+ </button>
99
+ </div>
100
+
101
+ <Messages messages={messages} />
102
+
103
+ <div className="p-3 border-t border-orange-500/20">
104
+ <form
105
+ onSubmit={(e) => {
106
+ e.preventDefault();
107
+ if (input.trim()) {
108
+ sendMessage(input);
109
+ setInput("");
110
+ }
111
+ }}
112
+ >
113
+ <div className="relative">
114
+ <textarea
115
+ value={input}
116
+ onChange={(e) => setInput(e.target.value)}
117
+ placeholder="Type your message..."
118
+ className="w-full rounded-lg border border-orange-500/20 bg-gray-800/50 pl-3 pr-10 py-2 text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-orange-500/50 focus:border-transparent resize-none overflow-hidden"
119
+ rows={1}
120
+ style={{ minHeight: "36px", maxHeight: "120px" }}
121
+ onInput={(e) => {
122
+ const target = e.target as HTMLTextAreaElement;
123
+ target.style.height = "auto";
124
+ target.style.height =
125
+ Math.min(target.scrollHeight, 120) + "px";
126
+ }}
127
+ onKeyDown={(e) => {
128
+ if (e.key === "Enter" && !e.shiftKey && input.trim()) {
129
+ e.preventDefault();
130
+ sendMessage(input);
131
+ setInput("");
132
+ }
133
+ }}
134
+ />
135
+ <button
136
+ type="submit"
137
+ disabled={!input.trim()}
138
+ className="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 text-orange-500 hover:text-orange-400 disabled:text-gray-500 transition-colors focus:outline-none"
139
+ >
140
+ <Send className="w-4 h-4" />
141
+ </button>
142
+ </div>
143
+ </form>
144
+ </div>
145
+ </div>
146
+ )}
147
+ </div>
148
+ );
149
+ }
@@ -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 simple usage
9
+ const defaultChatOptions = createChatClientOptions({
10
+ connection: fetchServerSentEvents('/api/chat'),
11
+ })
12
+
13
+ export type ChatMessages = InferChatMessages<typeof defaultChatOptions>
14
+
15
+ export const useAIChat = () => {
16
+ const chatOptions = createChatClientOptions({
17
+ connection: fetchServerSentEvents('/api/chat'),
18
+ })
19
+
20
+ return useChat(chatOptions)
21
+ }
@@ -0,0 +1,30 @@
1
+ import { toolDefinition } from '@tanstack/ai'
2
+ import { z } from 'zod'
3
+
4
+ // Tool definition for getting weather
5
+ export const getWeatherToolDef = toolDefinition({
6
+ name: 'getWeather',
7
+ description:
8
+ 'Get the current weather for a city. Returns temperature, condition, and humidity.',
9
+ inputSchema: z.object({
10
+ city: z.string().describe('The city to get weather for'),
11
+ }),
12
+ outputSchema: z.object({
13
+ city: z.string(),
14
+ temperature: z.number(),
15
+ condition: z.string(),
16
+ humidity: z.number(),
17
+ }),
18
+ })
19
+
20
+ // Server implementation - mock weather data
21
+ export const getWeather = getWeatherToolDef.server(({ city }) => {
22
+ // Mock weather data - in a real app, call a weather API
23
+ const conditions = ['sunny', 'cloudy', 'rainy', 'partly cloudy', 'windy']
24
+ return {
25
+ city,
26
+ temperature: Math.floor(Math.random() * 30) + 5,
27
+ condition: conditions[Math.floor(Math.random() * conditions.length)],
28
+ humidity: Math.floor(Math.random() * 50) + 30,
29
+ }
30
+ })
@@ -0,0 +1,94 @@
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 { getWeather } from '@/lib/weather-tools'
9
+
10
+ const SYSTEM_PROMPT = `You are a helpful weather assistant. You can check the weather for any city using the getWeather tool.
11
+
12
+ CAPABILITIES:
13
+ 1. Use getWeather to get the current weather for any city
14
+
15
+ INSTRUCTIONS:
16
+ - When users ask about weather, use the getWeather tool to get the information
17
+ - Provide friendly, conversational responses about the weather
18
+ - You can give advice based on weather conditions (e.g., "bring an umbrella" if rainy)
19
+ - Keep responses concise but helpful`
20
+
21
+ export const Route = createFileRoute('/api/chat')({
22
+ server: {
23
+ handlers: {
24
+ POST: async ({ request }) => {
25
+ const requestSignal = request.signal
26
+
27
+ if (requestSignal.aborted) {
28
+ return new Response(null, { status: 499 })
29
+ }
30
+
31
+ const abortController = new AbortController()
32
+
33
+ try {
34
+ const body = await request.json()
35
+ const { messages } = body
36
+ const data = body.data || {}
37
+
38
+ // Determine the best available provider
39
+ let provider: 'anthropic' | 'openai' | 'gemini' | 'ollama' =
40
+ data.provider || 'ollama'
41
+ let model: string = data.model || 'mistral:7b'
42
+
43
+ // Use the first available provider with an API key, fallback to ollama
44
+ if (process.env.ANTHROPIC_API_KEY) {
45
+ provider = 'anthropic'
46
+ model = 'claude-haiku-4-5'
47
+ } else if (process.env.OPENAI_API_KEY) {
48
+ provider = 'openai'
49
+ model = 'gpt-4o'
50
+ } else if (process.env.GEMINI_API_KEY) {
51
+ provider = 'gemini'
52
+ model = 'gemini-2.0-flash-exp'
53
+ }
54
+
55
+ const adapterConfig = {
56
+ anthropic: () =>
57
+ anthropicText((model || 'claude-haiku-4-5') as any),
58
+ openai: () => openaiText((model || 'gpt-4o') as any),
59
+ gemini: () => geminiText((model || 'gemini-2.0-flash-exp') as any),
60
+ ollama: () => ollamaText((model || 'mistral:7b') as any),
61
+ }
62
+
63
+ const adapter = adapterConfig[provider]()
64
+
65
+ const stream = chat({
66
+ adapter,
67
+ tools: [getWeather],
68
+ systemPrompts: [SYSTEM_PROMPT],
69
+ agentLoopStrategy: maxIterations(5),
70
+ messages,
71
+ abortController,
72
+ })
73
+
74
+ return toServerSentEventsResponse(stream, { abortController })
75
+ } catch (error: any) {
76
+ console.error('Chat error:', error)
77
+ if (error.name === 'AbortError' || abortController.signal.aborted) {
78
+ return new Response(null, { status: 499 })
79
+ }
80
+ return new Response(
81
+ JSON.stringify({
82
+ error: 'Failed to process chat request',
83
+ message: error.message,
84
+ }),
85
+ {
86
+ status: 500,
87
+ headers: { 'Content-Type': 'application/json' },
88
+ },
89
+ )
90
+ }
91
+ },
92
+ },
93
+ },
94
+ })