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
package/add-ons/ai/README.md
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
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.
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
### AI Integration
|
|
2
|
-
|
|
3
|
-
Multi-provider AI chat with fallback chain:
|
|
4
|
-
|
|
5
|
-
1. Anthropic Claude (preferred)
|
|
6
|
-
2. OpenAI GPT-4o
|
|
7
|
-
3. Google Gemini
|
|
8
|
-
4. Ollama (local)
|
|
9
|
-
|
|
10
|
-
**Tools available to AI:**
|
|
11
|
-
- `getWeather` - Get the current weather for a city. Returns temperature, condition, and humidity.
|
|
12
|
-
|
|
13
|
-
## Environment Variables
|
|
14
|
-
|
|
15
|
-
For AI functionality, set one or more:
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
ANTHROPIC_API_KEY=...
|
|
19
|
-
OPENAI_API_KEY=...
|
|
20
|
-
GOOGLE_GENERATIVE_AI_API_KEY=...
|
|
21
|
-
OLLAMA_BASE_URL=...
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
The application uses the first available provider in the fallback chain.
|
|
@@ -1,13 +0,0 @@
|
|
|
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
|
|
@@ -1,149 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
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
|
-
})
|
|
@@ -1,94 +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
|
-
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
|
-
})
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
@import "tailwindcss";
|
|
2
|
-
@import "highlight.js/styles/github-dark.css";
|
|
3
|
-
@source "../../../../node_modules/streamdown/dist/*.js";
|
|
4
|
-
|
|
5
|
-
/* Custom scrollbar styles */
|
|
6
|
-
::-webkit-scrollbar {
|
|
7
|
-
width: 8px;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
::-webkit-scrollbar-track {
|
|
11
|
-
background: transparent;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
::-webkit-scrollbar-thumb {
|
|
15
|
-
background-color: rgba(156, 163, 175, 0.5);
|
|
16
|
-
border-radius: 4px;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
::-webkit-scrollbar-thumb:hover {
|
|
20
|
-
background-color: rgba(156, 163, 175, 0.7);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/* Smooth transitions for dark mode */
|
|
24
|
-
html {
|
|
25
|
-
transition: background-color 0.3s ease;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/* Markdown content styles */
|
|
29
|
-
.prose {
|
|
30
|
-
max-width: none;
|
|
31
|
-
color: #e5e7eb; /* text-gray-200 */
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.prose code {
|
|
35
|
-
color: #e5e7eb;
|
|
36
|
-
background-color: rgba(31, 41, 55, 0.5);
|
|
37
|
-
padding: 0.2em 0.4em;
|
|
38
|
-
border-radius: 0.375rem;
|
|
39
|
-
font-size: 0.875em;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.prose pre {
|
|
43
|
-
background-color: rgba(31, 41, 55, 0.5);
|
|
44
|
-
border-radius: 0.5rem;
|
|
45
|
-
padding: 1rem;
|
|
46
|
-
margin: 1.25em 0;
|
|
47
|
-
overflow-x: auto;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
.prose pre code {
|
|
51
|
-
background-color: transparent;
|
|
52
|
-
padding: 0;
|
|
53
|
-
border-radius: 0;
|
|
54
|
-
color: inherit;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.prose h1,
|
|
58
|
-
.prose h2,
|
|
59
|
-
.prose h3,
|
|
60
|
-
.prose h4 {
|
|
61
|
-
color: #f9fafb; /* text-gray-50 */
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.prose ul,
|
|
65
|
-
.prose ol {
|
|
66
|
-
margin-top: 1.25em;
|
|
67
|
-
margin-bottom: 1.25em;
|
|
68
|
-
padding-left: 1.625em;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.prose li {
|
|
72
|
-
margin-top: 0.5em;
|
|
73
|
-
margin-bottom: 0.5em;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.prose blockquote {
|
|
77
|
-
border-left-color: #f97316; /* orange-500 */
|
|
78
|
-
background-color: rgba(249, 115, 22, 0.1);
|
|
79
|
-
padding: 1em;
|
|
80
|
-
margin: 1.25em 0;
|
|
81
|
-
border-radius: 0.5rem;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.prose hr {
|
|
85
|
-
border-color: rgba(249, 115, 22, 0.2);
|
|
86
|
-
margin: 2em 0;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.prose a {
|
|
90
|
-
color: #f97316; /* orange-500 */
|
|
91
|
-
text-decoration: underline;
|
|
92
|
-
text-decoration-thickness: 0.1em;
|
|
93
|
-
text-underline-offset: 0.2em;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.prose a:hover {
|
|
97
|
-
color: #fb923c; /* orange-400 */
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.prose table {
|
|
101
|
-
width: 100%;
|
|
102
|
-
border-collapse: collapse;
|
|
103
|
-
margin: 1.25em 0;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.prose th,
|
|
107
|
-
.prose td {
|
|
108
|
-
padding: 0.75em;
|
|
109
|
-
border: 1px solid rgba(249, 115, 22, 0.2);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.prose th {
|
|
113
|
-
background-color: rgba(249, 115, 22, 0.1);
|
|
114
|
-
font-weight: 600;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/* Message transition animations */
|
|
118
|
-
.message-enter {
|
|
119
|
-
opacity: 0;
|
|
120
|
-
transform: translateY(10px);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.message-enter-active {
|
|
124
|
-
opacity: 1;
|
|
125
|
-
transform: translateY(0);
|
|
126
|
-
transition:
|
|
127
|
-
opacity 300ms,
|
|
128
|
-
transform 300ms;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
.message-exit {
|
|
132
|
-
opacity: 1;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.message-exit-active {
|
|
136
|
-
opacity: 0;
|
|
137
|
-
transition: opacity 300ms;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/* Heading styles */
|
|
141
|
-
.prose h1 {
|
|
142
|
-
font-size: 2em;
|
|
143
|
-
margin-bottom: 0.5em;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
.prose h2 {
|
|
147
|
-
font-size: 1.5em;
|
|
148
|
-
margin-top: 1em;
|
|
149
|
-
margin-bottom: 0.5em;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.prose h3 {
|
|
153
|
-
font-size: 1.25em;
|
|
154
|
-
margin-top: 1em;
|
|
155
|
-
margin-bottom: 0.5em;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.prose ul {
|
|
159
|
-
list-style-type: disc;
|
|
160
|
-
padding-left: 1.5em;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.prose ol {
|
|
164
|
-
list-style-type: decimal;
|
|
165
|
-
padding-left: 1.5em;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.prose strong {
|
|
169
|
-
color: #f9fafb; /* text-gray-50 */
|
|
170
|
-
font-weight: 600;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.prose em {
|
|
174
|
-
font-style: italic;
|
|
175
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
3
|
-
import { Send, Square } from 'lucide-react'
|
|
4
|
-
import { Streamdown } from 'streamdown'
|
|
5
|
-
|
|
6
|
-
import { useAIChat } from '@/lib/ai-hook'
|
|
7
|
-
import type { ChatMessages } from '@/lib/ai-hook'
|
|
8
|
-
|
|
9
|
-
import './chat.css'
|
|
10
|
-
|
|
11
|
-
function Messages({ messages }: { messages: ChatMessages }) {
|
|
12
|
-
const messagesContainerRef = useRef<HTMLDivElement>(null)
|
|
13
|
-
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
if (messagesContainerRef.current) {
|
|
16
|
-
messagesContainerRef.current.scrollTop =
|
|
17
|
-
messagesContainerRef.current.scrollHeight
|
|
18
|
-
}
|
|
19
|
-
}, [messages])
|
|
20
|
-
|
|
21
|
-
if (!messages.length) {
|
|
22
|
-
return null
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<div
|
|
27
|
-
ref={messagesContainerRef}
|
|
28
|
-
className="flex-1 overflow-y-auto pb-4 min-h-0"
|
|
29
|
-
>
|
|
30
|
-
<div className="max-w-3xl mx-auto w-full px-4">
|
|
31
|
-
{messages.map((message) => (
|
|
32
|
-
<div
|
|
33
|
-
key={message.id}
|
|
34
|
-
className={`p-4 ${
|
|
35
|
-
message.role === 'assistant'
|
|
36
|
-
? 'bg-linear-to-r from-orange-500/5 to-red-600/5'
|
|
37
|
-
: 'bg-transparent'
|
|
38
|
-
}`}
|
|
39
|
-
>
|
|
40
|
-
<div className="flex items-start gap-4 max-w-3xl mx-auto w-full">
|
|
41
|
-
<div className={`w-8 h-8 rounded-lg flex items-center justify-center text-sm font-medium text-white flex-shrink-0 ${
|
|
42
|
-
message.role === 'assistant'
|
|
43
|
-
? 'bg-linear-to-r from-orange-500 to-red-600'
|
|
44
|
-
: 'bg-gray-700'
|
|
45
|
-
}`}>
|
|
46
|
-
{message.role === 'assistant' ? 'AI' : 'Y'}
|
|
47
|
-
</div>
|
|
48
|
-
<div className="flex-1 min-w-0">
|
|
49
|
-
{message.parts.map((part, index) => {
|
|
50
|
-
if (part.type === 'text' && part.content) {
|
|
51
|
-
return (
|
|
52
|
-
<div
|
|
53
|
-
className="flex-1 min-w-0 prose dark:prose-invert max-w-none prose-sm"
|
|
54
|
-
key={index}
|
|
55
|
-
>
|
|
56
|
-
<Streamdown>{part.content}</Streamdown>
|
|
57
|
-
</div>
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
return null
|
|
61
|
-
})}
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
))}
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function ChatPage() {
|
|
72
|
-
const [input, setInput] = useState('')
|
|
73
|
-
const { messages, sendMessage, isLoading, stop } = useAIChat()
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<div className="relative flex h-[calc(100vh-80px)] bg-gray-900">
|
|
77
|
-
<div className="flex-1 flex flex-col min-h-0">
|
|
78
|
-
{messages.length === 0 && (
|
|
79
|
-
<div className="flex-1 flex items-center justify-center px-4">
|
|
80
|
-
<div className="text-center max-w-3xl mx-auto w-full">
|
|
81
|
-
<h1 className="text-4xl font-bold mb-4 text-white">
|
|
82
|
-
Weather Chat
|
|
83
|
-
</h1>
|
|
84
|
-
<p className="text-gray-400 mb-6">
|
|
85
|
-
Ask me about the weather! Try "What's the weather in Paris?"
|
|
86
|
-
</p>
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
)}
|
|
90
|
-
<Messages messages={messages} />
|
|
91
|
-
|
|
92
|
-
<div className="sticky bottom-0 left-0 right-0 bg-gray-900/80 backdrop-blur-sm border-t border-orange-500/10 z-10">
|
|
93
|
-
<div className="max-w-3xl mx-auto w-full px-4 py-3">
|
|
94
|
-
{isLoading && (
|
|
95
|
-
<div className="flex items-center justify-center mb-3">
|
|
96
|
-
<button
|
|
97
|
-
onClick={stop}
|
|
98
|
-
className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg text-sm font-medium transition-colors flex items-center gap-2"
|
|
99
|
-
>
|
|
100
|
-
<Square className="w-4 h-4 fill-current" />
|
|
101
|
-
Stop
|
|
102
|
-
</button>
|
|
103
|
-
</div>
|
|
104
|
-
)}
|
|
105
|
-
<form
|
|
106
|
-
onSubmit={(e) => {
|
|
107
|
-
e.preventDefault()
|
|
108
|
-
if (input.trim()) {
|
|
109
|
-
sendMessage(input)
|
|
110
|
-
setInput('')
|
|
111
|
-
}
|
|
112
|
-
}}
|
|
113
|
-
>
|
|
114
|
-
<div className="relative max-w-xl mx-auto flex items-center gap-2">
|
|
115
|
-
<input
|
|
116
|
-
type="text"
|
|
117
|
-
value={input}
|
|
118
|
-
onChange={(e) => setInput(e.target.value)}
|
|
119
|
-
placeholder="Ask about the weather..."
|
|
120
|
-
className="w-full rounded-lg border border-orange-500/20 bg-gray-800/50 px-4 py-3 text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-orange-500/50"
|
|
121
|
-
disabled={isLoading}
|
|
122
|
-
/>
|
|
123
|
-
<button
|
|
124
|
-
type="submit"
|
|
125
|
-
disabled={!input.trim() || isLoading}
|
|
126
|
-
className="p-3 bg-orange-500 hover:bg-orange-600 disabled:bg-gray-600 text-white rounded-lg transition-colors"
|
|
127
|
-
>
|
|
128
|
-
<Send className="w-4 h-4" />
|
|
129
|
-
</button>
|
|
130
|
-
</div>
|
|
131
|
-
</form>
|
|
132
|
-
</div>
|
|
133
|
-
</div>
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
136
|
-
)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export const Route = createFileRoute('/chat')({
|
|
140
|
-
component: ChatPage,
|
|
141
|
-
})
|