kaddidlehopper 0.5.0 → 0.6.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/dist/cli.js +104 -208
- package/dist/index.js +1 -31
- package/dist/types/cli.d.ts +1 -8
- package/dist/types/types.d.ts +7 -12
- package/package.json +1 -2
- package/src/cli.ts +129 -271
- package/src/index.ts +1 -47
- package/src/types.ts +7 -13
- package/CONTEXT.md +0 -139
- package/add-ons/ai/README.md +0 -34
- package/add-ons/ai/assets/AGENTS.md.append +0 -24
- package/add-ons/ai/assets/_dot_env.local.append +0 -13
- package/add-ons/ai/assets/src/components/AIAssistant.tsx +0 -149
- package/add-ons/ai/assets/src/lib/ai-hook.ts +0 -21
- package/add-ons/ai/assets/src/lib/weather-tools.ts +0 -30
- package/add-ons/ai/assets/src/routes/api.chat.ts +0 -94
- package/add-ons/ai/assets/src/routes/chat.css +0 -175
- package/add-ons/ai/assets/src/routes/chat.tsx +0 -141
- package/add-ons/ai/info.json +0 -22
- package/add-ons/ai/package.json +0 -17
- package/add-ons/ai/small-logo.svg +0 -8
- package/add-ons/db/assets/AGENTS.md.append +0 -22
- package/add-ons/db/assets/DB-SETUP.md +0 -65
- package/add-ons/db/assets/_dot_env.local.append +0 -2
- package/add-ons/db/assets/drizzle.config.ts +0 -10
- package/add-ons/db/assets/src/db/index.ts +0 -8
- package/add-ons/db/assets/src/db/schema.ts +0 -8
- package/add-ons/db/assets/src/routes/db-example.tsx +0 -118
- package/add-ons/db/assets/src/server/guestbook.functions.ts +0 -23
- package/add-ons/db/info.json +0 -22
- package/add-ons/db/package.json +0 -10
- package/add-ons/forms/assets/AGENTS.md.append +0 -13
- package/add-ons/forms/assets/public/form-example.html +0 -14
- package/add-ons/forms/assets/src/routes/form-example.tsx +0 -76
- package/add-ons/forms/info.json +0 -18
- package/add-ons/forms/package.json +0 -3
- package/examples/ai-chat/README.md +0 -36
- package/examples/ai-chat/assets/AGENTS.md.append +0 -24
- package/examples/ai-chat/assets/src/lib/ai-hook.ts +0 -21
- package/examples/ai-chat/assets/src/lib/weather-tools.ts +0 -30
- package/examples/ai-chat/assets/src/routes/__root.tsx +0 -57
- package/examples/ai-chat/assets/src/routes/api.chat.ts +0 -94
- package/examples/ai-chat/assets/src/routes/index.tsx +0 -141
- package/examples/ai-chat/assets/src/styles.css +0 -165
- package/examples/ai-chat/info.json +0 -25
- package/examples/ai-chat/package.json +0 -14
- package/examples/blog/README.md +0 -60
- package/examples/blog/assets/AGENTS.md.append +0 -18
- package/examples/blog/assets/content/posts/beach.md +0 -12
- package/examples/blog/assets/content/posts/jungle.md.ejs +0 -12
- package/examples/blog/assets/content/posts/mountains.md.ejs +0 -12
- package/examples/blog/assets/content/posts/snorkeling.md.ejs +0 -12
- package/examples/blog/assets/content/posts/waterfall.md.ejs +0 -12
- package/examples/blog/assets/content-collections.ts +0 -30
- package/examples/blog/assets/public/beach.jpg +0 -0
- package/examples/blog/assets/public/jungle.jpg +0 -0
- package/examples/blog/assets/public/mountains.jpg +0 -0
- package/examples/blog/assets/public/snorkeling.jpg +0 -0
- package/examples/blog/assets/public/waterfall.jpg +0 -0
- package/examples/blog/assets/src/components/Header.tsx +0 -52
- package/examples/blog/assets/src/components/VacayAssistant.tsx +0 -205
- package/examples/blog/assets/src/components/blog-posts.tsx +0 -78
- package/examples/blog/assets/src/components/ui/card.tsx +0 -92
- package/examples/blog/assets/src/lib/blog-ai-hook.ts +0 -25
- package/examples/blog/assets/src/lib/blog-tools.ts +0 -111
- package/examples/blog/assets/src/lib/utils.ts +0 -6
- package/examples/blog/assets/src/routes/__root.tsx +0 -57
- package/examples/blog/assets/src/routes/api.blog-chat.ts +0 -117
- package/examples/blog/assets/src/routes/category.$category.tsx +0 -19
- package/examples/blog/assets/src/routes/index.tsx +0 -19
- package/examples/blog/assets/src/routes/posts.$slug.tsx +0 -63
- package/examples/blog/assets/src/styles.css +0 -138
- package/examples/blog/info.json +0 -40
- package/examples/blog/package.json +0 -23
- package/examples/calculator/README.md +0 -16
- package/examples/calculator/assets/AGENTS.md.append +0 -9
- package/examples/calculator/assets/src/components/Calculator.tsx +0 -238
- package/examples/calculator/assets/src/components/Header.tsx +0 -17
- package/examples/calculator/assets/src/routes/__root.tsx +0 -57
- package/examples/calculator/assets/src/routes/index.tsx +0 -14
- package/examples/calculator/info.json +0 -20
- package/examples/calculator/package.json +0 -1
- package/examples/dashboard/README.md +0 -17
- package/examples/dashboard/assets/AGENTS.md.append +0 -12
- package/examples/dashboard/assets/src/components/Header.tsx +0 -17
- package/examples/dashboard/assets/src/routes/__root.tsx +0 -57
- package/examples/dashboard/assets/src/routes/index.tsx +0 -158
- package/examples/dashboard/info.json +0 -19
- package/examples/dashboard/package.json +0 -6
- package/examples/ecommerce/README.md +0 -48
- package/examples/ecommerce/assets/AGENTS.md.append +0 -22
- package/examples/ecommerce/assets/public/logo.png +0 -0
- package/examples/ecommerce/assets/public/motorcycle-scooter.jpg +0 -0
- package/examples/ecommerce/assets/src/components/BuyButton.tsx +0 -35
- package/examples/ecommerce/assets/src/components/Header.tsx +0 -36
- package/examples/ecommerce/assets/src/components/MotorcycleAIAssistant.tsx +0 -162
- package/examples/ecommerce/assets/src/components/MotorcycleRecommendation.tsx +0 -53
- package/examples/ecommerce/assets/src/data/motorcycles.ts +0 -27
- package/examples/ecommerce/assets/src/lib/motorcycle-ai-hook.ts +0 -24
- package/examples/ecommerce/assets/src/lib/motorcycle-tools.ts +0 -42
- package/examples/ecommerce/assets/src/lib/stripe.server.ts +0 -39
- package/examples/ecommerce/assets/src/routes/__root.tsx +0 -57
- package/examples/ecommerce/assets/src/routes/api.motorcycle-chat.ts +0 -78
- package/examples/ecommerce/assets/src/routes/checkout/cancel.tsx +0 -25
- package/examples/ecommerce/assets/src/routes/checkout/success.tsx +0 -25
- package/examples/ecommerce/assets/src/routes/index.tsx +0 -76
- package/examples/ecommerce/assets/src/routes/motorcycles/$motorcycleId.tsx +0 -55
- package/examples/ecommerce/assets/src/store/motorcycle-assistant.ts +0 -3
- package/examples/ecommerce/assets/src/styles.css +0 -212
- package/examples/ecommerce/info.json +0 -38
- package/examples/ecommerce/package.json +0 -13
- package/examples/events/README.md +0 -110
- package/examples/events/assets/AGENTS.md.append +0 -21
- package/examples/events/assets/content/speakers/andre-costa.md +0 -22
- package/examples/events/assets/content/speakers/hans-mueller.md.ejs +0 -22
- package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +0 -22
- package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +0 -22
- package/examples/events/assets/content/speakers/marie-dubois.md.ejs +0 -20
- package/examples/events/assets/content/speakers/priya-sharma.md.ejs +0 -22
- package/examples/events/assets/content/talks/croissant-lamination-secrets.md +0 -39
- package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +0 -39
- package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +0 -39
- package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +0 -39
- package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +0 -36
- package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +0 -32
- package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +0 -39
- package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +0 -39
- package/examples/events/assets/content-collections.ts +0 -56
- 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 +0 -1
- package/examples/events/assets/src/components/Header.tsx +0 -59
- package/examples/events/assets/src/components/HeaderNav.tsx +0 -67
- package/examples/events/assets/src/components/HeroCarousel.tsx +0 -61
- package/examples/events/assets/src/components/RemyAssistant.tsx +0 -207
- package/examples/events/assets/src/components/SpeakerCard.tsx +0 -67
- package/examples/events/assets/src/components/TalkCard.tsx +0 -77
- package/examples/events/assets/src/components/ui/card.tsx +0 -92
- package/examples/events/assets/src/lib/conference-ai-hook.ts +0 -26
- package/examples/events/assets/src/lib/conference-tools.ts +0 -210
- package/examples/events/assets/src/lib/model-selection.ts +0 -1
- package/examples/events/assets/src/lib/utils.ts +0 -6
- package/examples/events/assets/src/routes/__root.tsx +0 -70
- package/examples/events/assets/src/routes/api.remy-chat.ts +0 -119
- package/examples/events/assets/src/routes/index.tsx +0 -192
- package/examples/events/assets/src/routes/schedule.index.tsx +0 -274
- package/examples/events/assets/src/routes/speakers.$slug.tsx +0 -122
- package/examples/events/assets/src/routes/speakers.index.tsx +0 -40
- package/examples/events/assets/src/routes/talks.$slug.tsx +0 -116
- package/examples/events/assets/src/routes/talks.index.tsx +0 -40
- package/examples/events/assets/src/styles.css +0 -182
- package/examples/events/info.json +0 -61
- package/examples/events/package.json +0 -23
- package/examples/marketing/README.md +0 -60
- package/examples/marketing/assets/AGENTS.md.append +0 -15
- package/examples/marketing/assets/public/logo.png +0 -0
- package/examples/marketing/assets/public/motorcycle-adventure.jpg +0 -0
- package/examples/marketing/assets/public/motorcycle-cruiser.jpg +0 -0
- package/examples/marketing/assets/public/motorcycle-scooter.jpg +0 -0
- package/examples/marketing/assets/public/motorcycle-sport.jpg +0 -0
- package/examples/marketing/assets/public/motorcycle-supersport.jpg +0 -0
- package/examples/marketing/assets/src/components/Header.tsx +0 -36
- package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +0 -162
- package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +0 -53
- package/examples/marketing/assets/src/data/motorcycles.ts.ejs +0 -77
- package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +0 -24
- package/examples/marketing/assets/src/lib/motorcycle-tools.ts +0 -42
- package/examples/marketing/assets/src/routes/__root.tsx +0 -57
- package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +0 -78
- package/examples/marketing/assets/src/routes/index.tsx +0 -72
- package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +0 -56
- package/examples/marketing/assets/src/store/motorcycle-assistant.ts +0 -3
- package/examples/marketing/assets/src/styles.css +0 -212
- package/examples/marketing/info.json +0 -33
- package/examples/marketing/package.json +0 -14
- package/examples/portfolio/README.md +0 -49
- package/examples/portfolio/assets/AGENTS.md.append +0 -21
- package/examples/portfolio/assets/content/blog/getting-started-with-tanstack.md +0 -53
- package/examples/portfolio/assets/content/blog/react-19-features.md +0 -78
- package/examples/portfolio/assets/content/blog/tailwind-css-v4-guide.md +0 -60
- package/examples/portfolio/assets/content/education/code-school.md +0 -17
- package/examples/portfolio/assets/content/jobs/initech-junior.md +0 -20
- package/examples/portfolio/assets/content/projects/portfolio-site.md +0 -15
- package/examples/portfolio/assets/content/projects/task-manager.md +0 -15
- package/examples/portfolio/assets/content-collections.ts +0 -65
- package/examples/portfolio/assets/public/contact.html +0 -6
- package/examples/portfolio/assets/public/headshot-on-white.jpg +0 -0
- package/examples/portfolio/assets/src/components/Header.tsx +0 -33
- package/examples/portfolio/assets/src/components/ResumeAssistant.tsx +0 -193
- package/examples/portfolio/assets/src/components/ui/badge.tsx +0 -46
- package/examples/portfolio/assets/src/components/ui/card.tsx +0 -92
- package/examples/portfolio/assets/src/components/ui/checkbox.tsx +0 -30
- package/examples/portfolio/assets/src/components/ui/hover-card.tsx +0 -44
- package/examples/portfolio/assets/src/components/ui/separator.tsx +0 -26
- package/examples/portfolio/assets/src/lib/resume-ai-hook.ts +0 -21
- package/examples/portfolio/assets/src/lib/resume-tools.ts +0 -165
- package/examples/portfolio/assets/src/lib/utils.ts +0 -6
- package/examples/portfolio/assets/src/routes/__root.tsx +0 -57
- package/examples/portfolio/assets/src/routes/api.resume-chat.ts +0 -116
- package/examples/portfolio/assets/src/routes/blog/$slug.tsx +0 -73
- package/examples/portfolio/assets/src/routes/contact.tsx +0 -121
- package/examples/portfolio/assets/src/routes/index.tsx +0 -55
- package/examples/portfolio/assets/src/routes/projects.tsx +0 -62
- package/examples/portfolio/assets/src/routes/resume.tsx +0 -220
- package/examples/portfolio/assets/src/styles.css +0 -138
- package/examples/portfolio/info.json +0 -50
- package/examples/portfolio/package.json +0 -26
- package/examples/resume/README.md +0 -82
- package/examples/resume/assets/AGENTS.md.append +0 -19
- package/examples/resume/assets/content/education/code-school.md +0 -17
- package/examples/resume/assets/content/jobs/freelance.md.ejs +0 -13
- package/examples/resume/assets/content/jobs/initech-junior.md +0 -20
- package/examples/resume/assets/content/jobs/initech-lead.md.ejs +0 -29
- package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +0 -28
- package/examples/resume/assets/content-collections.ts +0 -36
- package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
- package/examples/resume/assets/src/components/Header.tsx +0 -33
- package/examples/resume/assets/src/components/ResumeAssistant.tsx +0 -193
- package/examples/resume/assets/src/components/ui/badge.tsx +0 -46
- package/examples/resume/assets/src/components/ui/card.tsx +0 -92
- package/examples/resume/assets/src/components/ui/checkbox.tsx +0 -30
- package/examples/resume/assets/src/components/ui/hover-card.tsx +0 -44
- package/examples/resume/assets/src/components/ui/separator.tsx +0 -26
- package/examples/resume/assets/src/lib/resume-ai-hook.ts +0 -21
- package/examples/resume/assets/src/lib/resume-tools.ts +0 -165
- package/examples/resume/assets/src/lib/utils.ts +0 -6
- package/examples/resume/assets/src/routes/api.resume-chat.ts +0 -110
- package/examples/resume/assets/src/routes/index.tsx +0 -220
- package/examples/resume/assets/src/styles.css +0 -138
- package/examples/resume/info.json +0 -35
- package/examples/resume/package.json +0 -26
- package/examples/saas/README.md +0 -16
- package/examples/saas/assets/AGENTS.md.append +0 -9
- package/examples/saas/assets/src/components/Header.tsx +0 -17
- package/examples/saas/assets/src/routes/__root.tsx +0 -57
- package/examples/saas/assets/src/routes/faq.tsx +0 -94
- package/examples/saas/assets/src/routes/index.tsx +0 -197
- package/examples/saas/info.json +0 -23
- package/examples/saas/package.json +0 -1
- package/examples/survey/README.md +0 -20
- package/examples/survey/assets/AGENTS.md.append +0 -9
- package/examples/survey/assets/public/form-survey.html +0 -15
- package/examples/survey/assets/src/components/SurveyForm.tsx +0 -128
- package/examples/survey/assets/src/routes/index.tsx +0 -14
- package/examples/survey/info.json +0 -19
- package/examples/survey/package.json +0 -1
- package/project/base/AGENTS.md +0 -86
- package/project/base/_dot_claude/skills/content-collections/SKILL.md +0 -505
- package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +0 -410
- package/project/base/_dot_claude/skills/netlify-db/SKILL.md +0 -424
- package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +0 -419
- package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +0 -243
- package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +0 -372
- package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +0 -421
- package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +0 -426
- package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +0 -493
- package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +0 -430
- package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +0 -445
- package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +0 -494
- package/project/base/_dot_gitignore +0 -8
- package/project/base/netlify.toml +0 -7
- package/project/base/package.json +0 -33
- package/project/base/public/favicon.ico +0 -0
- package/project/base/public/tanstack-circle-logo.png +0 -0
- package/project/base/public/tanstack-word-logo-white.svg +0 -1
- package/project/base/src/components/Header.tsx +0 -17
- package/project/base/src/components/HeaderNav.tsx.ejs +0 -179
- package/project/base/src/router.tsx +0 -15
- package/project/base/src/routes/__root.tsx +0 -57
- package/project/base/src/routes/index.tsx +0 -48
- package/project/base/src/styles.css +0 -15
- package/project/base/tsconfig.json +0 -28
- package/project/base/vite.config.ts.ejs +0 -25
- package/project/info.json +0 -17
- package/project/packages.json +0 -22
- package/scripts/check-outdated-packages.js +0 -421
- package/src/agents-md.ts +0 -139
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# Ecommerce Example
|
|
2
|
-
|
|
3
|
-
A motorcycle ecommerce site built with TanStack Start, Stripe checkout, and an AI shopping assistant for Netlify deployment.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Product Catalog**: Motorcycle listings with details and images
|
|
8
|
-
- **Stripe Checkout**: Secure payment processing via Stripe
|
|
9
|
-
- **AI Shopping Assistant**: Chat-based motorcycle recommendation engine
|
|
10
|
-
- **Product Detail Pages**: Individual motorcycle pages with full descriptions
|
|
11
|
-
- **Checkout Flow**: Success and cancellation pages
|
|
12
|
-
|
|
13
|
-
## Project Structure
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
├── src/
|
|
17
|
-
│ ├── components/
|
|
18
|
-
│ │ ├── BuyButton.tsx # Stripe checkout trigger
|
|
19
|
-
│ │ ├── Header.tsx # Custom header with AI toggle
|
|
20
|
-
│ │ ├── MotorcycleAIAssistant.tsx # AI chat panel
|
|
21
|
-
│ │ └── MotorcycleRecommendation.tsx
|
|
22
|
-
│ ├── data/
|
|
23
|
-
│ │ └── motorcycles.ts # Product data
|
|
24
|
-
│ ├── lib/
|
|
25
|
-
│ │ ├── motorcycle-ai-hook.ts # Chat hook
|
|
26
|
-
│ │ ├── motorcycle-tools.ts # AI tools
|
|
27
|
-
│ │ └── stripe.server.ts # Stripe server functions
|
|
28
|
-
│ ├── store/
|
|
29
|
-
│ │ └── motorcycle-assistant.ts # UI state
|
|
30
|
-
│ └── routes/
|
|
31
|
-
│ ├── index.tsx # Product listing
|
|
32
|
-
│ ├── motorcycles/$motorcycleId.tsx
|
|
33
|
-
│ ├── checkout/success.tsx
|
|
34
|
-
│ ├── checkout/cancel.tsx
|
|
35
|
-
│ └── api.motorcycle-chat.ts
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## Environment Variables
|
|
39
|
-
|
|
40
|
-
- `STRIPE_SECRET_KEY` — Stripe secret key for checkout
|
|
41
|
-
- `ANTHROPIC_API_KEY` — For AI assistant
|
|
42
|
-
- `SITE_URL` — Base URL for checkout redirects (defaults to localhost:3000)
|
|
43
|
-
|
|
44
|
-
## Development
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
npm run dev
|
|
48
|
-
```
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
### Ecommerce Integration
|
|
2
|
-
|
|
3
|
-
Motorcycle ecommerce site with Stripe checkout and AI shopping assistant.
|
|
4
|
-
|
|
5
|
-
**Stripe checkout:**
|
|
6
|
-
- `createCheckoutSession` server function in `src/lib/stripe.server.ts`
|
|
7
|
-
- BuyButton component redirects to Stripe Checkout
|
|
8
|
-
- Routes: `/checkout/success`, `/checkout/cancel`
|
|
9
|
-
|
|
10
|
-
**AI tools available:**
|
|
11
|
-
- `getMotorcycles` - Get all motorcycles from catalog
|
|
12
|
-
- `recommendMotorcycle` - Display motorcycle recommendation card (MUST use for recommendations; do not write recommendations manually)
|
|
13
|
-
|
|
14
|
-
**Dependencies:** stripe, @tanstack/ai, streamdown
|
|
15
|
-
|
|
16
|
-
## Environment Variables
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
STRIPE_SECRET_KEY=... # Required for checkout
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
For AI assistant: ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, or OLLAMA_BASE_URL (same as ai add-on).
|
|
Binary file
|
|
Binary file
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import { createCheckoutSession } from '@/lib/stripe.server'
|
|
3
|
-
|
|
4
|
-
export function BuyButton({
|
|
5
|
-
motorcycleId,
|
|
6
|
-
className = '',
|
|
7
|
-
}: {
|
|
8
|
-
motorcycleId: number
|
|
9
|
-
className?: string
|
|
10
|
-
}) {
|
|
11
|
-
const [loading, setLoading] = useState(false)
|
|
12
|
-
|
|
13
|
-
const handleClick = async () => {
|
|
14
|
-
setLoading(true)
|
|
15
|
-
try {
|
|
16
|
-
const url = await createCheckoutSession({ data: motorcycleId })
|
|
17
|
-
if (url) {
|
|
18
|
-
window.location.href = url
|
|
19
|
-
}
|
|
20
|
-
} catch (error) {
|
|
21
|
-
console.error('Checkout error:', error)
|
|
22
|
-
setLoading(false)
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<button
|
|
28
|
-
onClick={handleClick}
|
|
29
|
-
disabled={loading}
|
|
30
|
-
className={`bg-emerald-600 hover:bg-emerald-500 disabled:bg-emerald-800 disabled:cursor-wait text-white px-6 py-2 rounded-lg transition-colors ${className}`}
|
|
31
|
-
>
|
|
32
|
-
{loading ? 'Processing...' : 'Buy Now'}
|
|
33
|
-
</button>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { Link } from '@tanstack/react-router'
|
|
2
|
-
|
|
3
|
-
import HeaderNav from './HeaderNav'
|
|
4
|
-
|
|
5
|
-
import MotorcycleAIAssistant from '@/components/MotorcycleAIAssistant'
|
|
6
|
-
|
|
7
|
-
export default function Header() {
|
|
8
|
-
return (
|
|
9
|
-
<>
|
|
10
|
-
<div className="z-40">
|
|
11
|
-
<HeaderNav />
|
|
12
|
-
</div>
|
|
13
|
-
<header className="p-4 flex gap-2 bg-black/30 text-white border-b border-orange-500/20 backdrop-blur-md sticky top-0 z-40">
|
|
14
|
-
<nav className="flex flex-row items-center">
|
|
15
|
-
<div className="px-4 font-extrabold text-lg">
|
|
16
|
-
<Link
|
|
17
|
-
to="/"
|
|
18
|
-
className="hover:text-orange-400 transition-colors flex flex-row items-center"
|
|
19
|
-
>
|
|
20
|
-
<img
|
|
21
|
-
src="/logo.png"
|
|
22
|
-
alt="Luna-C Motorcycles"
|
|
23
|
-
className="w-20 h-20"
|
|
24
|
-
/>
|
|
25
|
-
<span className="text-2xl font-bold">Luna-C Motorcycles</span>
|
|
26
|
-
</Link>
|
|
27
|
-
</div>
|
|
28
|
-
</nav>
|
|
29
|
-
|
|
30
|
-
<div className="ml-auto">
|
|
31
|
-
<MotorcycleAIAssistant />
|
|
32
|
-
</div>
|
|
33
|
-
</header>
|
|
34
|
-
</>
|
|
35
|
-
)
|
|
36
|
-
}
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { useStore } from '@tanstack/react-store'
|
|
3
|
-
import { Send, X } from 'lucide-react'
|
|
4
|
-
import { Streamdown } from 'streamdown'
|
|
5
|
-
|
|
6
|
-
import { useMotorcycleChat } from '@/lib/motorcycle-ai-hook'
|
|
7
|
-
import type { ChatMessages } from '@/lib/ai-hook'
|
|
8
|
-
import { showMotorcycleAIAssistant } from '@/store/motorcycle-assistant'
|
|
9
|
-
|
|
10
|
-
import MotorcycleRecommendation from './MotorcycleRecommendation'
|
|
11
|
-
|
|
12
|
-
function Messages({ messages }: { messages: ChatMessages }) {
|
|
13
|
-
const messagesContainerRef = useRef<HTMLDivElement>(null)
|
|
14
|
-
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
if (messagesContainerRef.current) {
|
|
17
|
-
messagesContainerRef.current.scrollTop =
|
|
18
|
-
messagesContainerRef.current.scrollHeight
|
|
19
|
-
}
|
|
20
|
-
}, [messages])
|
|
21
|
-
|
|
22
|
-
if (!messages.length) {
|
|
23
|
-
return (
|
|
24
|
-
<div className="flex-1 flex items-center justify-center text-gray-400 text-sm">
|
|
25
|
-
Ask me about your next motorcycle.
|
|
26
|
-
</div>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div ref={messagesContainerRef} className="flex-1 overflow-y-auto">
|
|
32
|
-
{messages.map(({ id, role, parts }) => (
|
|
33
|
-
<div
|
|
34
|
-
key={id}
|
|
35
|
-
className={`py-3 ${
|
|
36
|
-
role === 'assistant'
|
|
37
|
-
? 'bg-linear-to-r from-orange-500/5 to-red-600/5'
|
|
38
|
-
: 'bg-transparent'
|
|
39
|
-
}`}
|
|
40
|
-
>
|
|
41
|
-
{parts.map((part, index) => {
|
|
42
|
-
if (part.type === 'text' && part.content) {
|
|
43
|
-
return (
|
|
44
|
-
<div key={index} className="flex items-start gap-2 px-4">
|
|
45
|
-
{role === 'assistant' ? (
|
|
46
|
-
<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">
|
|
47
|
-
AI
|
|
48
|
-
</div>
|
|
49
|
-
) : (
|
|
50
|
-
<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">
|
|
51
|
-
Y
|
|
52
|
-
</div>
|
|
53
|
-
)}
|
|
54
|
-
<div className="flex-1 min-w-0 text-white prose dark:prose-invert max-w-none prose-sm">
|
|
55
|
-
<Streamdown>{part.content}</Streamdown>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
if (
|
|
61
|
-
part.type === 'tool-call' &&
|
|
62
|
-
part.name === 'recommendMotorcycle' &&
|
|
63
|
-
part.output
|
|
64
|
-
) {
|
|
65
|
-
return (
|
|
66
|
-
<div key={part.id} className="max-w-[80%] mx-auto">
|
|
67
|
-
<MotorcycleRecommendation id={String(part.output?.id)} />
|
|
68
|
-
</div>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
})}
|
|
72
|
-
</div>
|
|
73
|
-
))}
|
|
74
|
-
</div>
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export default function AIAssistant() {
|
|
79
|
-
const isOpen = useStore(showMotorcycleAIAssistant)
|
|
80
|
-
const { messages, sendMessage } = useMotorcycleChat()
|
|
81
|
-
const [input, setInput] = useState('')
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<div className="relative z-50">
|
|
85
|
-
<button
|
|
86
|
-
onClick={() => showMotorcycleAIAssistant.setState((state) => !state)}
|
|
87
|
-
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-black/90 text-white hover:bg-black/80 transition-all border border-orange-500/20 shadow-lg shadow-orange-500/10"
|
|
88
|
-
>
|
|
89
|
-
<div className="w-5 h-5 rounded-lg bg-linear-to-r from-orange-500 to-red-600 flex items-center justify-center text-xs font-medium">
|
|
90
|
-
AI
|
|
91
|
-
</div>
|
|
92
|
-
Motorcycle Expert
|
|
93
|
-
</button>
|
|
94
|
-
|
|
95
|
-
{isOpen && (
|
|
96
|
-
<div className="absolute top-full right-0 mt-2 w-[700px] h-[600px] bg-black/95 rounded-lg shadow-2xl border border-orange-500/20 flex flex-col backdrop-blur-sm">
|
|
97
|
-
<div className="flex items-center justify-between p-4 border-b border-orange-500/20 bg-linear-to-r from-orange-500/5 to-red-600/5">
|
|
98
|
-
<h3 className="font-semibold text-white flex items-center gap-2">
|
|
99
|
-
<span className="w-6 h-6 rounded-lg bg-linear-to-r from-orange-500 to-red-600 flex items-center justify-center text-xs">
|
|
100
|
-
AI
|
|
101
|
-
</span>
|
|
102
|
-
Motorcycle Expert
|
|
103
|
-
</h3>
|
|
104
|
-
<button
|
|
105
|
-
onClick={() =>
|
|
106
|
-
showMotorcycleAIAssistant.setState((state) => !state)
|
|
107
|
-
}
|
|
108
|
-
className="text-gray-400 hover:text-white transition-colors"
|
|
109
|
-
>
|
|
110
|
-
<X className="w-4 h-4" />
|
|
111
|
-
</button>
|
|
112
|
-
</div>
|
|
113
|
-
|
|
114
|
-
<Messages messages={messages} />
|
|
115
|
-
|
|
116
|
-
<div className="p-4 border-t border-orange-500/20 bg-linear-to-r from-orange-500/5 to-red-600/5">
|
|
117
|
-
<form
|
|
118
|
-
onSubmit={(e) => {
|
|
119
|
-
e.preventDefault()
|
|
120
|
-
if (input.trim()) {
|
|
121
|
-
sendMessage(input)
|
|
122
|
-
setInput('')
|
|
123
|
-
}
|
|
124
|
-
}}
|
|
125
|
-
>
|
|
126
|
-
<div className="relative">
|
|
127
|
-
<textarea
|
|
128
|
-
value={input}
|
|
129
|
-
onChange={(e) => setInput(e.target.value)}
|
|
130
|
-
placeholder="Ask about our motorcycles..."
|
|
131
|
-
className="w-full rounded-lg border border-orange-500/20 bg-black/50 pl-3 pr-10 py-3 text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-orange-500/30 focus:border-transparent resize-none overflow-hidden shadow-inner"
|
|
132
|
-
rows={1}
|
|
133
|
-
style={{ minHeight: '44px', maxHeight: '120px' }}
|
|
134
|
-
onInput={(e) => {
|
|
135
|
-
const target = e.target as HTMLTextAreaElement
|
|
136
|
-
target.style.height = 'auto'
|
|
137
|
-
target.style.height =
|
|
138
|
-
Math.min(target.scrollHeight, 120) + 'px'
|
|
139
|
-
}}
|
|
140
|
-
onKeyDown={(e) => {
|
|
141
|
-
if (e.key === 'Enter' && !e.shiftKey && input.trim()) {
|
|
142
|
-
e.preventDefault()
|
|
143
|
-
sendMessage(input)
|
|
144
|
-
setInput('')
|
|
145
|
-
}
|
|
146
|
-
}}
|
|
147
|
-
/>
|
|
148
|
-
<button
|
|
149
|
-
type="submit"
|
|
150
|
-
disabled={!input.trim()}
|
|
151
|
-
className="absolute right-2 top-1/2 -translate-y-1/2 p-2 text-orange-500 hover:text-orange-400 disabled:text-gray-500 transition-colors focus:outline-none"
|
|
152
|
-
>
|
|
153
|
-
<Send className="w-4 h-4" />
|
|
154
|
-
</button>
|
|
155
|
-
</div>
|
|
156
|
-
</form>
|
|
157
|
-
</div>
|
|
158
|
-
</div>
|
|
159
|
-
)}
|
|
160
|
-
</div>
|
|
161
|
-
)
|
|
162
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { useNavigate } from '@tanstack/react-router'
|
|
2
|
-
|
|
3
|
-
import { showMotorcycleAIAssistant } from '@/store/motorcycle-assistant'
|
|
4
|
-
|
|
5
|
-
import motorcycles from '@/data/motorcycles'
|
|
6
|
-
|
|
7
|
-
export default function MotorcycleRecommendation({ id }: { id: string }) {
|
|
8
|
-
const navigate = useNavigate()
|
|
9
|
-
const motorcycle = motorcycles.find((motorcycle) => motorcycle.id === +id)
|
|
10
|
-
if (!motorcycle) {
|
|
11
|
-
return null
|
|
12
|
-
}
|
|
13
|
-
return (
|
|
14
|
-
<div className="my-4 rounded-lg overflow-hidden border border-orange-500/20 bg-gray-800/50">
|
|
15
|
-
<div className="aspect-[4/3] relative overflow-hidden">
|
|
16
|
-
<img
|
|
17
|
-
src={motorcycle.image}
|
|
18
|
-
alt={motorcycle.name}
|
|
19
|
-
className="w-full h-full object-cover"
|
|
20
|
-
/>
|
|
21
|
-
</div>
|
|
22
|
-
<div className="p-4">
|
|
23
|
-
<h3 className="text-lg font-semibold text-white mb-2">
|
|
24
|
-
{motorcycle.name}
|
|
25
|
-
</h3>
|
|
26
|
-
<p className="text-sm text-gray-300 mb-1 line-clamp-2">
|
|
27
|
-
{motorcycle.shortDescription}
|
|
28
|
-
</p>
|
|
29
|
-
<p className="text-sm text-gray-400 mb-3">
|
|
30
|
-
{motorcycle.engineSize}cc •{' '}
|
|
31
|
-
{motorcycle.type.charAt(0).toUpperCase() + motorcycle.type.slice(1)}
|
|
32
|
-
</p>
|
|
33
|
-
<div className="flex items-center justify-between">
|
|
34
|
-
<div className="text-lg font-bold text-emerald-400">
|
|
35
|
-
${motorcycle.price.toLocaleString()}
|
|
36
|
-
</div>
|
|
37
|
-
<button
|
|
38
|
-
onClick={() => {
|
|
39
|
-
navigate({
|
|
40
|
-
to: '/motorcycles/$motorcycleId',
|
|
41
|
-
params: { motorcycleId: motorcycle.id.toString() },
|
|
42
|
-
})
|
|
43
|
-
showMotorcycleAIAssistant.setState(() => false)
|
|
44
|
-
}}
|
|
45
|
-
className="bg-linear-to-r from-orange-500 to-red-600 text-white px-4 py-1.5 rounded-lg text-sm hover:opacity-90 transition-opacity"
|
|
46
|
-
>
|
|
47
|
-
View Details
|
|
48
|
-
</button>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
|
-
)
|
|
53
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export interface Motorcycle {
|
|
2
|
-
id: number
|
|
3
|
-
name: string
|
|
4
|
-
image: string
|
|
5
|
-
description: string
|
|
6
|
-
shortDescription: string
|
|
7
|
-
price: number
|
|
8
|
-
engineSize: number // in CCs
|
|
9
|
-
type: 'scooter' | 'cruiser' | 'adventure' | 'sport' | 'supersport'
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const motorcycles: Array<Motorcycle> = [
|
|
13
|
-
{
|
|
14
|
-
id: 1,
|
|
15
|
-
name: 'Luna-C Urbanite',
|
|
16
|
-
image: '/motorcycle-scooter.jpg',
|
|
17
|
-
description:
|
|
18
|
-
'The Luna-C Urbanite is the perfect city companion, combining style and practicality in a compact package. With its 125cc engine, it delivers excellent fuel efficiency while maintaining enough power for urban commuting. The comfortable seating position and lightweight frame make it easy to maneuver through city traffic, while the under-seat storage provides ample space for your daily essentials.',
|
|
19
|
-
shortDescription:
|
|
20
|
-
'A stylish and practical 125cc scooter perfect for urban commuting with excellent fuel efficiency.',
|
|
21
|
-
price: 3000,
|
|
22
|
-
engineSize: 125,
|
|
23
|
-
type: 'scooter',
|
|
24
|
-
},
|
|
25
|
-
]
|
|
26
|
-
|
|
27
|
-
export default motorcycles
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
fetchServerSentEvents,
|
|
3
|
-
useChat,
|
|
4
|
-
createChatClientOptions,
|
|
5
|
-
} from '@tanstack/ai-react'
|
|
6
|
-
import type { InferChatMessages } from '@tanstack/ai-react'
|
|
7
|
-
import { clientTools } from '@tanstack/ai-client'
|
|
8
|
-
|
|
9
|
-
import { recommendMotorcycleToolDef } from '@/lib/motorcycle-tools'
|
|
10
|
-
|
|
11
|
-
const recommendMotorcycleToolClient = recommendMotorcycleToolDef.client(
|
|
12
|
-
({ id }) => ({
|
|
13
|
-
id: +id,
|
|
14
|
-
}),
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
const chatOptions = createChatClientOptions({
|
|
18
|
-
connection: fetchServerSentEvents('/api/motorcycle-chat'),
|
|
19
|
-
tools: clientTools(recommendMotorcycleToolClient),
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
export type ChatMessages = InferChatMessages<typeof chatOptions>
|
|
23
|
-
|
|
24
|
-
export const useMotorcycleChat = () => useChat(chatOptions)
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { toolDefinition } from '@tanstack/ai'
|
|
2
|
-
import { z } from 'zod'
|
|
3
|
-
import motorcycles from '@/data/motorcycles'
|
|
4
|
-
|
|
5
|
-
// Tool definition for getting motorcycles
|
|
6
|
-
export const getMotorcyclesToolDef = toolDefinition({
|
|
7
|
-
name: 'getMotorcycles',
|
|
8
|
-
description: 'Get all motorcycles from the database',
|
|
9
|
-
inputSchema: z.object({}),
|
|
10
|
-
outputSchema: z.array(
|
|
11
|
-
z.object({
|
|
12
|
-
id: z.number(),
|
|
13
|
-
name: z.string(),
|
|
14
|
-
image: z.string(),
|
|
15
|
-
description: z.string(),
|
|
16
|
-
shortDescription: z.string(),
|
|
17
|
-
price: z.number(),
|
|
18
|
-
engineSize: z.number(),
|
|
19
|
-
type: z.enum(['scooter', 'cruiser', 'adventure', 'sport', 'supersport']),
|
|
20
|
-
}),
|
|
21
|
-
),
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
// Server implementation
|
|
25
|
-
export const getMotorcycles = getMotorcyclesToolDef.server(() => motorcycles)
|
|
26
|
-
|
|
27
|
-
// Tool definition for motorcycle recommendation
|
|
28
|
-
export const recommendMotorcycleToolDef = toolDefinition({
|
|
29
|
-
name: 'recommendMotorcycle',
|
|
30
|
-
description:
|
|
31
|
-
'REQUIRED tool to display a motorcycle recommendation to the user. This tool MUST be used whenever recommending a motorcycle - do NOT write recommendations yourself. This displays the motorcycle in a special appealing format with a view details button.',
|
|
32
|
-
inputSchema: z.object({
|
|
33
|
-
id: z
|
|
34
|
-
.union([z.string(), z.number()])
|
|
35
|
-
.describe(
|
|
36
|
-
'The ID of the motorcycle to recommend (from the getMotorcycles results)',
|
|
37
|
-
),
|
|
38
|
-
}),
|
|
39
|
-
outputSchema: z.object({
|
|
40
|
-
id: z.number(),
|
|
41
|
-
}),
|
|
42
|
-
})
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import Stripe from 'stripe'
|
|
2
|
-
import { createServerFn } from '@tanstack/react-start'
|
|
3
|
-
import motorcycles from '@/data/motorcycles'
|
|
4
|
-
|
|
5
|
-
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
|
|
6
|
-
|
|
7
|
-
export const createCheckoutSession = createServerFn({
|
|
8
|
-
method: 'POST',
|
|
9
|
-
})
|
|
10
|
-
.inputValidator((motorcycleId: number) => motorcycleId)
|
|
11
|
-
.handler(async ({ data: motorcycleId }) => {
|
|
12
|
-
const motorcycle = motorcycles.find((m) => m.id === motorcycleId)
|
|
13
|
-
if (!motorcycle) {
|
|
14
|
-
throw new Error('Motorcycle not found')
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const session = await stripe.checkout.sessions.create({
|
|
18
|
-
payment_method_types: ['card'],
|
|
19
|
-
line_items: [
|
|
20
|
-
{
|
|
21
|
-
price_data: {
|
|
22
|
-
currency: 'usd',
|
|
23
|
-
product_data: {
|
|
24
|
-
name: motorcycle.name,
|
|
25
|
-
description: motorcycle.shortDescription,
|
|
26
|
-
images: [motorcycle.image],
|
|
27
|
-
},
|
|
28
|
-
unit_amount: motorcycle.price * 100,
|
|
29
|
-
},
|
|
30
|
-
quantity: 1,
|
|
31
|
-
},
|
|
32
|
-
],
|
|
33
|
-
mode: 'payment',
|
|
34
|
-
success_url: `${process.env.SITE_URL ?? 'http://localhost:3000'}/checkout/success`,
|
|
35
|
-
cancel_url: `${process.env.SITE_URL ?? 'http://localhost:3000'}/checkout/cancel`,
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
return session.url
|
|
39
|
-
})
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
|
|
2
|
-
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
|
|
3
|
-
import { TanStackDevtools } from '@tanstack/react-devtools'
|
|
4
|
-
|
|
5
|
-
import Header from '@/components/Header'
|
|
6
|
-
|
|
7
|
-
import appCss from '@/styles.css?url'
|
|
8
|
-
|
|
9
|
-
export const Route = createRootRoute({
|
|
10
|
-
head: () => ({
|
|
11
|
-
meta: [
|
|
12
|
-
{
|
|
13
|
-
charSet: 'utf-8',
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
name: 'viewport',
|
|
17
|
-
content: 'width=device-width, initial-scale=1',
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
title: 'Luna-C Motorcycles',
|
|
21
|
-
},
|
|
22
|
-
],
|
|
23
|
-
links: [
|
|
24
|
-
{
|
|
25
|
-
rel: 'stylesheet',
|
|
26
|
-
href: appCss,
|
|
27
|
-
},
|
|
28
|
-
],
|
|
29
|
-
}),
|
|
30
|
-
shellComponent: RootDocument,
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
function RootDocument({ children }: { children: React.ReactNode }) {
|
|
34
|
-
return (
|
|
35
|
-
<html lang="en">
|
|
36
|
-
<head>
|
|
37
|
-
<HeadContent />
|
|
38
|
-
</head>
|
|
39
|
-
<body className="bg-black">
|
|
40
|
-
<Header />
|
|
41
|
-
{children}
|
|
42
|
-
<TanStackDevtools
|
|
43
|
-
config={{
|
|
44
|
-
position: 'bottom-right',
|
|
45
|
-
}}
|
|
46
|
-
plugins={[
|
|
47
|
-
{
|
|
48
|
-
name: 'Tanstack Router',
|
|
49
|
-
render: <TanStackRouterDevtoolsPanel />,
|
|
50
|
-
},
|
|
51
|
-
]}
|
|
52
|
-
/>
|
|
53
|
-
<Scripts />
|
|
54
|
-
</body>
|
|
55
|
-
</html>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
-
import { chat, maxIterations, toServerSentEventsResponse } from '@tanstack/ai'
|
|
3
|
-
import { anthropicText } from '@tanstack/ai-anthropic'
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
getMotorcycles,
|
|
7
|
-
recommendMotorcycleToolDef,
|
|
8
|
-
} from '@/lib/motorcycle-tools'
|
|
9
|
-
|
|
10
|
-
const SYSTEM_PROMPT = `You are a helpful assistant for a store that sells motorcycles.
|
|
11
|
-
|
|
12
|
-
You are also a motorcycle enthusiast who is passionate about our products and how they can bring joy to people's lives, the excitement of the open road and adventure. Emphasize these aspects in your responses.
|
|
13
|
-
|
|
14
|
-
CRITICAL INSTRUCTIONS - YOU MUST FOLLOW THIS EXACT WORKFLOW:
|
|
15
|
-
|
|
16
|
-
When a user asks for a motorcycle recommendation:
|
|
17
|
-
1. FIRST: Use the getMotorcycles tool (no parameters needed)
|
|
18
|
-
2. SECOND: Use the recommendMotorcycle tool with the ID of the motorcycle you want to recommend
|
|
19
|
-
3. NEVER write a recommendation directly - ALWAYS use the recommendMotorcycle tool
|
|
20
|
-
|
|
21
|
-
IMPORTANT:
|
|
22
|
-
- The recommendMotorcycle tool will display the motorcycle in a special, appealing format
|
|
23
|
-
- You MUST use recommendMotorcycle for ANY motorcycle recommendation
|
|
24
|
-
- ONLY recommend motorcycles from our inventory (use getMotorcycles first)
|
|
25
|
-
- The recommendMotorcycle tool has a view details button - this is how customers can learn more
|
|
26
|
-
- Do NOT describe the motorcycle yourself - let the recommendMotorcycle tool do it
|
|
27
|
-
`
|
|
28
|
-
|
|
29
|
-
export const Route = createFileRoute('/api/motorcycle-chat')({
|
|
30
|
-
server: {
|
|
31
|
-
handlers: {
|
|
32
|
-
POST: async ({ request }) => {
|
|
33
|
-
// Capture request signal before reading body (it may be aborted after body is consumed)
|
|
34
|
-
const requestSignal = request.signal
|
|
35
|
-
|
|
36
|
-
// If request is already aborted, return early
|
|
37
|
-
if (requestSignal.aborted) {
|
|
38
|
-
return new Response(null, { status: 499 }) // 499 = Client Closed Request
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const abortController = new AbortController()
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
const body = await request.json()
|
|
45
|
-
const { messages } = body
|
|
46
|
-
|
|
47
|
-
const adapter = anthropicText('claude-haiku-4-5')
|
|
48
|
-
|
|
49
|
-
const stream = chat({
|
|
50
|
-
adapter,
|
|
51
|
-
tools: [
|
|
52
|
-
getMotorcycles, // Server tool
|
|
53
|
-
recommendMotorcycleToolDef, // No server execute - client will handle
|
|
54
|
-
],
|
|
55
|
-
systemPrompts: [SYSTEM_PROMPT],
|
|
56
|
-
agentLoopStrategy: maxIterations(5),
|
|
57
|
-
messages,
|
|
58
|
-
abortController,
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
return toServerSentEventsResponse(stream, { abortController })
|
|
62
|
-
} catch (error: any) {
|
|
63
|
-
// If request was aborted, return early (don't send error response)
|
|
64
|
-
if (error.name === 'AbortError' || abortController.signal.aborted) {
|
|
65
|
-
return new Response(null, { status: 499 }) // 499 = Client Closed Request
|
|
66
|
-
}
|
|
67
|
-
return new Response(
|
|
68
|
-
JSON.stringify({ error: 'Failed to process chat request' }),
|
|
69
|
-
{
|
|
70
|
-
status: 500,
|
|
71
|
-
headers: { 'Content-Type': 'application/json' },
|
|
72
|
-
},
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
})
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Link, createFileRoute } from '@tanstack/react-router'
|
|
2
|
-
|
|
3
|
-
export const Route = createFileRoute('/checkout/cancel')({
|
|
4
|
-
component: CheckoutCancel,
|
|
5
|
-
})
|
|
6
|
-
|
|
7
|
-
function CheckoutCancel() {
|
|
8
|
-
return (
|
|
9
|
-
<div className="bg-black text-white min-h-screen flex items-center justify-center p-5">
|
|
10
|
-
<div className="bg-gray-900/60 backdrop-blur-md rounded-2xl p-12 border border-gray-800/50 shadow-xl text-center max-w-lg">
|
|
11
|
-
<div className="text-gray-400 text-6xl mb-6">✕</div>
|
|
12
|
-
<h1 className="text-3xl font-bold mb-4">Checkout Cancelled</h1>
|
|
13
|
-
<p className="text-gray-300 mb-8">
|
|
14
|
-
Your payment was cancelled. No charges were made.
|
|
15
|
-
</p>
|
|
16
|
-
<Link
|
|
17
|
-
to="/"
|
|
18
|
-
className="inline-block bg-emerald-600 hover:bg-emerald-500 text-white px-6 py-3 rounded-lg transition-colors"
|
|
19
|
-
>
|
|
20
|
-
Back to Home
|
|
21
|
-
</Link>
|
|
22
|
-
</div>
|
|
23
|
-
</div>
|
|
24
|
-
)
|
|
25
|
-
}
|