Files
chatbot/src/App.js

172 lines
4.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useRef, useState } from "react";
import {
Avatar,
Button,
Card,
CardBody,
CardHeader,
Divider,
ScrollShadow,
Spinner,
Textarea,
} from "@heroui/react";
import { requestBotReply } from "./dsAPI";
export default function App() {
const [messages, setMessages] = useState([
{
role: "assistant",
content: "你好,有什么可以帮你?",
},
]);
const [inputText, setInputText] = useState("");
const [isSending, setIsSending] = useState(false);
const [isBotTyping, setIsBotTyping] = useState(false);
const bottomRef = useRef(null);
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages, isBotTyping]);
async function handleSend() {
const text = inputText.trim();
if (!text || isSending) return;
const userMessage = {
role: "user",
content: text,
};
const nextMessages = [...messages, userMessage];
setMessages(nextMessages);
setInputText("");
setIsSending(true);
setIsBotTyping(true);
// 给模型的上下文
const historyForModel = [
{ role: "system", content: "你是一个简洁的助手,用中文回答。" },
...nextMessages.map(({ role, content }) => ({ role, content })),
];
let reply = "";
try {
reply = await requestBotReply(historyForModel);
} catch (e) {
reply = "出错了,请稍后再试。";
}
const botMessage = {
role: "assistant",
content: reply,
};
setMessages((prev) => [...prev, botMessage]);
setIsBotTyping(false);
setIsSending(false);
}
function handleKeyDown(e) {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSend();
}
}
return (
<div className="min-h-screen bg-gradient-to-b from-background to-content2/50 p-4">
<div className="mx-auto max-w-3xl">
<Card className="shadow-xl">
{/* 顶部 */}
<CardHeader className="flex items-center gap-3">
<Avatar name="Bot" />
<div className="flex flex-col">
<span className="font-semibold">Chatbot Demo</span>
<span className="text-xs text-default-500">
</span>
</div>
</CardHeader>
<Divider />
{/* 聊天区域 */}
<CardBody className="p-0">
<ScrollShadow className="h-[520px] px-4 py-4">
<div className="flex flex-col gap-3">
{messages.map((m) => {
const isUser = m.role === "user";
return (
<div
className={`flex ${isUser ? "justify-end" : "justify-start"}`}
>
<div
className={`flex max-w-[80%] items-end gap-2 ${isUser ? "flex-row-reverse" : ""}`}
>
<Avatar
size="sm"
name={isUser ? "You" : "Bot"}
/>
<div
className={`rounded-2xl px-4 py-3 text-sm leading-relaxed ${isUser
? "bg-primary text-primary-foreground"
: "bg-content2 text-foreground"
}`}
>
{m.content}
</div>
</div>
</div>
);
})}
{isBotTyping && (
<div className="flex justify-start">
<div className="flex items-center gap-2">
<Avatar size="sm" name="Bot" />
<div className="flex items-center gap-2 rounded-2xl bg-content2 px-4 py-2 text-sm text-default-600">
<Spinner size="sm" />
正在回复
</div>
</div>
</div>
)}
<div ref={bottomRef} />
</div>
</ScrollShadow>
<Divider />
{/* 输入区 */}
<div className="p-4">
<div className="flex items-end gap-3">
<Textarea
value={inputText}
onValueChange={setInputText}
onKeyDown={handleKeyDown}
placeholder="输入消息Enter 发送Shift+Enter 换行"
minRows={2}
maxRows={6}
isDisabled={isSending}
variant="bordered"
className="flex-1"
/>
<Button
color="primary"
onPress={handleSend}
isDisabled={isSending || !inputText.trim()}
>
{isSending ? "发送中…" : "发送"}
</Button>
</div>
</div>
</CardBody>
</Card>
</div>
</div>
);
}