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,61 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react'
|
|
2
|
-
|
|
3
|
-
const backgrounds = [
|
|
4
|
-
'/background-1.jpg',
|
|
5
|
-
'/background-2.jpg',
|
|
6
|
-
'/background-3.jpg',
|
|
7
|
-
'/background-4.jpg',
|
|
8
|
-
]
|
|
9
|
-
|
|
10
|
-
export default function HeroCarousel() {
|
|
11
|
-
const [currentIndex, setCurrentIndex] = useState(0)
|
|
12
|
-
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
const interval = setInterval(() => {
|
|
15
|
-
setCurrentIndex((prev) => (prev + 1) % backgrounds.length)
|
|
16
|
-
}, 6000)
|
|
17
|
-
return () => clearInterval(interval)
|
|
18
|
-
}, [])
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<div className="absolute inset-0 overflow-hidden">
|
|
22
|
-
{/* Background images with crossfade */}
|
|
23
|
-
{backgrounds.map((bg, index) => (
|
|
24
|
-
<div
|
|
25
|
-
key={bg}
|
|
26
|
-
className={`absolute inset-0 transition-opacity duration-1000 ease-in-out ${
|
|
27
|
-
index === currentIndex ? 'opacity-100' : 'opacity-0'
|
|
28
|
-
}`}
|
|
29
|
-
>
|
|
30
|
-
<img
|
|
31
|
-
src={bg}
|
|
32
|
-
alt=""
|
|
33
|
-
className="w-full h-full object-cover"
|
|
34
|
-
/>
|
|
35
|
-
</div>
|
|
36
|
-
))}
|
|
37
|
-
|
|
38
|
-
{/* Dark overlay for text readability */}
|
|
39
|
-
<div className="absolute inset-0 bg-gradient-to-b from-charcoal/70 via-charcoal/60 to-charcoal" />
|
|
40
|
-
|
|
41
|
-
{/* Subtle vignette effect */}
|
|
42
|
-
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,transparent_0%,rgba(26,26,26,0.4)_100%)]" />
|
|
43
|
-
|
|
44
|
-
{/* Carousel indicators */}
|
|
45
|
-
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex gap-2 z-10">
|
|
46
|
-
{backgrounds.map((_, index) => (
|
|
47
|
-
<button
|
|
48
|
-
key={index}
|
|
49
|
-
onClick={() => setCurrentIndex(index)}
|
|
50
|
-
className={`w-2 h-2 rounded-full transition-all duration-300 ${
|
|
51
|
-
index === currentIndex
|
|
52
|
-
? 'bg-gold w-6'
|
|
53
|
-
: 'bg-cream/30 hover:bg-cream/50'
|
|
54
|
-
}`}
|
|
55
|
-
aria-label={`Go to slide ${index + 1}`}
|
|
56
|
-
/>
|
|
57
|
-
))}
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
)
|
|
61
|
-
}
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { Send, X, ChefHat, Croissant } from 'lucide-react'
|
|
3
|
-
import { Streamdown } from 'streamdown'
|
|
4
|
-
import { Store } from '@tanstack/store'
|
|
5
|
-
|
|
6
|
-
import { useConferenceChat } from '@/lib/conference-ai-hook'
|
|
7
|
-
import type { ConferenceChatMessages } from '@/lib/conference-ai-hook'
|
|
8
|
-
|
|
9
|
-
function Messages({ messages }: { messages: ConferenceChatMessages }) {
|
|
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-cream/60 text-sm px-6 py-8">
|
|
22
|
-
<div className="relative mb-4">
|
|
23
|
-
<ChefHat className="w-12 h-12 text-copper/60 animate-pulse" />
|
|
24
|
-
<Croissant className="w-6 h-6 text-gold/60 absolute -bottom-1 -right-1" />
|
|
25
|
-
</div>
|
|
26
|
-
<p className="text-center text-cream/80 font-medium font-display text-lg">
|
|
27
|
-
Bonjour! I'm Remy 👨🍳
|
|
28
|
-
</p>
|
|
29
|
-
<p className="text-xs text-cream/40 mt-2 text-center max-w-[220px]">
|
|
30
|
-
Your culinary guide to Haute Pâtisserie 2026. Ask about speakers, sessions, or pastry techniques!
|
|
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-gradient-to-r from-copper/5 via-gold/5 to-copper/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-gradient-to-br from-copper via-copper-dark to-gold flex items-center justify-center text-xs font-bold text-charcoal flex-shrink-0 shadow-lg shadow-copper/20">
|
|
53
|
-
👨🍳
|
|
54
|
-
</div>
|
|
55
|
-
) : (
|
|
56
|
-
<div className="w-7 h-7 rounded-full bg-charcoal-light flex items-center justify-center text-xs font-medium text-cream flex-shrink-0 border border-border/50">
|
|
57
|
-
You
|
|
58
|
-
</div>
|
|
59
|
-
)}
|
|
60
|
-
<div className="flex-1 min-w-0 text-cream prose dark:prose-invert max-w-none prose-sm prose-p:text-cream prose-headings:text-cream prose-strong:text-gold">
|
|
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 RemyAssistantProps {
|
|
75
|
-
speakerSlug?: string
|
|
76
|
-
talkSlug?: string
|
|
77
|
-
contextTitle?: string
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Export store for header control
|
|
81
|
-
export const showRemyAssistant = new Store(false)
|
|
82
|
-
|
|
83
|
-
export default function RemyAssistant({
|
|
84
|
-
speakerSlug,
|
|
85
|
-
talkSlug,
|
|
86
|
-
contextTitle,
|
|
87
|
-
}: RemyAssistantProps) {
|
|
88
|
-
const [isOpen, setIsOpen] = useState(false)
|
|
89
|
-
const { messages, sendMessage, isLoading } = useConferenceChat(speakerSlug, talkSlug)
|
|
90
|
-
const [input, setInput] = useState('')
|
|
91
|
-
|
|
92
|
-
// Sync with store for header control
|
|
93
|
-
useEffect(() => {
|
|
94
|
-
return showRemyAssistant.subscribe(() => {
|
|
95
|
-
setIsOpen(showRemyAssistant.state)
|
|
96
|
-
})
|
|
97
|
-
}, [])
|
|
98
|
-
|
|
99
|
-
const handleToggle = () => {
|
|
100
|
-
const newState = !isOpen
|
|
101
|
-
setIsOpen(newState)
|
|
102
|
-
showRemyAssistant.setState(() => newState)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const handleSend = () => {
|
|
106
|
-
if (input.trim()) {
|
|
107
|
-
sendMessage(input)
|
|
108
|
-
setInput('')
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (!isOpen) return null
|
|
113
|
-
|
|
114
|
-
return (
|
|
115
|
-
<div className="fixed top-36 right-4 z-[100] w-[400px] h-[520px] rounded-2xl shadow-2xl flex flex-col overflow-hidden border border-copper/20 backdrop-blur-xl bg-gradient-to-b from-charcoal/98 via-charcoal/95 to-charcoal-light/98">
|
|
116
|
-
{/* Decorative top gradient */}
|
|
117
|
-
<div className="absolute top-0 left-0 right-0 h-32 bg-gradient-to-b from-copper/10 via-gold/5 to-transparent pointer-events-none" />
|
|
118
|
-
|
|
119
|
-
{/* Header */}
|
|
120
|
-
<div className="relative flex items-center justify-between p-4 border-b border-copper/10">
|
|
121
|
-
<div className="flex items-center gap-3">
|
|
122
|
-
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-copper via-copper-dark to-gold flex items-center justify-center shadow-lg shadow-copper/30 rotate-3 hover:rotate-0 transition-transform">
|
|
123
|
-
<span className="text-lg">👨🍳</span>
|
|
124
|
-
</div>
|
|
125
|
-
<div>
|
|
126
|
-
<h3 className="font-display font-bold text-cream text-base tracking-tight">
|
|
127
|
-
Remy
|
|
128
|
-
</h3>
|
|
129
|
-
{contextTitle && (
|
|
130
|
-
<p className="text-xs text-copper/70 truncate max-w-[220px]">
|
|
131
|
-
🥐 {contextTitle}
|
|
132
|
-
</p>
|
|
133
|
-
)}
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
136
|
-
<button
|
|
137
|
-
onClick={handleToggle}
|
|
138
|
-
className="text-cream/50 hover:text-cream transition-colors p-2 hover:bg-white/5 rounded-xl"
|
|
139
|
-
>
|
|
140
|
-
<X className="w-5 h-5" />
|
|
141
|
-
</button>
|
|
142
|
-
</div>
|
|
143
|
-
|
|
144
|
-
{/* Messages */}
|
|
145
|
-
<Messages messages={messages} />
|
|
146
|
-
|
|
147
|
-
{/* Loading indicator */}
|
|
148
|
-
{isLoading && (
|
|
149
|
-
<div className="px-4 py-3 border-t border-copper/10">
|
|
150
|
-
<div className="flex items-center gap-2 text-copper/80 text-xs">
|
|
151
|
-
<div className="flex gap-1">
|
|
152
|
-
<span className="w-2 h-2 bg-copper rounded-full animate-bounce [animation-delay:-0.3s]"></span>
|
|
153
|
-
<span className="w-2 h-2 bg-gold rounded-full animate-bounce [animation-delay:-0.15s]"></span>
|
|
154
|
-
<span className="w-2 h-2 bg-copper-light rounded-full animate-bounce"></span>
|
|
155
|
-
</div>
|
|
156
|
-
<span className="font-medium">Crafting a response...</span>
|
|
157
|
-
</div>
|
|
158
|
-
</div>
|
|
159
|
-
)}
|
|
160
|
-
|
|
161
|
-
{/* Input */}
|
|
162
|
-
<div className="relative p-4 border-t border-copper/10 bg-charcoal/50">
|
|
163
|
-
<form
|
|
164
|
-
onSubmit={(e) => {
|
|
165
|
-
e.preventDefault()
|
|
166
|
-
handleSend()
|
|
167
|
-
}}
|
|
168
|
-
>
|
|
169
|
-
<div className="relative">
|
|
170
|
-
<textarea
|
|
171
|
-
value={input}
|
|
172
|
-
onChange={(e) => setInput(e.target.value)}
|
|
173
|
-
placeholder="Ask about speakers, sessions, techniques..."
|
|
174
|
-
disabled={isLoading}
|
|
175
|
-
className="w-full rounded-xl border border-copper/20 bg-charcoal-light/50 pl-4 pr-12 py-3 text-sm text-cream placeholder-cream/30 focus:outline-none focus:ring-2 focus:ring-copper/40 focus:border-transparent resize-none overflow-hidden disabled:opacity-50 transition-all"
|
|
176
|
-
rows={1}
|
|
177
|
-
style={{ minHeight: '48px', maxHeight: '100px' }}
|
|
178
|
-
onInput={(e) => {
|
|
179
|
-
const target = e.target as HTMLTextAreaElement
|
|
180
|
-
target.style.height = 'auto'
|
|
181
|
-
target.style.height = Math.min(target.scrollHeight, 100) + 'px'
|
|
182
|
-
}}
|
|
183
|
-
onKeyDown={(e) => {
|
|
184
|
-
if (
|
|
185
|
-
e.key === 'Enter' &&
|
|
186
|
-
!e.shiftKey &&
|
|
187
|
-
input.trim() &&
|
|
188
|
-
!isLoading
|
|
189
|
-
) {
|
|
190
|
-
e.preventDefault()
|
|
191
|
-
handleSend()
|
|
192
|
-
}
|
|
193
|
-
}}
|
|
194
|
-
/>
|
|
195
|
-
<button
|
|
196
|
-
type="submit"
|
|
197
|
-
disabled={!input.trim() || isLoading}
|
|
198
|
-
className="absolute right-3 top-1/2 -translate-y-1/2 p-2 rounded-lg bg-gradient-to-r from-copper to-copper-dark text-charcoal disabled:opacity-30 disabled:bg-gray-600 disabled:from-gray-600 disabled:to-gray-600 transition-all hover:shadow-lg hover:shadow-copper/20"
|
|
199
|
-
>
|
|
200
|
-
<Send className="w-4 h-4" />
|
|
201
|
-
</button>
|
|
202
|
-
</div>
|
|
203
|
-
</form>
|
|
204
|
-
</div>
|
|
205
|
-
</div>
|
|
206
|
-
)
|
|
207
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { Link } from '@tanstack/react-router'
|
|
2
|
-
import { MapPin } from 'lucide-react'
|
|
3
|
-
|
|
4
|
-
import { type Speaker } from 'content-collections'
|
|
5
|
-
|
|
6
|
-
import { Card, CardContent } from '@/components/ui/card'
|
|
7
|
-
|
|
8
|
-
interface SpeakerCardProps {
|
|
9
|
-
speaker: Speaker
|
|
10
|
-
featured?: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export default function SpeakerCard({ speaker, featured = false }: SpeakerCardProps) {
|
|
14
|
-
return (
|
|
15
|
-
<Link
|
|
16
|
-
to={`/speakers/${speaker.slug}`}
|
|
17
|
-
className="group relative block"
|
|
18
|
-
>
|
|
19
|
-
<Card
|
|
20
|
-
className={`relative overflow-hidden bg-card border-border/50 card-hover
|
|
21
|
-
${featured ? 'aspect-square' : 'aspect-square'}
|
|
22
|
-
hover:border-copper/50`}
|
|
23
|
-
>
|
|
24
|
-
{/* Headshot */}
|
|
25
|
-
<div className="absolute inset-0">
|
|
26
|
-
<img
|
|
27
|
-
src={`/${speaker.headshot}`}
|
|
28
|
-
alt={speaker.name}
|
|
29
|
-
className="w-full h-full object-cover"
|
|
30
|
-
/>
|
|
31
|
-
<div className="absolute inset-0 bg-gradient-to-t from-charcoal via-charcoal/50 to-transparent" />
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
{/* Content overlay */}
|
|
35
|
-
<CardContent className="absolute bottom-0 left-0 right-0 p-6 z-10">
|
|
36
|
-
<div className="space-y-2">
|
|
37
|
-
{/* Specialty tag */}
|
|
38
|
-
<span className="inline-block px-3 py-1 text-xs font-medium tracking-wider uppercase bg-copper/20 text-copper-light rounded-full border border-copper/30">
|
|
39
|
-
{speaker.specialty}
|
|
40
|
-
</span>
|
|
41
|
-
|
|
42
|
-
{/* Name */}
|
|
43
|
-
<h3 className="font-display text-2xl font-semibold text-cream group-hover:text-gold transition-colors">
|
|
44
|
-
{speaker.name}
|
|
45
|
-
</h3>
|
|
46
|
-
|
|
47
|
-
{/* Title & Restaurant */}
|
|
48
|
-
<p className="text-cream/70 font-body text-lg">
|
|
49
|
-
{speaker.title}
|
|
50
|
-
</p>
|
|
51
|
-
|
|
52
|
-
{/* Location */}
|
|
53
|
-
<div className="flex items-center gap-2 text-cream/50 text-sm">
|
|
54
|
-
<MapPin className="w-3.5 h-3.5" />
|
|
55
|
-
<span>{speaker.restaurant}, {speaker.location}</span>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
</CardContent>
|
|
59
|
-
|
|
60
|
-
{/* Decorative corner accent */}
|
|
61
|
-
<div className="absolute top-0 right-0 w-20 h-20 overflow-hidden">
|
|
62
|
-
<div className="absolute top-0 right-0 w-28 h-28 bg-gradient-to-bl from-copper/20 to-transparent transform rotate-45 translate-x-14 -translate-y-14" />
|
|
63
|
-
</div>
|
|
64
|
-
</Card>
|
|
65
|
-
</Link>
|
|
66
|
-
)
|
|
67
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { Link } from '@tanstack/react-router'
|
|
2
|
-
import { Clock, User } from 'lucide-react'
|
|
3
|
-
|
|
4
|
-
import { type Talk } from 'content-collections'
|
|
5
|
-
|
|
6
|
-
import { Card, CardContent } from '@/components/ui/card'
|
|
7
|
-
|
|
8
|
-
interface TalkCardProps {
|
|
9
|
-
talk: Talk
|
|
10
|
-
featured?: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export default function TalkCard({ talk, featured = false }: TalkCardProps) {
|
|
14
|
-
return (
|
|
15
|
-
<Link
|
|
16
|
-
to={`/talks/${talk.slug}`}
|
|
17
|
-
className="group relative block"
|
|
18
|
-
>
|
|
19
|
-
<Card
|
|
20
|
-
className={`relative overflow-hidden bg-card border-border/50 card-hover
|
|
21
|
-
${featured ? 'aspect-[16/10]' : 'aspect-[16/9]'}
|
|
22
|
-
hover:border-gold/50`}
|
|
23
|
-
>
|
|
24
|
-
{/* Image */}
|
|
25
|
-
<div className="absolute inset-0">
|
|
26
|
-
<img
|
|
27
|
-
src={`/${talk.image}`}
|
|
28
|
-
alt={talk.title}
|
|
29
|
-
className="w-full h-full object-cover"
|
|
30
|
-
/>
|
|
31
|
-
<div className="absolute inset-0 bg-gradient-to-t from-charcoal via-charcoal/60 to-transparent" />
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
{/* Content overlay */}
|
|
35
|
-
<CardContent className="absolute bottom-0 left-0 right-0 p-6 z-10">
|
|
36
|
-
<div className="space-y-3">
|
|
37
|
-
{/* Topics */}
|
|
38
|
-
<div className="flex flex-wrap gap-2">
|
|
39
|
-
{talk.topics.slice(0, 2).map((topic) => (
|
|
40
|
-
<span
|
|
41
|
-
key={topic}
|
|
42
|
-
className="px-2.5 py-0.5 text-xs font-medium tracking-wide uppercase bg-gold/15 text-gold border border-gold/30 rounded-full"
|
|
43
|
-
>
|
|
44
|
-
{topic}
|
|
45
|
-
</span>
|
|
46
|
-
))}
|
|
47
|
-
</div>
|
|
48
|
-
|
|
49
|
-
{/* Title */}
|
|
50
|
-
<h3 className="font-display text-xl font-semibold text-cream group-hover:text-gold transition-colors leading-tight">
|
|
51
|
-
{talk.title}
|
|
52
|
-
</h3>
|
|
53
|
-
|
|
54
|
-
{/* Speaker & Duration */}
|
|
55
|
-
<div className="flex items-center gap-4 text-cream/60 text-sm">
|
|
56
|
-
<div className="flex items-center gap-1.5">
|
|
57
|
-
<User className="w-3.5 h-3.5" />
|
|
58
|
-
<span>{talk.speaker}</span>
|
|
59
|
-
</div>
|
|
60
|
-
<div className="flex items-center gap-1.5">
|
|
61
|
-
<Clock className="w-3.5 h-3.5" />
|
|
62
|
-
<span>{talk.duration}</span>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
</CardContent>
|
|
67
|
-
|
|
68
|
-
{/* Decorative accent */}
|
|
69
|
-
<div className="absolute top-4 right-4">
|
|
70
|
-
<div className="w-8 h-8 rounded-full bg-gold/10 border border-gold/20 flex items-center justify-center">
|
|
71
|
-
<span className="text-gold/60 text-xs font-display">✦</span>
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
</Card>
|
|
75
|
-
</Link>
|
|
76
|
-
)
|
|
77
|
-
}
|
|
@@ -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,26 +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/remy-chat'),
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
export type ConferenceChatMessages = InferChatMessages<typeof defaultChatOptions>
|
|
14
|
-
|
|
15
|
-
export const useConferenceChat = (speakerSlug?: string, talkSlug?: string) => {
|
|
16
|
-
const chatOptions = createChatClientOptions({
|
|
17
|
-
connection: fetchServerSentEvents('/api/remy-chat', {
|
|
18
|
-
body: {
|
|
19
|
-
speakerSlug,
|
|
20
|
-
talkSlug,
|
|
21
|
-
},
|
|
22
|
-
}),
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
return useChat(chatOptions)
|
|
26
|
-
}
|