fix: Phase 6联调 — 插件管理器端口修正 + 多模型配置系统整合 + 历史消息刷新修复
## 调试日志
### 1. 插件管理器启动失败
- **症状**: DevTools 显示插件管理器一直"已停止",手动启动正常
- **排查**: 对比 process-manager.js 传入的环境变量 vs plugin-manager config.go 读取的变量
- **根因**: config.js 传入 PLUGIN_MANAGER_PORT=8094,但 config.go 读取 os.Getenv("PORT"),env 名不匹配。且 process.env 中 PORT 泄露时被误读为 9090,与 DevTools 端口冲突
- **修复**: config.js 将 PLUGIN_MANAGER_PORT → PORT,使 env 名与代码一致 (c3055f4)
### 2. 历史消息刷新后消失
- **症状**: 浏览器刷新后聊天历史清空
- **排查**: WebSocket history_response handler 中 if (msg.messages) 对空数组 [] 为 truthy
- **根因**: 后端返回空的 history_response (缓存为空) 时,空数组覆盖了 HTTP 已加载的消息
- **修复**: useWebSocket.ts 改为 if (msg.messages && msg.messages.length > 0),空数组走 else-if 分支仅打日志,不覆盖已有消息
### 3. Phase 6 多模型配置系统
- Gateway: ModelsConfigStore (JSON文件持久化) + Admin CRUD API (providers/models/routing)
- ai-core: ModelSelector 支持按 purpose 选择 + fallback_chain,无配置时回退 .env
- DevTools: 模型配置管理面板 (Providers/Models/Routing 三Tab)、在线模型查询代理、路由表单 checkbox 多选、关键词搜索过滤
- .gitignore: models.json + platform_configs.json
### 4. 多端客户端追踪
- Hub 新增 knownClients 映射 (clientID → KnownClient),在线/离线状态追踪
- 客户端备注持久化到 PostgreSQL
- DevTools 客户端管理面板
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -21,9 +21,16 @@ interface ChatStore {
|
||||
isLoadingHistory: boolean;
|
||||
historyPage: number;
|
||||
|
||||
// 多气泡消息队列:确保气泡依次出现 + 逐字动画
|
||||
messageQueue: Message[];
|
||||
|
||||
addMessage: (message: Message) => void;
|
||||
appendToLastMessage: (content: string) => void;
|
||||
finishStreaming: () => void;
|
||||
/** 将一个消息加入队列,等待上一个气泡的逐字动画完成后再展示 */
|
||||
enqueueMessage: (message: Message) => void;
|
||||
/** 当前气泡逐字动画完成后调用:关闭 isStreaming,出队下一个 */
|
||||
onTypewriterDone: (messageId: string) => void;
|
||||
setMessages: (messages: Message[]) => void;
|
||||
setTyping: (typing: boolean) => void;
|
||||
clearMessages: () => void;
|
||||
@@ -49,6 +56,7 @@ export const useChatStore = create<ChatStore>((set) => ({
|
||||
hasMoreMessages: false,
|
||||
isLoadingHistory: false,
|
||||
historyPage: 1,
|
||||
messageQueue: [],
|
||||
|
||||
addMessage: (message) =>
|
||||
set((state) => ({
|
||||
@@ -84,14 +92,48 @@ export const useChatStore = create<ChatStore>((set) => ({
|
||||
isStreaming: false,
|
||||
};
|
||||
}
|
||||
return { messages: msgs, isTyping: false };
|
||||
// Process queued messages after streaming finishes
|
||||
const queue = [...state.messageQueue];
|
||||
if (queue.length > 0) {
|
||||
const next = queue.shift()!;
|
||||
return {
|
||||
messages: [...msgs, { ...next, isStreaming: true }],
|
||||
messageQueue: queue,
|
||||
isTyping: false,
|
||||
};
|
||||
}
|
||||
return { messages: msgs, isTyping: false, messageQueue: queue };
|
||||
}),
|
||||
|
||||
enqueueMessage: (message) =>
|
||||
set((state) => ({
|
||||
messageQueue: [...state.messageQueue, message],
|
||||
})),
|
||||
|
||||
onTypewriterDone: (messageId) =>
|
||||
set((state) => {
|
||||
// Mark this specific message as no longer streaming
|
||||
const msgs = state.messages.map((m) =>
|
||||
m.id === messageId ? { ...m, isStreaming: false } : m
|
||||
);
|
||||
// Process next queued message
|
||||
const queue = [...state.messageQueue];
|
||||
if (queue.length > 0) {
|
||||
const next = queue.shift()!;
|
||||
return {
|
||||
messages: [...msgs, { ...next, isStreaming: true }],
|
||||
messageQueue: queue,
|
||||
isTyping: false,
|
||||
};
|
||||
}
|
||||
return { messages: msgs, isTyping: false, messageQueue: queue };
|
||||
}),
|
||||
|
||||
setMessages: (messages) => set({ messages, isTyping: false }),
|
||||
|
||||
setTyping: (typing) => set({ isTyping: typing }),
|
||||
|
||||
clearMessages: () => set({ messages: [], isTyping: false, hasMoreMessages: false, historyPage: 1 }),
|
||||
clearMessages: () => set({ messages: [], isTyping: false, hasMoreMessages: false, historyPage: 1, messageQueue: [] }),
|
||||
|
||||
setContinuousMode: (enabled) => set({ continuousMode: enabled }),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user