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,52 +0,0 @@
|
|
|
1
|
-
import { Link } from '@tanstack/react-router'
|
|
2
|
-
|
|
3
|
-
import HeaderNav from './HeaderNav'
|
|
4
|
-
import { showVacayAssistant } from './VacayAssistant'
|
|
5
|
-
|
|
6
|
-
import { allPosts } from 'content-collections'
|
|
7
|
-
|
|
8
|
-
export default function Header() {
|
|
9
|
-
const categories = Array.from(
|
|
10
|
-
new Set(allPosts.flatMap((post) => post.categories)),
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
const handleVacayClick = () => {
|
|
14
|
-
showVacayAssistant.setState(() => true)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<>
|
|
19
|
-
<HeaderNav />
|
|
20
|
-
<header className="text-slate-700 font-serif font-extrabold fixed top-20 left-0 right-0 z-0 backdrop-blur-md bg-white/50 border-b border-slate-200/50 transition-all">
|
|
21
|
-
<nav className="max-w-7xl mx-auto p-4 flex gap-2 justify-between">
|
|
22
|
-
<div className="flex flex-row items-center space-x-6">
|
|
23
|
-
<div className="text-xl tracking-wide">
|
|
24
|
-
<Link to="/" className="hover:text-emerald-700 transition-colors">
|
|
25
|
-
Hawaii Adventures
|
|
26
|
-
</Link>
|
|
27
|
-
</div>
|
|
28
|
-
<div className="flex gap-6">
|
|
29
|
-
{categories.map((category) => (
|
|
30
|
-
<div key={category}>
|
|
31
|
-
<Link
|
|
32
|
-
to={`/category/${category}`}
|
|
33
|
-
className="hover:text-emerald-700 transition-colors"
|
|
34
|
-
>
|
|
35
|
-
{category}
|
|
36
|
-
</Link>
|
|
37
|
-
</div>
|
|
38
|
-
))}
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
<button
|
|
42
|
-
onClick={handleVacayClick}
|
|
43
|
-
className="flex items-center gap-2 px-4 py-2 rounded-xl bg-linear-to-r from-amber-400/90 via-orange-500/90 to-rose-500/90 hover:from-amber-400 hover:via-orange-500 hover:to-rose-500 text-white font-semibold text-sm transition-all hover:shadow-lg hover:shadow-orange-500/20 hover:scale-[1.02]"
|
|
44
|
-
>
|
|
45
|
-
<span className="text-base">🌴</span>
|
|
46
|
-
<span className="tracking-wide">Vacay Assistant</span>
|
|
47
|
-
</button>
|
|
48
|
-
</nav>
|
|
49
|
-
</header>
|
|
50
|
-
</>
|
|
51
|
-
)
|
|
52
|
-
}
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { Send, X, Palmtree, Sun } from 'lucide-react'
|
|
3
|
-
import { Streamdown } from 'streamdown'
|
|
4
|
-
import { Store } from '@tanstack/store'
|
|
5
|
-
|
|
6
|
-
import { useBlogChat } from '@/lib/blog-ai-hook'
|
|
7
|
-
import type { BlogChatMessages } from '@/lib/blog-ai-hook'
|
|
8
|
-
|
|
9
|
-
function Messages({ messages }: { messages: BlogChatMessages }) {
|
|
10
|
-
const messagesContainerRef = useRef<HTMLDivElement>(null)
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
if (messagesContainerRef.current) {
|
|
14
|
-
messagesContainerRef.current.scrollTop =
|
|
15
|
-
messagesContainerRef.current.scrollHeight
|
|
16
|
-
}
|
|
17
|
-
}, [messages])
|
|
18
|
-
|
|
19
|
-
if (!messages.length) {
|
|
20
|
-
return (
|
|
21
|
-
<div className="flex-1 flex flex-col items-center justify-center text-amber-200/60 text-sm px-6 py-8">
|
|
22
|
-
<div className="relative mb-4">
|
|
23
|
-
<Sun className="w-12 h-12 text-amber-400/40 animate-pulse" />
|
|
24
|
-
<Palmtree className="w-6 h-6 text-emerald-400/60 absolute -bottom-1 -right-1" />
|
|
25
|
-
</div>
|
|
26
|
-
<p className="text-center text-amber-100/80 font-medium">
|
|
27
|
-
Aloha! 🌺 How can I help?
|
|
28
|
-
</p>
|
|
29
|
-
<p className="text-xs text-amber-200/40 mt-2 text-center max-w-[200px]">
|
|
30
|
-
Ask about adventures, get trip ideas, or explore the islands!
|
|
31
|
-
</p>
|
|
32
|
-
</div>
|
|
33
|
-
)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<div ref={messagesContainerRef} className="flex-1 overflow-y-auto">
|
|
38
|
-
{messages.map(({ id, role, parts }) => (
|
|
39
|
-
<div
|
|
40
|
-
key={id}
|
|
41
|
-
className={`py-3 ${
|
|
42
|
-
role === 'assistant'
|
|
43
|
-
? 'bg-linear-to-r from-amber-500/5 via-orange-500/5 to-rose-500/5'
|
|
44
|
-
: 'bg-transparent'
|
|
45
|
-
}`}
|
|
46
|
-
>
|
|
47
|
-
{parts.map((part, index) => {
|
|
48
|
-
if (part.type === 'text' && part.content) {
|
|
49
|
-
return (
|
|
50
|
-
<div key={index} className="flex items-start gap-3 px-4">
|
|
51
|
-
{role === 'assistant' ? (
|
|
52
|
-
<div className="w-7 h-7 rounded-full bg-linear-to-br from-amber-400 via-orange-500 to-rose-500 flex items-center justify-center text-xs font-bold text-white flex-shrink-0 shadow-lg shadow-orange-500/20">
|
|
53
|
-
🌴
|
|
54
|
-
</div>
|
|
55
|
-
) : (
|
|
56
|
-
<div className="w-7 h-7 rounded-full bg-slate-600 flex items-center justify-center text-xs font-medium text-white flex-shrink-0">
|
|
57
|
-
You
|
|
58
|
-
</div>
|
|
59
|
-
)}
|
|
60
|
-
<div className="flex-1 min-w-0 text-amber-50 prose dark:prose-invert max-w-none prose-sm prose-p:text-amber-50 prose-headings:text-amber-100 prose-strong:text-amber-200">
|
|
61
|
-
<Streamdown>{part.content}</Streamdown>
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
return null
|
|
67
|
-
})}
|
|
68
|
-
</div>
|
|
69
|
-
))}
|
|
70
|
-
</div>
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
interface VacayAssistantProps {
|
|
75
|
-
slug?: string
|
|
76
|
-
postTitle?: string
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Export store for header control
|
|
80
|
-
export const showVacayAssistant = new Store(false)
|
|
81
|
-
|
|
82
|
-
export default function VacayAssistant({
|
|
83
|
-
slug,
|
|
84
|
-
postTitle,
|
|
85
|
-
}: VacayAssistantProps) {
|
|
86
|
-
const [isOpen, setIsOpen] = useState(false)
|
|
87
|
-
const { messages, sendMessage, isLoading } = useBlogChat(slug)
|
|
88
|
-
const [input, setInput] = useState('')
|
|
89
|
-
|
|
90
|
-
// Sync with store for header control
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
return showVacayAssistant.subscribe(() => {
|
|
93
|
-
setIsOpen(showVacayAssistant.state)
|
|
94
|
-
})
|
|
95
|
-
}, [])
|
|
96
|
-
|
|
97
|
-
const handleToggle = () => {
|
|
98
|
-
const newState = !isOpen
|
|
99
|
-
setIsOpen(newState)
|
|
100
|
-
showVacayAssistant.setState(() => newState)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const handleSend = () => {
|
|
104
|
-
if (input.trim()) {
|
|
105
|
-
sendMessage(input)
|
|
106
|
-
setInput('')
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (!isOpen) return null
|
|
111
|
-
|
|
112
|
-
return (
|
|
113
|
-
<div className="fixed top-20 right-4 z-[100] w-[400px] h-[520px] rounded-3xl shadow-2xl flex flex-col overflow-hidden border border-amber-500/20 backdrop-blur-xl bg-linear-to-b from-slate-900/98 via-slate-900/95 to-slate-800/98">
|
|
114
|
-
{/* Decorative top gradient */}
|
|
115
|
-
<div className="absolute top-0 left-0 right-0 h-32 bg-linear-to-b from-amber-500/10 via-orange-500/5 to-transparent pointer-events-none" />
|
|
116
|
-
|
|
117
|
-
{/* Header */}
|
|
118
|
-
<div className="relative flex items-center justify-between p-4 border-b border-amber-500/10">
|
|
119
|
-
<div className="flex items-center gap-3">
|
|
120
|
-
<div className="w-10 h-10 rounded-2xl bg-linear-to-br from-amber-400 via-orange-500 to-rose-500 flex items-center justify-center shadow-lg shadow-orange-500/30 rotate-3 hover:rotate-0 transition-transform">
|
|
121
|
-
<span className="text-lg">🌴</span>
|
|
122
|
-
</div>
|
|
123
|
-
<div>
|
|
124
|
-
<h3 className="font-bold text-amber-100 text-base tracking-tight">
|
|
125
|
-
Vacay Assistant
|
|
126
|
-
</h3>
|
|
127
|
-
{postTitle && (
|
|
128
|
-
<p className="text-xs text-amber-300/50 truncate max-w-[220px]">
|
|
129
|
-
📍 {postTitle}
|
|
130
|
-
</p>
|
|
131
|
-
)}
|
|
132
|
-
</div>
|
|
133
|
-
</div>
|
|
134
|
-
<button
|
|
135
|
-
onClick={handleToggle}
|
|
136
|
-
className="text-amber-300/50 hover:text-amber-100 transition-colors p-2 hover:bg-white/5 rounded-xl"
|
|
137
|
-
>
|
|
138
|
-
<X className="w-5 h-5" />
|
|
139
|
-
</button>
|
|
140
|
-
</div>
|
|
141
|
-
|
|
142
|
-
{/* Messages */}
|
|
143
|
-
<Messages messages={messages} />
|
|
144
|
-
|
|
145
|
-
{/* Loading indicator */}
|
|
146
|
-
{isLoading && (
|
|
147
|
-
<div className="px-4 py-3 border-t border-amber-500/10">
|
|
148
|
-
<div className="flex items-center gap-2 text-amber-400/80 text-xs">
|
|
149
|
-
<div className="flex gap-1">
|
|
150
|
-
<span className="w-2 h-2 bg-amber-400 rounded-full animate-bounce [animation-delay:-0.3s]"></span>
|
|
151
|
-
<span className="w-2 h-2 bg-orange-400 rounded-full animate-bounce [animation-delay:-0.15s]"></span>
|
|
152
|
-
<span className="w-2 h-2 bg-rose-400 rounded-full animate-bounce"></span>
|
|
153
|
-
</div>
|
|
154
|
-
<span className="font-medium">Planning your adventure...</span>
|
|
155
|
-
</div>
|
|
156
|
-
</div>
|
|
157
|
-
)}
|
|
158
|
-
|
|
159
|
-
{/* Input */}
|
|
160
|
-
<div className="relative p-4 border-t border-amber-500/10 bg-slate-900/50">
|
|
161
|
-
<form
|
|
162
|
-
onSubmit={(e) => {
|
|
163
|
-
e.preventDefault()
|
|
164
|
-
handleSend()
|
|
165
|
-
}}
|
|
166
|
-
>
|
|
167
|
-
<div className="relative">
|
|
168
|
-
<textarea
|
|
169
|
-
value={input}
|
|
170
|
-
onChange={(e) => setInput(e.target.value)}
|
|
171
|
-
placeholder="Ask about Hawaii adventures..."
|
|
172
|
-
disabled={isLoading}
|
|
173
|
-
className="w-full rounded-2xl border border-amber-500/20 bg-slate-800/50 pl-4 pr-12 py-3 text-sm text-amber-50 placeholder-amber-200/30 focus:outline-none focus:ring-2 focus:ring-amber-500/40 focus:border-transparent resize-none overflow-hidden disabled:opacity-50 transition-all"
|
|
174
|
-
rows={1}
|
|
175
|
-
style={{ minHeight: '48px', maxHeight: '100px' }}
|
|
176
|
-
onInput={(e) => {
|
|
177
|
-
const target = e.target as HTMLTextAreaElement
|
|
178
|
-
target.style.height = 'auto'
|
|
179
|
-
target.style.height = Math.min(target.scrollHeight, 100) + 'px'
|
|
180
|
-
}}
|
|
181
|
-
onKeyDown={(e) => {
|
|
182
|
-
if (
|
|
183
|
-
e.key === 'Enter' &&
|
|
184
|
-
!e.shiftKey &&
|
|
185
|
-
input.trim() &&
|
|
186
|
-
!isLoading
|
|
187
|
-
) {
|
|
188
|
-
e.preventDefault()
|
|
189
|
-
handleSend()
|
|
190
|
-
}
|
|
191
|
-
}}
|
|
192
|
-
/>
|
|
193
|
-
<button
|
|
194
|
-
type="submit"
|
|
195
|
-
disabled={!input.trim() || isLoading}
|
|
196
|
-
className="absolute right-3 top-1/2 -translate-y-1/2 p-2 rounded-xl bg-linear-to-r from-amber-500 to-orange-500 text-white disabled:opacity-30 disabled:bg-gray-600 disabled:from-gray-600 disabled:to-gray-600 transition-all hover:shadow-lg hover:shadow-amber-500/20"
|
|
197
|
-
>
|
|
198
|
-
<Send className="w-4 h-4" />
|
|
199
|
-
</button>
|
|
200
|
-
</div>
|
|
201
|
-
</form>
|
|
202
|
-
</div>
|
|
203
|
-
</div>
|
|
204
|
-
)
|
|
205
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { Link } from "@tanstack/react-router";
|
|
2
|
-
|
|
3
|
-
import { type Post } from "content-collections";
|
|
4
|
-
|
|
5
|
-
import { Card, CardContent } from "@/components/ui/card";
|
|
6
|
-
|
|
7
|
-
export default function BlogPosts({
|
|
8
|
-
title,
|
|
9
|
-
posts,
|
|
10
|
-
}: {
|
|
11
|
-
title: string;
|
|
12
|
-
posts: Post[];
|
|
13
|
-
}) {
|
|
14
|
-
return (
|
|
15
|
-
<div>
|
|
16
|
-
<div className="container mx-auto max-w-7xl">
|
|
17
|
-
<h1 className="mb-16 text-7xl font-bold text-teal-900 text-center font-serif italic">
|
|
18
|
-
{title}
|
|
19
|
-
</h1>
|
|
20
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 px-4">
|
|
21
|
-
{posts.map((post, index) => (
|
|
22
|
-
<Link
|
|
23
|
-
to={`/posts/${post.slug}`}
|
|
24
|
-
key={post._meta.path}
|
|
25
|
-
className="group relative block"
|
|
26
|
-
>
|
|
27
|
-
<Card
|
|
28
|
-
className={`relative overflow-hidden transform transition-all duration-500 bg-white
|
|
29
|
-
${
|
|
30
|
-
index % 2 === 0
|
|
31
|
-
? "rotate-[-1deg] hover:rotate-[-2deg]"
|
|
32
|
-
: "rotate-[1deg] hover:rotate-[2deg]"
|
|
33
|
-
}
|
|
34
|
-
hover:scale-105 hover:z-10 hover:shadow-[0_20px_40px_rgba(0,0,0,0.25)]
|
|
35
|
-
before:absolute before:inset-0 before:z-10 before:border-[12px] before:border-white
|
|
36
|
-
after:absolute after:inset-0 after:z-0 after:bg-[radial-gradient(#00000005_1px,transparent_1px)] after:bg-[length:4px_4px]`}
|
|
37
|
-
>
|
|
38
|
-
<div className="relative z-20">
|
|
39
|
-
{/* Postcard Stamp */}
|
|
40
|
-
<div className="absolute top-4 right-4 w-16 h-20 border-2 border-dashed border-teal-900/20 rounded-sm" />
|
|
41
|
-
|
|
42
|
-
{/* Image Container */}
|
|
43
|
-
<div className="aspect-[3/2] relative overflow-hidden">
|
|
44
|
-
<div className="absolute inset-0 bg-black/10 z-10 transition-opacity group-hover:opacity-0" />
|
|
45
|
-
<img
|
|
46
|
-
src={`/${post.image}`}
|
|
47
|
-
alt={post.title}
|
|
48
|
-
className="object-cover w-full h-full"
|
|
49
|
-
/>
|
|
50
|
-
</div>
|
|
51
|
-
|
|
52
|
-
{/* Content */}
|
|
53
|
-
<CardContent className="p-6 bg-transparent">
|
|
54
|
-
<div className="space-y-4">
|
|
55
|
-
<h3 className="text-3xl font-serif italic text-teal-900 group-hover:text-teal-800 transition-colors">
|
|
56
|
-
{post.title}
|
|
57
|
-
</h3>
|
|
58
|
-
<p className="text-teal-800/80 font-serif">
|
|
59
|
-
{post.summary}
|
|
60
|
-
</p>
|
|
61
|
-
<p className="text-teal-700/60 text-sm italic font-serif">
|
|
62
|
-
{post.date}
|
|
63
|
-
</p>
|
|
64
|
-
</div>
|
|
65
|
-
</CardContent>
|
|
66
|
-
|
|
67
|
-
{/* Decorative Lines */}
|
|
68
|
-
<div className="absolute left-0 right-0 bottom-0 h-1 bg-[repeating-linear-gradient(45deg,transparent,transparent_2px,#0F766E20_2px,#0F766E20_4px)]" />
|
|
69
|
-
<div className="absolute left-0 top-0 bottom-0 w-1 bg-[repeating-linear-gradient(0deg,transparent,transparent_2px,#0F766E20_2px,#0F766E20_4px)]" />
|
|
70
|
-
</div>
|
|
71
|
-
</Card>
|
|
72
|
-
</Link>
|
|
73
|
-
))}
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
|
|
3
|
-
import { cn } from "@/lib/utils"
|
|
4
|
-
|
|
5
|
-
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
-
return (
|
|
7
|
-
<div
|
|
8
|
-
data-slot="card"
|
|
9
|
-
className={cn(
|
|
10
|
-
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
|
11
|
-
className
|
|
12
|
-
)}
|
|
13
|
-
{...props}
|
|
14
|
-
/>
|
|
15
|
-
)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
19
|
-
return (
|
|
20
|
-
<div
|
|
21
|
-
data-slot="card-header"
|
|
22
|
-
className={cn(
|
|
23
|
-
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
|
24
|
-
className
|
|
25
|
-
)}
|
|
26
|
-
{...props}
|
|
27
|
-
/>
|
|
28
|
-
)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
32
|
-
return (
|
|
33
|
-
<div
|
|
34
|
-
data-slot="card-title"
|
|
35
|
-
className={cn("leading-none font-semibold", className)}
|
|
36
|
-
{...props}
|
|
37
|
-
/>
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
42
|
-
return (
|
|
43
|
-
<div
|
|
44
|
-
data-slot="card-description"
|
|
45
|
-
className={cn("text-muted-foreground text-sm", className)}
|
|
46
|
-
{...props}
|
|
47
|
-
/>
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
52
|
-
return (
|
|
53
|
-
<div
|
|
54
|
-
data-slot="card-action"
|
|
55
|
-
className={cn(
|
|
56
|
-
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
57
|
-
className
|
|
58
|
-
)}
|
|
59
|
-
{...props}
|
|
60
|
-
/>
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
65
|
-
return (
|
|
66
|
-
<div
|
|
67
|
-
data-slot="card-content"
|
|
68
|
-
className={cn("px-6", className)}
|
|
69
|
-
{...props}
|
|
70
|
-
/>
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
75
|
-
return (
|
|
76
|
-
<div
|
|
77
|
-
data-slot="card-footer"
|
|
78
|
-
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
|
79
|
-
{...props}
|
|
80
|
-
/>
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export {
|
|
85
|
-
Card,
|
|
86
|
-
CardHeader,
|
|
87
|
-
CardFooter,
|
|
88
|
-
CardTitle,
|
|
89
|
-
CardAction,
|
|
90
|
-
CardDescription,
|
|
91
|
-
CardContent,
|
|
92
|
-
}
|
|
@@ -1,25 +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 type inference
|
|
9
|
-
const defaultChatOptions = createChatClientOptions({
|
|
10
|
-
connection: fetchServerSentEvents("/api/blog-chat"),
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
export type BlogChatMessages = InferChatMessages<typeof defaultChatOptions>;
|
|
14
|
-
|
|
15
|
-
export const useBlogChat = (slug?: string) => {
|
|
16
|
-
const chatOptions = createChatClientOptions({
|
|
17
|
-
connection: fetchServerSentEvents("/api/blog-chat", {
|
|
18
|
-
body: {
|
|
19
|
-
slug,
|
|
20
|
-
},
|
|
21
|
-
}),
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
return useChat(chatOptions);
|
|
25
|
-
};
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { toolDefinition } from '@tanstack/ai'
|
|
2
|
-
import { z } from 'zod'
|
|
3
|
-
|
|
4
|
-
import { allPosts } from 'content-collections'
|
|
5
|
-
|
|
6
|
-
// Tool definition for getting the current blog post content
|
|
7
|
-
export const getPostBySlugToolDef = toolDefinition({
|
|
8
|
-
name: 'getCurrentBlogPost',
|
|
9
|
-
description:
|
|
10
|
-
'Get the full content and metadata of the current blog post the user is viewing. Use this to answer questions about the article.',
|
|
11
|
-
inputSchema: z.object({
|
|
12
|
-
slug: z.string().describe('The slug of the current blog post'),
|
|
13
|
-
}),
|
|
14
|
-
outputSchema: z.object({
|
|
15
|
-
title: z.string(),
|
|
16
|
-
summary: z.string(),
|
|
17
|
-
content: z.string(),
|
|
18
|
-
categories: z.array(z.string()),
|
|
19
|
-
date: z.string(),
|
|
20
|
-
image: z.string(),
|
|
21
|
-
}),
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
// Server implementation
|
|
25
|
-
export const getPostBySlug = getPostBySlugToolDef.server(({ slug }) => {
|
|
26
|
-
const post = allPosts.find((post) => post.slug === slug)
|
|
27
|
-
if (!post) {
|
|
28
|
-
return {
|
|
29
|
-
title: 'Post not found',
|
|
30
|
-
summary: '',
|
|
31
|
-
content: 'The requested blog post was not found.',
|
|
32
|
-
categories: [],
|
|
33
|
-
date: '',
|
|
34
|
-
image: '',
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return {
|
|
38
|
-
title: post.title,
|
|
39
|
-
summary: post.summary,
|
|
40
|
-
content: post.content,
|
|
41
|
-
categories: post.categories,
|
|
42
|
-
date: post.date,
|
|
43
|
-
image: post.image,
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
// Tool definition for listing all available blog posts
|
|
48
|
-
export const getAllBlogPostsToolDef = toolDefinition({
|
|
49
|
-
name: 'getAllBlogPosts',
|
|
50
|
-
description:
|
|
51
|
-
'Get a list of all available blog posts with their titles, summaries, and categories. Useful for recommending related content or answering questions about other posts.',
|
|
52
|
-
inputSchema: z.object({}),
|
|
53
|
-
outputSchema: z.array(
|
|
54
|
-
z.object({
|
|
55
|
-
slug: z.string(),
|
|
56
|
-
title: z.string(),
|
|
57
|
-
summary: z.string(),
|
|
58
|
-
categories: z.array(z.string()),
|
|
59
|
-
date: z.string(),
|
|
60
|
-
}),
|
|
61
|
-
),
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
// Server implementation
|
|
65
|
-
export const getAllBlogPosts = getAllBlogPostsToolDef.server(() => {
|
|
66
|
-
return allPosts.map((post) => ({
|
|
67
|
-
slug: post.slug,
|
|
68
|
-
title: post.title,
|
|
69
|
-
summary: post.summary,
|
|
70
|
-
categories: post.categories,
|
|
71
|
-
date: post.date,
|
|
72
|
-
}))
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
// Tool definition for suggesting related posts
|
|
76
|
-
export const searchBlogPostsToolDef = toolDefinition({
|
|
77
|
-
name: 'searchBlogPosts',
|
|
78
|
-
description:
|
|
79
|
-
"Search for blog posts by title, summary, or categories. Use this to find articles that match the user's query.",
|
|
80
|
-
inputSchema: z.object({
|
|
81
|
-
query: z.string().describe('The search query'),
|
|
82
|
-
}),
|
|
83
|
-
outputSchema: z.array(
|
|
84
|
-
z.object({
|
|
85
|
-
slug: z.string(),
|
|
86
|
-
title: z.string(),
|
|
87
|
-
summary: z.string(),
|
|
88
|
-
categories: z.array(z.string()),
|
|
89
|
-
sharedCategories: z.array(z.string()),
|
|
90
|
-
}),
|
|
91
|
-
),
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
// Server implementation
|
|
95
|
-
export const searchBlogPosts = searchBlogPostsToolDef.server(({ query }) => {
|
|
96
|
-
return allPosts
|
|
97
|
-
.filter(
|
|
98
|
-
(post) =>
|
|
99
|
-
post.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
100
|
-
post.summary.toLowerCase().includes(query.toLowerCase()) ||
|
|
101
|
-
post.categories.some((cat) =>
|
|
102
|
-
cat.toLowerCase().includes(query.toLowerCase()),
|
|
103
|
-
),
|
|
104
|
-
)
|
|
105
|
-
.map((post) => ({
|
|
106
|
-
slug: post.slug,
|
|
107
|
-
title: post.title,
|
|
108
|
-
summary: post.summary,
|
|
109
|
-
categories: post.categories,
|
|
110
|
-
}))
|
|
111
|
-
})
|
|
@@ -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: 'Hawaii Adventures Blog',
|
|
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-[#f5f1e4] min-h-screen">
|
|
40
|
-
<Header />
|
|
41
|
-
<main className="mt-20">{children}</main>
|
|
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
|
-
}
|