chatgpt2api / web /src /app /debug /components /chat-panel.tsx
huanx's picture
Deploy chatgpt2api to Hugging Face Space
bf53440 verified
Raw
History Blame Contribute Delete
4.58 kB
"use client";
import { useState } from "react";
import { LoaderCircle, Send } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { httpRequest } from "@/lib/request";
import { pretty, type ChatCompletionResponse, type ChatMessage } from "./types";
export function ChatPanel() {
const [model, setModel] = useState("auto");
const [input, setInput] = useState("你好,先记住我的项目叫 chatgpt2api。");
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [raw, setRaw] = useState<ChatCompletionResponse | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const sendChat = async () => {
const content = input.trim();
if (!content) return;
const nextMessages: ChatMessage[] = [...messages, { role: "user", content }];
setMessages(nextMessages);
setInput("");
setLoading(true);
setError("");
try {
const result = await httpRequest<ChatCompletionResponse>("/v1/chat/completions", { method: "POST", body: { model: model.trim() || "auto", messages: nextMessages } });
setRaw(result);
setMessages([...nextMessages, { role: "assistant", content: String(result.choices?.[0]?.message?.content || "") }]);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
}
};
const clearChat = () => {
setMessages([]);
setRaw(null);
setError("");
};
return (
<div className="grid h-full min-h-0 gap-8 lg:grid-cols-[360px_minmax(0,1fr)]">
<section className="flex min-h-0 flex-col lg:border-r lg:border-stone-200/70 lg:pr-8 dark:lg:border-white/10">
<div className="border-b border-stone-200/70 pb-3 dark:border-white/10">
<h2 className="text-sm font-medium text-stone-500 dark:text-stone-400">请求</h2>
</div>
<div className="min-h-0 flex-1 space-y-4 overflow-auto pt-4">
<div className="space-y-2">
<Label htmlFor="chat-model">Model</Label>
<Input id="chat-model" value={model} onChange={(event) => setModel(event.target.value)} className="rounded-md border-stone-200/70 bg-transparent shadow-none dark:border-white/10" />
</div>
<div className="space-y-2">
<Label htmlFor="chat-input">Message</Label>
<Textarea id="chat-input" value={input} onChange={(event) => setInput(event.target.value)} className="min-h-32 rounded-md border-stone-200/70 bg-transparent shadow-none dark:border-white/10" />
</div>
<div className="flex gap-2">
<Button size="sm" onClick={() => void sendChat()} disabled={loading || !input.trim()}>
{loading ? <LoaderCircle className="animate-spin" /> : <Send />}
发送
</Button>
<Button size="sm" variant="outline" onClick={clearChat}>
清空
</Button>
</div>
{error ? <div className="rounded-md border border-rose-200 bg-rose-50/60 px-3 py-2 text-sm text-rose-700 dark:border-rose-900/60 dark:bg-rose-950/20 dark:text-rose-300">{error}</div> : null}
<Textarea value={raw ? pretty(raw) : "{\n \"messages\": []\n}"} readOnly className="min-h-72 resize-none rounded-md border-stone-200/70 bg-stone-50/50 p-4 font-mono text-xs leading-5 text-stone-600 shadow-none dark:border-white/10 dark:bg-white/[0.03] dark:text-stone-300" />
</div>
</section>
<section className="flex min-h-0 flex-col">
<div className="border-b border-stone-200/70 pb-3 dark:border-white/10">
<h2 className="text-sm font-medium text-stone-500 dark:text-stone-400">对话</h2>
</div>
<div className="min-h-0 flex-1 space-y-4 overflow-auto pt-4">
{messages.length ? messages.map((message, index) => (
<div key={`${message.role}-${index}`} className="space-y-1.5 text-sm">
<div className="text-xs font-medium uppercase tracking-wide text-stone-400 dark:text-stone-500">{message.role}</div>
<div className="whitespace-pre-wrap leading-7 text-stone-700 dark:text-stone-300">{message.content}</div>
</div>
)) : (
<div className="flex h-full items-center justify-center text-sm text-stone-400 dark:text-stone-500">暂无对话消息</div>
)}
</div>
</section>
</div>
);
}