diff --git a/frontend/public/env-config.js b/frontend/public/env-config.js
new file mode 100644
index 0000000..3ec3798
--- /dev/null
+++ b/frontend/public/env-config.js
@@ -0,0 +1,3 @@
+// Runtime config injection — generated at container startup
+// Uncomment and set the value for production deployment:
+// window.__ENV = { NEXT_PUBLIC_API_BASE_URL: "http://your-backend:8091/api/v1" };
diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css
new file mode 100644
index 0000000..7f146c3
--- /dev/null
+++ b/frontend/src/app/globals.css
@@ -0,0 +1,31 @@
+@import "tailwindcss";
+
+:root {
+ --background: #ffffff;
+ --foreground: #171717;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --background: #0a0a0a;
+ --foreground: #ededed;
+ }
+}
+
+body {
+ background: var(--background);
+ color: var(--foreground);
+ font-family: Arial, Helvetica, sans-serif;
+}
+
+/* 自定义滚动条 */
+::-webkit-scrollbar {
+ width: 6px;
+}
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+::-webkit-scrollbar-thumb {
+ background: #94a3b8;
+ border-radius: 3px;
+}
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
new file mode 100644
index 0000000..1b57ac8
--- /dev/null
+++ b/frontend/src/app/layout.tsx
@@ -0,0 +1,19 @@
+import type { Metadata } from "next";
+import "./globals.css";
+
+export const metadata: Metadata = {
+ title: "AI Agent Chat",
+ description: "AI 智能体对话平台",
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
{children}
+
+ );
+}
diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx
new file mode 100644
index 0000000..5ea5886
--- /dev/null
+++ b/frontend/src/app/login/page.tsx
@@ -0,0 +1,119 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import { useRouter } from 'next/navigation';
+import { setUserInfo, getUserInfo, clearUserInfo } from '@/utils/cookie';
+
+export default function Login() {
+ const router = useRouter();
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+ const [msg, setMsg] = useState({ text: '', type: '' });
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
+ const [currentUser, setCurrentUser] = useState('');
+
+ useEffect(() => {
+ const info = getUserInfo();
+ if (info?.user) {
+ setIsLoggedIn(true);
+ setCurrentUser(info.user);
+ router.push('/');
+ }
+ }, [router]);
+
+ const handleLogin = (e: React.FormEvent) => {
+ e.preventDefault();
+ setMsg({ text: '', type: '' });
+ if (!username || !password) {
+ setMsg({ text: '请输入账号与密码。', type: 'error' });
+ return;
+ }
+ if (username !== 'admin' || password !== 'admin') {
+ setMsg({ text: '账号或密码错误(演示:admin / admin)。', type: 'error' });
+ return;
+ }
+ setUserInfo(username);
+ setMsg({ text: '登录成功,正在跳转…', type: 'info' });
+ setTimeout(() => router.push('/'), 500);
+ };
+
+ const handleLogout = () => {
+ clearUserInfo();
+ setIsLoggedIn(false);
+ setCurrentUser('');
+ setMsg({ text: '已退出登录。', type: 'info' });
+ };
+
+ return (
+
+
+
+
AI 智能体工作台
+
登录后开始与 AI 对话
+
+
+ {isLoggedIn ? (
+
+
+ 当前用户:{currentUser}
+
+
+
+ ) : (
+
+ )}
+
+ {msg.text && (
+
+ {msg.text}
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx
new file mode 100644
index 0000000..ba6a0e5
--- /dev/null
+++ b/frontend/src/app/page.tsx
@@ -0,0 +1,367 @@
+'use client';
+
+import { useState, useEffect, useRef } from 'react';
+import { useRouter } from 'next/navigation';
+import { getUserInfo, clearUserInfo } from '@/utils/cookie';
+import { agentApi } from '@/api/agent';
+import { AiAgentConfigResponseDTO } from '@/types/api';
+
+interface Message {
+ id: string;
+ role: 'user' | 'agent';
+ content: string;
+ timestamp: number;
+}
+
+interface Session {
+ id: string;
+ backendSessionId?: string;
+ title: string;
+ messages: Message[];
+ lastModified: number;
+}
+
+const STORAGE_KEY = 'ai_agent_sessions';
+
+function loadSessions(): Session[] {
+ if (typeof window === 'undefined') return [];
+ try {
+ const raw = localStorage.getItem(STORAGE_KEY);
+ return raw ? JSON.parse(raw) : [];
+ } catch {
+ return [];
+ }
+}
+
+function saveSessions(sessions: Session[]) {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(sessions));
+}
+
+export default function Home() {
+ const router = useRouter();
+ const messagesEndRef = useRef(null);
+
+ // 用户状态
+ const [currentUser, setCurrentUser] = useState('');
+
+ // Agent 状态
+ const [agents, setAgents] = useState([]);
+ const [selectedAgentId, setSelectedAgentId] = useState('');
+
+ // 会话状态
+ const [sessions, setSessions] = useState([]);
+ const [currentSessionId, setCurrentSessionId] = useState('');
+
+ // 聊天状态
+ const [messages, setMessages] = useState([]);
+ const [inputValue, setInputValue] = useState('');
+ const [isSending, setIsSending] = useState(false);
+
+ // 检查登录状态 & 加载数据
+ useEffect(() => {
+ const info = getUserInfo();
+ if (!info?.user) {
+ router.push('/login');
+ return;
+ }
+ setCurrentUser(info.user);
+
+ // 加载 Agent 列表
+ agentApi
+ .queryAgentList()
+ .then(res => {
+ setAgents(res.data);
+ // 恢复上次选择的 Agent
+ const last = localStorage.getItem('ai_agent_last_agent');
+ if (last && res.data.some(a => a.agentId === last)) {
+ setSelectedAgentId(last);
+ } else if (res.data.length > 0) {
+ setSelectedAgentId(res.data[0].agentId);
+ }
+ })
+ .catch(console.error);
+
+ // 加载本地会话
+ const saved = loadSessions();
+ setSessions(saved);
+ if (saved.length > 0) {
+ setCurrentSessionId(saved[0].id);
+ setMessages(saved[0].messages);
+ }
+ }, [router]);
+
+ // 自动滚动到最新消息
+ useEffect(() => {
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ }, [messages]);
+
+ // 会话变更时持久化
+ useEffect(() => {
+ if (sessions.length > 0) saveSessions(sessions);
+ }, [sessions]);
+
+ const currentSession = sessions.find(s => s.id === currentSessionId);
+
+ const createSession = () => {
+ const id = Date.now().toString();
+ const newSession: Session = {
+ id,
+ title: `对话 ${sessions.length + 1}`,
+ messages: [],
+ lastModified: Date.now(),
+ };
+ setSessions(prev => [newSession, ...prev]);
+ setCurrentSessionId(id);
+ setMessages([]);
+ };
+
+ const deleteSession = (id: string) => {
+ setSessions(prev => {
+ const next = prev.filter(s => s.id !== id);
+ if (currentSessionId === id) {
+ if (next.length > 0) {
+ setCurrentSessionId(next[0].id);
+ setMessages(next[0].messages);
+ } else {
+ setCurrentSessionId('');
+ setMessages([]);
+ }
+ }
+ return next;
+ });
+ };
+
+ const switchSession = (id: string) => {
+ setCurrentSessionId(id);
+ const s = sessions.find(s => s.id === id);
+ setMessages(s?.messages || []);
+ };
+
+ const updateSessionMessages = (sessionId: string, msgs: Message[]) => {
+ setSessions(prev =>
+ prev.map(s =>
+ s.id === sessionId
+ ? {
+ ...s,
+ messages: msgs,
+ lastModified: Date.now(),
+ title:
+ msgs.length === 1
+ ? msgs[0].content.slice(0, 20)
+ : s.title,
+ }
+ : s
+ )
+ );
+ };
+
+ const sendMessage = async () => {
+ if (!inputValue.trim() || !selectedAgentId || isSending) return;
+ if (!currentSessionId) createSession();
+
+ const userMsg: Message = {
+ id: Date.now().toString(),
+ role: 'user',
+ content: inputValue.trim(),
+ timestamp: Date.now(),
+ };
+
+ const newMessages = [...messages, userMsg];
+ setMessages(newMessages);
+ setInputValue('');
+ setIsSending(true);
+
+ // 记住上次选择的 Agent
+ localStorage.setItem('ai_agent_last_agent', selectedAgentId);
+
+ try {
+ // 获取或创建后端会话
+ let sessionId = currentSession?.backendSessionId;
+ if (!sessionId) {
+ const res = await agentApi.createSession(selectedAgentId, currentUser);
+ sessionId = res.data.sessionId;
+ setSessions(prev =>
+ prev.map(s =>
+ s.id === (currentSessionId || sessions[0]?.id)
+ ? { ...s, backendSessionId: sessionId }
+ : s
+ )
+ );
+ }
+
+ // 发送消息
+ const res = await agentApi.chat({
+ agentId: selectedAgentId,
+ userId: currentUser,
+ sessionId: sessionId!,
+ message: userMsg.content,
+ });
+
+ const agentMsg: Message = {
+ id: (Date.now() + 1).toString(),
+ role: 'agent',
+ content: res.data.content,
+ timestamp: Date.now(),
+ };
+
+ const updated = [...newMessages, agentMsg];
+ setMessages(updated);
+ updateSessionMessages(currentSessionId || sessions[0]?.id, updated);
+ } catch (err) {
+ const errMsg: Message = {
+ id: (Date.now() + 1).toString(),
+ role: 'agent',
+ content: `错误:${err instanceof Error ? err.message : '请求失败'}`,
+ timestamp: Date.now(),
+ };
+ const updated = [...newMessages, errMsg];
+ setMessages(updated);
+ } finally {
+ setIsSending(false);
+ }
+ };
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ sendMessage();
+ }
+ };
+
+ const handleLogout = () => {
+ clearUserInfo();
+ router.push('/login');
+ };
+
+ return (
+
+ {/* 左侧栏 — 会话列表 */}
+
+
+
+
+
+ {sessions.map(s => (
+
switchSession(s.id)}
+ className={`group flex items-center justify-between p-3 rounded-lg cursor-pointer transition ${
+ s.id === currentSessionId
+ ? 'bg-slate-700'
+ : 'hover:bg-slate-700/50'
+ }`}
+ >
+ {s.title}
+
+
+ ))}
+
+
+
+ {/* 主聊天区域 */}
+
+ {/* 顶部栏 */}
+
+
+ AI 智能体
+
+
+
+ {currentUser}
+
+
+
+
+ {/* 消息列表 */}
+
+ {messages.length === 0 && (
+
+ )}
+ {messages.map(msg => (
+
+
+
{msg.content}
+
+ {new Date(msg.timestamp).toLocaleTimeString()}
+
+
+
+ ))}
+ {isSending && (
+
+ )}
+
+
+
+ {/* 输入区域 */}
+
+
+
+ );
+}