feat: 多功能升级 — 流式逐字渲染、对话缓存、会话组织优化、记忆管理修复、性能仪表盘

- 前端消息流式逐字渲染 (AI-Core ChatStream → SSE → Gateway → WebSocket stream_chunk → fadeInUp + cursorBlink)
- 后端对话缓存 (conversationCache sync.Map, GET /sessions/:id/messages)
- 前端侧边栏历史多轮对话显示
- DevTools 性能监控图标移至首页仪表盘
- DevTools 用户记忆查询/删减功能修复 (补全 DELETE 数据链路)
- 后端和 DevTools 按用户分类组织实时活动会话 (map[userID]map[sessionID]*Client)
- 新增 docs/api-reference/ 路由参考文档
- 新增 docs/message-flow-architecture.md 消息链路架构文档
This commit is contained in:
2026-05-16 17:44:03 +08:00
parent 63513210b7
commit 186513f381
24 changed files with 1024 additions and 216 deletions
+41 -16
View File
@@ -1,6 +1,5 @@
import { useSession } from '@/hooks/useSession';
import { useSessionStore } from '@/store/sessionStore';
import { useEffect } from 'react';
import { CyreneAvatar } from '@/components/persona/CyreneAvatar';
interface SidebarProps {
@@ -8,20 +7,51 @@ interface SidebarProps {
}
export function Sidebar({ onClose }: SidebarProps) {
const { sessions, currentSessionId, loadSessions, createSession, deleteSession, setCurrentSession } = useSession();
const {
sessions,
currentSessionId,
createSession,
deleteSession,
setCurrentSession,
} = useSession();
const storeSessions = useSessionStore((s) => s.sessions);
useEffect(() => {
loadSessions();
}, [loadSessions]);
const storeCurrentSessionId = useSessionStore((s) => s.currentSessionId);
const displaySessions = sessions.length > 0 ? sessions : storeSessions;
const activeSessionId = currentSessionId || storeCurrentSessionId;
const handleNewChat = async () => {
const session = await createSession();
if (session && onClose) onClose();
};
const handleSelectSession = (id: string) => {
setCurrentSession(id);
if (onClose) onClose();
};
const handleDeleteSession = (e: { stopPropagation: () => void }, id: string) => {
e.stopPropagation();
deleteSession(id);
};
/** 格式化时间戳为可读字符串 */
const formatTime = (ts: string | number): string => {
if (!ts) return '';
const date = new Date(typeof ts === 'string' ? parseInt(ts, 10) : ts);
if (isNaN(date.getTime())) return '';
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMin = Math.floor(diffMs / 60000);
if (diffMin < 1) return '刚刚';
if (diffMin < 60) return `${diffMin}分钟前`;
const diffHour = Math.floor(diffMin / 60);
if (diffHour < 24) return `${diffHour}小时前`;
const diffDay = Math.floor(diffHour / 24);
if (diffDay < 7) return `${diffDay}天前`;
return date.toLocaleDateString('zh-CN');
};
return (
<aside className="h-full bg-white/90 dark:bg-gray-900/90 border-r border-pink-100 dark:border-pink-900 flex flex-col">
{/* 侧边栏头部 */}
@@ -45,14 +75,11 @@ export function Sidebar({ onClose }: SidebarProps) {
displaySessions.map((session) => (
<div
key={session.id}
onClick={() => {
setCurrentSession(session.id);
if (onClose) onClose();
}}
onClick={() => handleSelectSession(session.id)}
className={`
group flex items-center justify-between px-4 py-2.5 mx-2 rounded-lg cursor-pointer transition-colors
${
currentSessionId === session.id || session.id === useSessionStore.getState().currentSessionId
activeSessionId === session.id
? 'bg-pink-50 dark:bg-pink-900/30 text-pink-600'
: 'hover:bg-gray-50 dark:hover:bg-gray-800 text-gray-600 dark:text-gray-300'
}
@@ -63,15 +90,13 @@ export function Sidebar({ onClose }: SidebarProps) {
<div className="min-w-0">
<p className="text-sm font-medium truncate">{session.title || '新的对话'}</p>
<p className="text-xs text-gray-400 truncate">
{session.message_count || 0}
{session.message_count != null ? `${session.message_count} 条消息` : ''}
{session.updated_at ? ` · ${formatTime(session.updated_at)}` : ''}
</p>
</div>
</div>
<button
onClick={(e) => {
e.stopPropagation();
deleteSession(session.id);
}}
onClick={(e) => handleDeleteSession(e, session.id)}
className="opacity-0 group-hover:opacity-100 p-1 text-gray-400 hover:text-red-400 transition-all"
title="删除会话"
>