playgo_next / app /components /LandingPageChatBot.tsx
ChenyuRabbitLove's picture
feat: add more examples for landing page chatbot
09ee276
raw
history blame
9.89 kB
'use client'
import { useState, useRef, useEffect } from 'react'
import { useChat } from 'ai/react'
import {
AcademicCapIcon,
ClockIcon,
BookOpenIcon,
LightBulbIcon,
BriefcaseIcon,
} from '@heroicons/react/24/outline'
type MessageWithLoading = {
content: string;
role: string;
isStreaming?: boolean;
}
const EXAMPLE_PROMPTS = [
{
title: "教案規劃",
prompt: "我想規劃一堂關於數學的課程",
icon: AcademicCapIcon
},
{
title: "英語對話",
prompt: "我想練習英語對話",
icon: ClockIcon
},
{
title: "作業批改",
prompt: "我想批改學生的作文作業",
icon: BookOpenIcon
},
{
title: "數學解題",
prompt: "我想解數學題目",
icon: LightBulbIcon
},
{
title: "我想學程式",
prompt: "我想學習如何寫程式",
icon: BriefcaseIcon
}
]
export default function LandingPageChatBot() {
const { messages: rawMessages, input, handleInputChange, handleSubmit, isLoading, append } = useChat({
api: '/api/landing_page_chat',
streamProtocol: 'data',
onError: (error) => {
console.error('Chat error:', error);
},
onFinish: (message) => {
console.log('Chat finished:', message);
setMessages(prev => prev.map(msg => ({...msg, isStreaming: false})))
},
keepLastMessageOnError: true,
})
const chatContainerRef = useRef<HTMLDivElement>(null)
const [hasInteracted, setHasInteracted] = useState(false)
const [messages, setMessages] = useState<MessageWithLoading[]>([])
useEffect(() => {
setMessages(rawMessages.map((msg, index) => ({
...msg,
isStreaming: isLoading && index === rawMessages.length - 1 && msg.role === 'assistant'
})))
}, [rawMessages, isLoading])
const scrollToBottom = () => {
if (chatContainerRef.current) {
const { scrollHeight, clientHeight } = chatContainerRef.current
chatContainerRef.current.scrollTop = scrollHeight - clientHeight
}
}
useEffect(() => {
scrollToBottom()
}, [messages])
const sendMessage = async (text: string) => {
setHasInteracted(true)
await append({
content: text,
role: 'user',
})
}
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
setHasInteracted(true)
handleSubmit(e)
}
return (
<section className="w-full h-[1000px] bg-gradient-to-b from-background-secondary to-background-primary flex items-center justify-center px-4">
<div className="w-full max-w-5xl mx-auto h-full flex items-center">
{!hasInteracted && messages.length === 0 ? (
<div className="text-center space-y-8 w-full">
<div className="space-y-4">
<h2 className="text-5xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
今天你想學什麼?
</h2>
<p className="text-text-secondary text-xl max-w-2xl mx-auto leading-relaxed">
探索新主題、獲得教學協助,或發現學習資源
</p>
</div>
<form onSubmit={onSubmit} className="max-w-3xl mx-auto">
<div className="relative flex items-center">
<input
type="text"
value={input}
onChange={handleInputChange}
placeholder="請問任何關於學習的問題..."
className="w-full p-6 pr-36 rounded-2xl border border-border/50 bg-background-primary/50
backdrop-blur-sm shadow-lg focus:outline-none focus:ring-2 focus:ring-primary/50
text-lg transition-all duration-200 placeholder:text-text-secondary/50"
/>
<button
type="submit"
disabled={isLoading}
className="absolute right-2 p-3 bg-primary text-white rounded-full
hover:bg-primary/90 transition-all duration-200
shadow-md hover:shadow-lg active:scale-95 disabled:opacity-50
disabled:hover:bg-primary disabled:cursor-not-allowed"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className={`size-6 ${isLoading ? 'animate-pulse' : ''}`}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5"
/>
</svg>
</button>
</div>
</form>
<div className="mt-12">
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
{EXAMPLE_PROMPTS.map((prompt, index) => (
<button
key={index}
onClick={() => sendMessage(prompt.prompt)}
className="group p-4 rounded-2xl border border-border/50 bg-background-primary/30
hover:bg-background-primary/50 backdrop-blur-sm transition-all duration-200
hover:border-primary/50 hover:shadow-lg text-left w-full"
>
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-3">
<prompt.icon className="w-8 h-8 text-primary" />
<h4 className="font-semibold text-text-primary text-md">
{prompt.title}
</h4>
</div>
<svg
className="w-6 h-6 text-primary opacity-0 group-hover:opacity-100 transition-opacity duration-200"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
</svg>
</div>
</button>
))}
</div>
</div>
</div>
) : (
<div className="bg-background-primary/50 backdrop-blur-sm rounded-2xl shadow-lg border border-border/50 overflow-hidden w-full h-[700px] flex flex-col">
<div className="p-6 border-b border-border/50 bg-background-secondary/30">
<h2 className="text-xl font-semibold text-text-primary">PlayGO 導覽員</h2>
</div>
<div
ref={chatContainerRef}
className="flex-1 overflow-y-auto p-6 space-y-6"
>
{messages.map((message, index) => (
<div
key={index}
className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-[80%] p-4 rounded-2xl shadow-sm ${
message.role === 'user'
? 'bg-primary text-white rounded-br-none'
: 'bg-background-secondary/50 text-text-primary rounded-bl-none'
}`}
>
{message.content}
{message.isStreaming && (
<span className="inline-block w-2.5 h-2.5 ml-1.5 bg-primary rounded-full animate-pulse" />
)}
</div>
</div>
))}
</div>
<div className="p-6 border-t border-border/50 bg-background-secondary/30">
<form onSubmit={onSubmit}>
<div className="relative flex items-center">
<input
type="text"
value={input}
onChange={handleInputChange}
placeholder={isLoading ? "AI is thinking..." : "Type your message..."}
disabled={isLoading}
className="w-full p-4 pr-32 rounded-xl border border-border/50 bg-background-primary/50
backdrop-blur-sm focus:outline-none focus:ring-2 focus:ring-primary/50
text-base transition-all duration-200 disabled:opacity-50"
/>
<button
type="submit"
disabled={isLoading}
className="absolute right-2 p-2 bg-primary text-white rounded-full
hover:bg-primary/90 transition-all duration-200
shadow-md hover:shadow-lg active:scale-95 disabled:opacity-50
disabled:hover:bg-primary disabled:cursor-not-allowed"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className={`size-5 ${isLoading ? 'animate-pulse' : ''}`}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5"
/>
</svg>
</button>
</div>
</form>
</div>
</div>
)}
</div>
</section>
)
}