fix: 第二轮修复 — 数据库启动检查、会话持久化、URL路由、设备排序等

1. DevTools 启动前检查数据库状态,失败时自动尝试启动
2. ai-core 添加数据库断线重连机制 (30秒间隔)
3. Dashboard 添加数据库状态卡片 (启动/停止/重启)
4. Gateway 会话空闲超时管理 (30分钟标记空闲)
5. 会话/消息 PostgreSQL 持久化 (SessionStore + REST API)
6. 前端服务端会话持久化 + URL hash 路由 + 侧边栏管理
7. 管理员回到主对话按钮
8. IoT 设备卡片固定排序
9. 更新相关文档
This commit is contained in:
2026-05-17 17:18:02 +08:00
parent 745b1c6aad
commit e7b7eff0d8
21 changed files with 1735 additions and 284 deletions
+160 -32
View File
@@ -1,15 +1,38 @@
import { create } from 'zustand';
import type { Session } from '@/types/session';
import type { Message } from '@/types/chat';
import { fetchSessionMessages as apiFetchMessages } from '@/api/client';
import {
fetchSessions,
fetchMessages,
createSession as apiCreateSession,
deleteSession as apiDeleteSession,
deleteAllSessions as apiDeleteAllSessions,
clearSessionMessages as apiClearMessages,
} from '@/api/sessions';
import { useChatStore } from '@/store/chatStore';
/** 生成简易随机ID */
function randomID(n: number = 12): string {
const letters = 'abcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < n; i++) {
result += letters.charAt(Math.floor(Math.random() * letters.length));
}
return `session_${result}`;
}
/** 判断是否为管理员用户 (user_id 以 "admin_" 开头) */
export function isAdminUser(userId: string | null): boolean {
return userId?.startsWith('admin_') ?? false;
}
interface SessionStore {
sessions: Session[];
currentSessionId: string | null;
loading: boolean;
messages: Message[];
// 基础操作
setSessions: (sessions: Session[]) => void;
addSession: (session: Session) => void;
removeSession: (id: string) => void;
@@ -17,9 +40,17 @@ interface SessionStore {
setLoading: (loading: boolean) => void;
setMessages: (messages: Message[]) => void;
clearMessages: () => void;
// 服务端持久化操作
loadSessionsFromServer: (userId: string) => Promise<void>;
loadMessagesFromServer: (sessionId: string) => Promise<void>;
clearMainSessionMessages: (sessionId: string) => Promise<boolean>;
deleteSessionAndRefresh: (id: string, userId: string) => Promise<void>;
deleteAllSessionsAndReset: (userId: string) => Promise<void>;
ensureMainSession: (userId: string) => Promise<Session | null>;
}
export const useSessionStore = create<SessionStore>((set) => ({
export const useSessionStore = create<SessionStore>((set, get) => ({
sessions: [],
currentSessionId: null,
loading: false,
@@ -34,46 +65,143 @@ export const useSessionStore = create<SessionStore>((set) => ({
currentSessionId: state.currentSessionId === id ? null : state.currentSessionId,
messages: state.currentSessionId === id ? [] : state.messages,
})),
setCurrentSessionId: async (id) => {
// 立即清除旧消息,防止闪旧数据
set({ currentSessionId: id, messages: [], loading: true });
useChatStore.getState().clearMessages();
// 清除旧消息(同时清 chatStore)
if (id === null) {
set({ messages: [], loading: false });
return;
}
// 从后端加载历史消息
try {
const resp = await apiFetchMessages(id);
if (resp.data) {
const data = resp.data as { messages: Message[] };
const msgs = (data.messages || []).map((m: Message, i: number) => ({
...m,
id: m.id || `hist_${i}_${Date.now()}`,
}));
set({ messages: msgs, loading: false });
// 同步到 chatStore 以便 ChatContainer 渲染
useChatStore.getState().setMessages(msgs);
} else {
set({ messages: [], loading: false });
useChatStore.getState().clearMessages();
}
} catch {
set({ messages: [], loading: false });
setCurrentSessionId: (id) => {
set({ currentSessionId: id });
// 切换会话时清空旧消息,等待加载
if (id !== get().currentSessionId) {
set({ messages: [], loading: true });
useChatStore.getState().clearMessages();
}
},
setLoading: (loading) => set({ loading }),
setMessages: (messages) => {
set({ messages });
// 同步到 chatStore
useChatStore.getState().setMessages(messages);
},
clearMessages: () => {
set({ messages: [] });
useChatStore.getState().clearMessages();
},
// ========== 服务端持久化操作 ==========
/**
* 从服务端加载会话列表
*/
loadSessionsFromServer: async (userId: string) => {
set({ loading: true });
try {
const sessions = await fetchSessions(userId);
set({ sessions, loading: false });
} catch {
set({ loading: false });
}
},
/**
* 从服务端加载指定会话的消息历史
*/
loadMessagesFromServer: async (sessionId: string) => {
set({ loading: true });
try {
const resp = await fetchMessages(sessionId);
const rawMessages = resp.messages || [];
const msgs: Message[] = rawMessages.map((m: any, i: number) => ({
id: m.id ? String(m.id) : `hist_${i}_${Date.now()}`,
role: m.role,
content: m.content,
timestamp: typeof m.created_at === 'number' ? m.created_at : Date.now(),
isStreaming: false,
}));
set({ messages: msgs, loading: false });
useChatStore.getState().setMessages(msgs);
} catch {
set({ messages: [], loading: false });
useChatStore.getState().clearMessages();
}
},
/**
* 清空主对话的消息但保留会话本身
*/
clearMainSessionMessages: async (sessionId: string) => {
const ok = await apiClearMessages(sessionId);
if (ok) {
set({ messages: [] });
useChatStore.getState().clearMessages();
}
return ok;
},
/**
* 删除会话并自动切换到下一个可用会话
*/
deleteSessionAndRefresh: async (id: string, userId: string) => {
const ok = await apiDeleteSession(id);
if (!ok) return;
const state = get();
const remaining = state.sessions.filter((s) => s.id !== id);
const wasCurrent = state.currentSessionId === id;
// 更新本地列表
set({ sessions: remaining });
if (wasCurrent) {
if (remaining.length > 0) {
// 切换到列表中的第一个会话
const nextId = remaining[0].id;
set({ currentSessionId: nextId });
await get().loadMessagesFromServer(nextId);
} else {
// 没有会话了:管理员回到主对话,普通用户创建新对话
set({ currentSessionId: null, messages: [] });
useChatStore.getState().clearMessages();
}
}
},
/**
* 删除所有会话并重置状态
*/
deleteAllSessionsAndReset: async (userId: string) => {
const ok = await apiDeleteAllSessions(userId);
if (!ok) return;
set({ sessions: [], currentSessionId: null, messages: [] });
useChatStore.getState().clearMessages();
},
/**
* 确保主对话存在(管理员用户)。若不存在则创建并返回。
*/
ensureMainSession: async (userId: string) => {
const state = get();
// 先检查本地列表
const existing = state.sessions.find((s) => s.is_main);
if (existing) return existing;
// 本地没有,尝试从服务端加载
await get().loadSessionsFromServer(userId);
const refreshed = get().sessions.find((s) => s.is_main);
if (refreshed) return refreshed;
// 服务端也没有,创建主对话
const sid = randomID();
const created = await apiCreateSession(userId, sid, '主对话', true);
if (created) {
const newSession: Session = {
id: created.id,
user_id: created.user_id,
title: created.title,
is_main: created.is_main,
created_at: String(created.created_at),
updated_at: String(created.updated_at),
message_count: 0,
};
set((s) => ({ sessions: [newSession, ...s.sessions] }));
return newSession;
}
return null;
},
}));