大活
This commit is contained in:
152
art-agent/frontend/src/app/page.tsx
Normal file
152
art-agent/frontend/src/app/page.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { ChatMessages } from "@/components/chat/chat-messages";
|
||||
import { ChatInput } from "@/components/chat/chat-input";
|
||||
import { sendChat, type Message } from "@/lib/api";
|
||||
|
||||
export default function Home() {
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [streamingText, setStreamingText] = useState("");
|
||||
const [streamingImages, setStreamingImages] = useState<string[]>([]);
|
||||
const [statusText, setStatusText] = useState("");
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
setTimeout(() => {
|
||||
scrollRef.current?.scrollTo({
|
||||
top: scrollRef.current.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}, 50);
|
||||
};
|
||||
|
||||
const handleSend = useCallback(
|
||||
async (text: string, refImage: File | null) => {
|
||||
// 添加用户消息
|
||||
const userMessage: Message = { role: "user", content: text };
|
||||
const updatedMessages = [...messages, userMessage];
|
||||
setMessages(updatedMessages);
|
||||
setIsLoading(true);
|
||||
setStreamingText("");
|
||||
setStreamingImages([]);
|
||||
setStatusText("");
|
||||
scrollToBottom();
|
||||
|
||||
let assistantText = "";
|
||||
let collectedImages: string[] = [];
|
||||
|
||||
try {
|
||||
for await (const event of sendChat(updatedMessages, refImage)) {
|
||||
switch (event.type) {
|
||||
case "text_delta":
|
||||
assistantText += event.data.text as string;
|
||||
setStreamingText(assistantText);
|
||||
scrollToBottom();
|
||||
break;
|
||||
|
||||
case "tool_start":
|
||||
setStatusText(event.data.message as string);
|
||||
scrollToBottom();
|
||||
break;
|
||||
|
||||
case "image_result":
|
||||
collectedImages = [
|
||||
...collectedImages,
|
||||
...(event.data.images as string[]),
|
||||
];
|
||||
setStreamingImages(collectedImages);
|
||||
setStatusText("");
|
||||
scrollToBottom();
|
||||
break;
|
||||
|
||||
case "error":
|
||||
assistantText += `\n\n[错误: ${event.data.message}]`;
|
||||
setStreamingText(assistantText);
|
||||
break;
|
||||
|
||||
case "done":
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
assistantText += `\n\n[请求失败: ${e instanceof Error ? e.message : "未知错误"}]`;
|
||||
}
|
||||
|
||||
// 完成:将流式内容合并为正式消息
|
||||
const assistantMessage: Message = {
|
||||
role: "assistant",
|
||||
content: assistantText || "(生成完成)",
|
||||
images: collectedImages.length > 0 ? collectedImages : undefined,
|
||||
};
|
||||
setMessages([...updatedMessages, assistantMessage]);
|
||||
setIsLoading(false);
|
||||
setStreamingText("");
|
||||
setStreamingImages([]);
|
||||
setStatusText("");
|
||||
scrollToBottom();
|
||||
},
|
||||
[messages]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-screen flex flex-col">
|
||||
{/* 标题栏 */}
|
||||
<header className="flex-shrink-0 border-b border-[var(--border)] bg-[var(--bg-secondary)] px-6 py-3">
|
||||
<h1 className="text-lg font-semibold">Art Agent</h1>
|
||||
<p className="text-xs text-[var(--text-secondary)]">
|
||||
AI 美术资源生成助手 · MVP
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{/* 消息区域 */}
|
||||
<div ref={scrollRef} className="flex-1 overflow-y-auto">
|
||||
{messages.length === 0 && !isLoading ? (
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<div className="text-center space-y-3">
|
||||
<div className="text-4xl">🎨</div>
|
||||
<h2 className="text-xl font-medium text-[var(--text-primary)]">
|
||||
欢迎使用 Art Agent
|
||||
</h2>
|
||||
<p className="text-sm text-[var(--text-secondary)] max-w-md">
|
||||
描述你想要的美术资源,我来帮你生成。
|
||||
<br />
|
||||
你可以上传参考图来引导风格方向。
|
||||
</p>
|
||||
<div className="flex flex-wrap justify-center gap-2 mt-4">
|
||||
{[
|
||||
"画一个赛博朋克风格的退出按钮",
|
||||
"设计一个卡通风格的金币图标",
|
||||
"画一个奇幻风格的游戏角色立绘",
|
||||
].map((hint) => (
|
||||
<button
|
||||
key={hint}
|
||||
onClick={() => handleSend(hint, null)}
|
||||
className="text-xs px-3 py-1.5 rounded-full
|
||||
border border-[var(--border)] text-[var(--text-secondary)]
|
||||
hover:border-[var(--accent)] hover:text-[var(--accent)]
|
||||
transition-colors cursor-pointer"
|
||||
>
|
||||
{hint}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ChatMessages
|
||||
messages={messages}
|
||||
isLoading={isLoading}
|
||||
streamingText={streamingText}
|
||||
streamingImages={streamingImages}
|
||||
statusText={statusText}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 输入区域 */}
|
||||
<ChatInput onSend={handleSend} disabled={isLoading} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user