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.
Files changed (296) hide show
  1. package/dist/cli.js +104 -208
  2. package/dist/index.js +1 -31
  3. package/dist/types/cli.d.ts +1 -8
  4. package/dist/types/types.d.ts +7 -12
  5. package/package.json +1 -2
  6. package/src/cli.ts +129 -271
  7. package/src/index.ts +1 -47
  8. package/src/types.ts +7 -13
  9. package/CONTEXT.md +0 -139
  10. package/add-ons/ai/README.md +0 -34
  11. package/add-ons/ai/assets/AGENTS.md.append +0 -24
  12. package/add-ons/ai/assets/_dot_env.local.append +0 -13
  13. package/add-ons/ai/assets/src/components/AIAssistant.tsx +0 -149
  14. package/add-ons/ai/assets/src/lib/ai-hook.ts +0 -21
  15. package/add-ons/ai/assets/src/lib/weather-tools.ts +0 -30
  16. package/add-ons/ai/assets/src/routes/api.chat.ts +0 -94
  17. package/add-ons/ai/assets/src/routes/chat.css +0 -175
  18. package/add-ons/ai/assets/src/routes/chat.tsx +0 -141
  19. package/add-ons/ai/info.json +0 -22
  20. package/add-ons/ai/package.json +0 -17
  21. package/add-ons/ai/small-logo.svg +0 -8
  22. package/add-ons/db/assets/AGENTS.md.append +0 -22
  23. package/add-ons/db/assets/DB-SETUP.md +0 -65
  24. package/add-ons/db/assets/_dot_env.local.append +0 -2
  25. package/add-ons/db/assets/drizzle.config.ts +0 -10
  26. package/add-ons/db/assets/src/db/index.ts +0 -8
  27. package/add-ons/db/assets/src/db/schema.ts +0 -8
  28. package/add-ons/db/assets/src/routes/db-example.tsx +0 -118
  29. package/add-ons/db/assets/src/server/guestbook.functions.ts +0 -23
  30. package/add-ons/db/info.json +0 -22
  31. package/add-ons/db/package.json +0 -10
  32. package/add-ons/forms/assets/AGENTS.md.append +0 -13
  33. package/add-ons/forms/assets/public/form-example.html +0 -14
  34. package/add-ons/forms/assets/src/routes/form-example.tsx +0 -76
  35. package/add-ons/forms/info.json +0 -18
  36. package/add-ons/forms/package.json +0 -3
  37. package/examples/ai-chat/README.md +0 -36
  38. package/examples/ai-chat/assets/AGENTS.md.append +0 -24
  39. package/examples/ai-chat/assets/src/lib/ai-hook.ts +0 -21
  40. package/examples/ai-chat/assets/src/lib/weather-tools.ts +0 -30
  41. package/examples/ai-chat/assets/src/routes/__root.tsx +0 -57
  42. package/examples/ai-chat/assets/src/routes/api.chat.ts +0 -94
  43. package/examples/ai-chat/assets/src/routes/index.tsx +0 -141
  44. package/examples/ai-chat/assets/src/styles.css +0 -165
  45. package/examples/ai-chat/info.json +0 -25
  46. package/examples/ai-chat/package.json +0 -14
  47. package/examples/blog/README.md +0 -60
  48. package/examples/blog/assets/AGENTS.md.append +0 -18
  49. package/examples/blog/assets/content/posts/beach.md +0 -12
  50. package/examples/blog/assets/content/posts/jungle.md.ejs +0 -12
  51. package/examples/blog/assets/content/posts/mountains.md.ejs +0 -12
  52. package/examples/blog/assets/content/posts/snorkeling.md.ejs +0 -12
  53. package/examples/blog/assets/content/posts/waterfall.md.ejs +0 -12
  54. package/examples/blog/assets/content-collections.ts +0 -30
  55. package/examples/blog/assets/public/beach.jpg +0 -0
  56. package/examples/blog/assets/public/jungle.jpg +0 -0
  57. package/examples/blog/assets/public/mountains.jpg +0 -0
  58. package/examples/blog/assets/public/snorkeling.jpg +0 -0
  59. package/examples/blog/assets/public/waterfall.jpg +0 -0
  60. package/examples/blog/assets/src/components/Header.tsx +0 -52
  61. package/examples/blog/assets/src/components/VacayAssistant.tsx +0 -205
  62. package/examples/blog/assets/src/components/blog-posts.tsx +0 -78
  63. package/examples/blog/assets/src/components/ui/card.tsx +0 -92
  64. package/examples/blog/assets/src/lib/blog-ai-hook.ts +0 -25
  65. package/examples/blog/assets/src/lib/blog-tools.ts +0 -111
  66. package/examples/blog/assets/src/lib/utils.ts +0 -6
  67. package/examples/blog/assets/src/routes/__root.tsx +0 -57
  68. package/examples/blog/assets/src/routes/api.blog-chat.ts +0 -117
  69. package/examples/blog/assets/src/routes/category.$category.tsx +0 -19
  70. package/examples/blog/assets/src/routes/index.tsx +0 -19
  71. package/examples/blog/assets/src/routes/posts.$slug.tsx +0 -63
  72. package/examples/blog/assets/src/styles.css +0 -138
  73. package/examples/blog/info.json +0 -40
  74. package/examples/blog/package.json +0 -23
  75. package/examples/calculator/README.md +0 -16
  76. package/examples/calculator/assets/AGENTS.md.append +0 -9
  77. package/examples/calculator/assets/src/components/Calculator.tsx +0 -238
  78. package/examples/calculator/assets/src/components/Header.tsx +0 -17
  79. package/examples/calculator/assets/src/routes/__root.tsx +0 -57
  80. package/examples/calculator/assets/src/routes/index.tsx +0 -14
  81. package/examples/calculator/info.json +0 -20
  82. package/examples/calculator/package.json +0 -1
  83. package/examples/dashboard/README.md +0 -17
  84. package/examples/dashboard/assets/AGENTS.md.append +0 -12
  85. package/examples/dashboard/assets/src/components/Header.tsx +0 -17
  86. package/examples/dashboard/assets/src/routes/__root.tsx +0 -57
  87. package/examples/dashboard/assets/src/routes/index.tsx +0 -158
  88. package/examples/dashboard/info.json +0 -19
  89. package/examples/dashboard/package.json +0 -6
  90. package/examples/ecommerce/README.md +0 -48
  91. package/examples/ecommerce/assets/AGENTS.md.append +0 -22
  92. package/examples/ecommerce/assets/public/logo.png +0 -0
  93. package/examples/ecommerce/assets/public/motorcycle-scooter.jpg +0 -0
  94. package/examples/ecommerce/assets/src/components/BuyButton.tsx +0 -35
  95. package/examples/ecommerce/assets/src/components/Header.tsx +0 -36
  96. package/examples/ecommerce/assets/src/components/MotorcycleAIAssistant.tsx +0 -162
  97. package/examples/ecommerce/assets/src/components/MotorcycleRecommendation.tsx +0 -53
  98. package/examples/ecommerce/assets/src/data/motorcycles.ts +0 -27
  99. package/examples/ecommerce/assets/src/lib/motorcycle-ai-hook.ts +0 -24
  100. package/examples/ecommerce/assets/src/lib/motorcycle-tools.ts +0 -42
  101. package/examples/ecommerce/assets/src/lib/stripe.server.ts +0 -39
  102. package/examples/ecommerce/assets/src/routes/__root.tsx +0 -57
  103. package/examples/ecommerce/assets/src/routes/api.motorcycle-chat.ts +0 -78
  104. package/examples/ecommerce/assets/src/routes/checkout/cancel.tsx +0 -25
  105. package/examples/ecommerce/assets/src/routes/checkout/success.tsx +0 -25
  106. package/examples/ecommerce/assets/src/routes/index.tsx +0 -76
  107. package/examples/ecommerce/assets/src/routes/motorcycles/$motorcycleId.tsx +0 -55
  108. package/examples/ecommerce/assets/src/store/motorcycle-assistant.ts +0 -3
  109. package/examples/ecommerce/assets/src/styles.css +0 -212
  110. package/examples/ecommerce/info.json +0 -38
  111. package/examples/ecommerce/package.json +0 -13
  112. package/examples/events/README.md +0 -110
  113. package/examples/events/assets/AGENTS.md.append +0 -21
  114. package/examples/events/assets/content/speakers/andre-costa.md +0 -22
  115. package/examples/events/assets/content/speakers/hans-mueller.md.ejs +0 -22
  116. package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +0 -22
  117. package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +0 -22
  118. package/examples/events/assets/content/speakers/marie-dubois.md.ejs +0 -20
  119. package/examples/events/assets/content/speakers/priya-sharma.md.ejs +0 -22
  120. package/examples/events/assets/content/talks/croissant-lamination-secrets.md +0 -39
  121. package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +0 -39
  122. package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +0 -39
  123. package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +0 -39
  124. package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +0 -36
  125. package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +0 -32
  126. package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +0 -39
  127. package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +0 -39
  128. package/examples/events/assets/content-collections.ts +0 -56
  129. package/examples/events/assets/public/background-1.jpg +0 -0
  130. package/examples/events/assets/public/background-2.jpg +0 -0
  131. package/examples/events/assets/public/background-3.jpg +0 -0
  132. package/examples/events/assets/public/background-4.jpg +0 -0
  133. package/examples/events/assets/public/conference-logo.png +0 -0
  134. package/examples/events/assets/public/favicon.ico +0 -0
  135. package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
  136. package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
  137. package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
  138. package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
  139. package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
  140. package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
  141. package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
  142. package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
  143. package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
  144. package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
  145. package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
  146. package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
  147. package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
  148. package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
  149. package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
  150. package/examples/events/assets/public/tanstack-word-logo-white.svg +0 -1
  151. package/examples/events/assets/src/components/Header.tsx +0 -59
  152. package/examples/events/assets/src/components/HeaderNav.tsx +0 -67
  153. package/examples/events/assets/src/components/HeroCarousel.tsx +0 -61
  154. package/examples/events/assets/src/components/RemyAssistant.tsx +0 -207
  155. package/examples/events/assets/src/components/SpeakerCard.tsx +0 -67
  156. package/examples/events/assets/src/components/TalkCard.tsx +0 -77
  157. package/examples/events/assets/src/components/ui/card.tsx +0 -92
  158. package/examples/events/assets/src/lib/conference-ai-hook.ts +0 -26
  159. package/examples/events/assets/src/lib/conference-tools.ts +0 -210
  160. package/examples/events/assets/src/lib/model-selection.ts +0 -1
  161. package/examples/events/assets/src/lib/utils.ts +0 -6
  162. package/examples/events/assets/src/routes/__root.tsx +0 -70
  163. package/examples/events/assets/src/routes/api.remy-chat.ts +0 -119
  164. package/examples/events/assets/src/routes/index.tsx +0 -192
  165. package/examples/events/assets/src/routes/schedule.index.tsx +0 -274
  166. package/examples/events/assets/src/routes/speakers.$slug.tsx +0 -122
  167. package/examples/events/assets/src/routes/speakers.index.tsx +0 -40
  168. package/examples/events/assets/src/routes/talks.$slug.tsx +0 -116
  169. package/examples/events/assets/src/routes/talks.index.tsx +0 -40
  170. package/examples/events/assets/src/styles.css +0 -182
  171. package/examples/events/info.json +0 -61
  172. package/examples/events/package.json +0 -23
  173. package/examples/marketing/README.md +0 -60
  174. package/examples/marketing/assets/AGENTS.md.append +0 -15
  175. package/examples/marketing/assets/public/logo.png +0 -0
  176. package/examples/marketing/assets/public/motorcycle-adventure.jpg +0 -0
  177. package/examples/marketing/assets/public/motorcycle-cruiser.jpg +0 -0
  178. package/examples/marketing/assets/public/motorcycle-scooter.jpg +0 -0
  179. package/examples/marketing/assets/public/motorcycle-sport.jpg +0 -0
  180. package/examples/marketing/assets/public/motorcycle-supersport.jpg +0 -0
  181. package/examples/marketing/assets/src/components/Header.tsx +0 -36
  182. package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +0 -162
  183. package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +0 -53
  184. package/examples/marketing/assets/src/data/motorcycles.ts.ejs +0 -77
  185. package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +0 -24
  186. package/examples/marketing/assets/src/lib/motorcycle-tools.ts +0 -42
  187. package/examples/marketing/assets/src/routes/__root.tsx +0 -57
  188. package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +0 -78
  189. package/examples/marketing/assets/src/routes/index.tsx +0 -72
  190. package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +0 -56
  191. package/examples/marketing/assets/src/store/motorcycle-assistant.ts +0 -3
  192. package/examples/marketing/assets/src/styles.css +0 -212
  193. package/examples/marketing/info.json +0 -33
  194. package/examples/marketing/package.json +0 -14
  195. package/examples/portfolio/README.md +0 -49
  196. package/examples/portfolio/assets/AGENTS.md.append +0 -21
  197. package/examples/portfolio/assets/content/blog/getting-started-with-tanstack.md +0 -53
  198. package/examples/portfolio/assets/content/blog/react-19-features.md +0 -78
  199. package/examples/portfolio/assets/content/blog/tailwind-css-v4-guide.md +0 -60
  200. package/examples/portfolio/assets/content/education/code-school.md +0 -17
  201. package/examples/portfolio/assets/content/jobs/initech-junior.md +0 -20
  202. package/examples/portfolio/assets/content/projects/portfolio-site.md +0 -15
  203. package/examples/portfolio/assets/content/projects/task-manager.md +0 -15
  204. package/examples/portfolio/assets/content-collections.ts +0 -65
  205. package/examples/portfolio/assets/public/contact.html +0 -6
  206. package/examples/portfolio/assets/public/headshot-on-white.jpg +0 -0
  207. package/examples/portfolio/assets/src/components/Header.tsx +0 -33
  208. package/examples/portfolio/assets/src/components/ResumeAssistant.tsx +0 -193
  209. package/examples/portfolio/assets/src/components/ui/badge.tsx +0 -46
  210. package/examples/portfolio/assets/src/components/ui/card.tsx +0 -92
  211. package/examples/portfolio/assets/src/components/ui/checkbox.tsx +0 -30
  212. package/examples/portfolio/assets/src/components/ui/hover-card.tsx +0 -44
  213. package/examples/portfolio/assets/src/components/ui/separator.tsx +0 -26
  214. package/examples/portfolio/assets/src/lib/resume-ai-hook.ts +0 -21
  215. package/examples/portfolio/assets/src/lib/resume-tools.ts +0 -165
  216. package/examples/portfolio/assets/src/lib/utils.ts +0 -6
  217. package/examples/portfolio/assets/src/routes/__root.tsx +0 -57
  218. package/examples/portfolio/assets/src/routes/api.resume-chat.ts +0 -116
  219. package/examples/portfolio/assets/src/routes/blog/$slug.tsx +0 -73
  220. package/examples/portfolio/assets/src/routes/contact.tsx +0 -121
  221. package/examples/portfolio/assets/src/routes/index.tsx +0 -55
  222. package/examples/portfolio/assets/src/routes/projects.tsx +0 -62
  223. package/examples/portfolio/assets/src/routes/resume.tsx +0 -220
  224. package/examples/portfolio/assets/src/styles.css +0 -138
  225. package/examples/portfolio/info.json +0 -50
  226. package/examples/portfolio/package.json +0 -26
  227. package/examples/resume/README.md +0 -82
  228. package/examples/resume/assets/AGENTS.md.append +0 -19
  229. package/examples/resume/assets/content/education/code-school.md +0 -17
  230. package/examples/resume/assets/content/jobs/freelance.md.ejs +0 -13
  231. package/examples/resume/assets/content/jobs/initech-junior.md +0 -20
  232. package/examples/resume/assets/content/jobs/initech-lead.md.ejs +0 -29
  233. package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +0 -28
  234. package/examples/resume/assets/content-collections.ts +0 -36
  235. package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
  236. package/examples/resume/assets/src/components/Header.tsx +0 -33
  237. package/examples/resume/assets/src/components/ResumeAssistant.tsx +0 -193
  238. package/examples/resume/assets/src/components/ui/badge.tsx +0 -46
  239. package/examples/resume/assets/src/components/ui/card.tsx +0 -92
  240. package/examples/resume/assets/src/components/ui/checkbox.tsx +0 -30
  241. package/examples/resume/assets/src/components/ui/hover-card.tsx +0 -44
  242. package/examples/resume/assets/src/components/ui/separator.tsx +0 -26
  243. package/examples/resume/assets/src/lib/resume-ai-hook.ts +0 -21
  244. package/examples/resume/assets/src/lib/resume-tools.ts +0 -165
  245. package/examples/resume/assets/src/lib/utils.ts +0 -6
  246. package/examples/resume/assets/src/routes/api.resume-chat.ts +0 -110
  247. package/examples/resume/assets/src/routes/index.tsx +0 -220
  248. package/examples/resume/assets/src/styles.css +0 -138
  249. package/examples/resume/info.json +0 -35
  250. package/examples/resume/package.json +0 -26
  251. package/examples/saas/README.md +0 -16
  252. package/examples/saas/assets/AGENTS.md.append +0 -9
  253. package/examples/saas/assets/src/components/Header.tsx +0 -17
  254. package/examples/saas/assets/src/routes/__root.tsx +0 -57
  255. package/examples/saas/assets/src/routes/faq.tsx +0 -94
  256. package/examples/saas/assets/src/routes/index.tsx +0 -197
  257. package/examples/saas/info.json +0 -23
  258. package/examples/saas/package.json +0 -1
  259. package/examples/survey/README.md +0 -20
  260. package/examples/survey/assets/AGENTS.md.append +0 -9
  261. package/examples/survey/assets/public/form-survey.html +0 -15
  262. package/examples/survey/assets/src/components/SurveyForm.tsx +0 -128
  263. package/examples/survey/assets/src/routes/index.tsx +0 -14
  264. package/examples/survey/info.json +0 -19
  265. package/examples/survey/package.json +0 -1
  266. package/project/base/AGENTS.md +0 -86
  267. package/project/base/_dot_claude/skills/content-collections/SKILL.md +0 -505
  268. package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +0 -410
  269. package/project/base/_dot_claude/skills/netlify-db/SKILL.md +0 -424
  270. package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +0 -419
  271. package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +0 -243
  272. package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +0 -372
  273. package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +0 -421
  274. package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +0 -426
  275. package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +0 -493
  276. package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +0 -430
  277. package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +0 -445
  278. package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +0 -494
  279. package/project/base/_dot_gitignore +0 -8
  280. package/project/base/netlify.toml +0 -7
  281. package/project/base/package.json +0 -33
  282. package/project/base/public/favicon.ico +0 -0
  283. package/project/base/public/tanstack-circle-logo.png +0 -0
  284. package/project/base/public/tanstack-word-logo-white.svg +0 -1
  285. package/project/base/src/components/Header.tsx +0 -17
  286. package/project/base/src/components/HeaderNav.tsx.ejs +0 -179
  287. package/project/base/src/router.tsx +0 -15
  288. package/project/base/src/routes/__root.tsx +0 -57
  289. package/project/base/src/routes/index.tsx +0 -48
  290. package/project/base/src/styles.css +0 -15
  291. package/project/base/tsconfig.json +0 -28
  292. package/project/base/vite.config.ts.ejs +0 -25
  293. package/project/info.json +0 -17
  294. package/project/packages.json +0 -22
  295. package/scripts/check-outdated-packages.js +0 -421
  296. 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,6 +0,0 @@
1
- import { clsx, type ClassValue } from 'clsx'
2
- import { twMerge } from 'tailwind-merge'
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs))
6
- }
@@ -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
- }