2026-05-10 13:12:44 +08:00

143 lines
4.9 KiB
TypeScript
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 { useState, useRef, useEffect, type FormEvent } from 'react';
import { useNavigate } from 'react-router-dom';
import { Send, Settings, LogOut, PenLine, BarChart3, Megaphone, MessageCircle } from 'lucide-react';
import { useAuth } from '../hooks/useAuth';
import { useSocket } from '../hooks/useSocket';
import AICompanion from '../components/AICompanion';
import ChatBubble from '../components/ChatBubble';
export default function DashboardPage() {
const navigate = useNavigate();
const { user, token, isAuthenticated, logout } = useAuth();
const { messages, sendMessage, connected } = useSocket(token);
const [input, setInput] = useState('');
const chatEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!isAuthenticated) {
navigate('/login');
}
}, [isAuthenticated, navigate]);
useEffect(() => {
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const handleSend = (e: FormEvent) => {
e.preventDefault();
if (!input.trim()) return;
sendMessage(input.trim());
setInput('');
};
const handleLogout = () => {
logout();
navigate('/');
};
if (!user) return null;
const companionName = user.aiCompanion?.name || 'AI伙伴';
const roleLabel =
user.role === 'author' ? '作者' : user.role === 'editor' ? '编辑' : '运营';
return (
<div className="min-h-screen pt-16 flex flex-col bg-ink-50">
{/* Header */}
<header className="fixed top-0 left-0 right-0 z-50 bg-white/90 backdrop-blur-md border-b border-ink-100">
<div className="max-w-4xl mx-auto px-4 h-16 flex items-center justify-between">
<div className="flex items-center gap-2 text-lg font-bold text-brand-700">
<span className="text-2xl">🌟</span>
<span></span>
</div>
<div className="flex items-center gap-3">
<span className="text-sm text-ink-600">
{user.nickname}{roleLabel}
</span>
<button
onClick={() => {}}
className="p-2 text-ink-400 hover:text-ink-600 transition-colors"
title="设置"
>
<Settings size={18} />
</button>
<button
onClick={handleLogout}
className="p-2 text-ink-400 hover:text-red-500 transition-colors"
title="退出"
>
<LogOut size={18} />
</button>
</div>
</div>
</header>
{/* Main chat area */}
<main className="flex-1 flex flex-col max-w-4xl w-full mx-auto px-4 pb-4">
{/* AI Companion avatar */}
<div className="flex flex-col items-center py-6">
<AICompanion name={companionName} size="md" />
{connected ? (
<span className="text-xs text-emerald-500 mt-2"> </span>
) : (
<span className="text-xs text-ink-400 mt-2"> ...</span>
)}
</div>
{/* Chat messages */}
<div className="flex-1 overflow-y-auto space-y-4 mb-4 min-h-[300px]">
{messages.map((msg, idx) => (
<ChatBubble
key={idx}
role={msg.role}
content={msg.content}
companionName={companionName}
userNickname={user.nickname}
/>
))}
<div ref={chatEndRef} />
</div>
{/* Input area */}
<form
onSubmit={handleSend}
className="flex gap-2 bg-white rounded-2xl border border-ink-200 p-2 shadow-soft"
>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder={`${companionName}说点什么...`}
className="flex-1 px-4 py-2.5 outline-none text-ink-800 text-sm bg-transparent"
/>
<button
type="submit"
disabled={!input.trim() || !connected}
className="px-4 py-2.5 bg-brand-600 text-white hover:bg-brand-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-xl transition-colors"
>
<Send size={18} />
</button>
</form>
{/* Quick actions */}
<div className="mt-4 flex flex-wrap gap-2 justify-center">
{[
{ icon: PenLine, label: '开始写作' },
{ icon: BarChart3, label: '查看数据' },
{ icon: Megaphone, label: '看定制书需求' },
{ icon: MessageCircle, label: '讨论区' },
].map((action) => (
<button
key={action.label}
className="flex items-center gap-1.5 px-4 py-2 text-sm text-ink-600 bg-white border border-ink-200 hover:border-brand-300 hover:text-brand-600 rounded-full transition-all"
>
<action.icon size={14} />
{action.label}
</button>
))}
</div>
</main>
</div>
);
}