fix: 第四轮调试 — 回复去重/消息时序/UI布局/自主思考深度优化 + 文档重整
后端修复: - main.go: 恢复 /api/v1/chat 路由中丢失的 handleChat 调用 (空响应回归) - orchestrator.go: splitChatByLines 改为双换行分割, 避免单换行误拆 - chat_handler.go: multi_message 增加 !hasReview 守卫, 消息延迟 200→800ms - thinker.go: RecordUserMessage 追踪活跃会话ID, 推送主动消息到正确会话 - thinker.go: 增强思考提示词 — 禁止在用户休息/离开时发送主动消息 前端修复: - useWebSocket.ts: stream_segments 不再创建消息气泡, 消除重复回复 - MessageBubble.tsx: 动作消息居左对齐无头像, 时间戳移至气泡外侧 hover 显示 - ChatInput.tsx: 昔涟输入提示移至输入框上方, 波点动画效果 - MessageList/TypingIndicator/ChatContainer: 清理冗余 isTyping 传递 - MemoryPanel.tsx: 新增记忆面板组件 文档重整: - docs/debug/ → docs/debug_log/ 重命名统一 - 新增 debug_log/README.md 索引 - .gitignore: 新增 android/ 排除规则 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+92
-10
@@ -298,6 +298,48 @@ input[type="range"] { accent-color: var(--accent); padding: 0; }
|
||||
.quick-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
|
||||
|
||||
/* 刷新按钮旋转 */
|
||||
|
||||
/* 页面加载进度条 */
|
||||
@keyframes loadingBar { 0% { width: 0; } 30% { width: 40%; } 60% { width: 75%; } 100% { width: 95%; } }
|
||||
.loading-overlay {
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
padding: 60px 20px; gap: 16px;
|
||||
}
|
||||
.loading-progress-bar {
|
||||
width: 260px; height: 3px; background: var(--bg3); border-radius: 2px; overflow: hidden;
|
||||
}
|
||||
.loading-progress-fill {
|
||||
height: 100%; background: linear-gradient(90deg, var(--accent), var(--blue));
|
||||
border-radius: 2px; animation: loadingBar 3s ease-out forwards;
|
||||
}
|
||||
.loading-text { color: var(--text2); font-size: 13px; }
|
||||
.loading-detail { color: var(--text3); font-size: 11px; }
|
||||
.loading-icon { font-size: 36px; animation: loadingPulse 1.2s ease-in-out infinite; }
|
||||
@keyframes loadingPulse {
|
||||
0%, 100% { opacity: 0.35; } 50% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* 按钮加载状态 */
|
||||
.btn.loading {
|
||||
position: relative; pointer-events: none; opacity: 0.7;
|
||||
}
|
||||
.btn.loading::after {
|
||||
content: ""; position: absolute;
|
||||
width: 12px; height: 12px; top: 50%; left: 50%; margin: -6px 0 0 -6px;
|
||||
border: 2px solid transparent; border-top-color: currentColor;
|
||||
border-radius: 50%; animation: spin .6s linear infinite;
|
||||
}
|
||||
.btn.loading .btn-text { visibility: hidden; }
|
||||
|
||||
/* 操作反馈闪烁 */
|
||||
@keyframes flashBorder {
|
||||
0% { border-color: var(--accent); box-shadow: 0 0 8px var(--accent); }
|
||||
100% { border-color: var(--border); box-shadow: none; }
|
||||
}
|
||||
.card.action-pending {
|
||||
animation: flashBorder .6s ease-in-out 3;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
||||
.spinning { animation: spin 1s linear infinite; }
|
||||
|
||||
@@ -657,7 +699,7 @@ input[type="range"] { accent-color: var(--accent); padding: 0; }
|
||||
</div>
|
||||
<div id="panel-container">
|
||||
<!-- 仪表盘 -->
|
||||
<div class="panel active" id="panel-dashboard"></div>
|
||||
<div class="panel active" id="panel-dashboard"><div class="loading-overlay" id="dashboard-loading"><div class="loading-icon">🛠️</div><div class="loading-text">正在连接服务并加载数据...</div><div class="loading-progress-bar"><div class="loading-progress-fill"></div></div><div class="loading-detail" id="loading-detail">初始化中</div></div></div>
|
||||
<!-- 记忆管理 -->
|
||||
<div class="panel" id="panel-memory"></div>
|
||||
<!-- 会话监看 -->
|
||||
@@ -997,6 +1039,9 @@ function stopDbAutoRefresh() {
|
||||
|
||||
// ========== 面板1: 仪表盘 ==========
|
||||
async function renderDashboard() {
|
||||
// 更新加载提示(如果加载覆盖层还在)
|
||||
var loadDetail = document.getElementById("loading-detail");
|
||||
if (loadDetail) loadDetail.textContent = "正在加载仪表盘数据...";
|
||||
const data = await api('/api/dashboard');
|
||||
if (data.error) {
|
||||
document.getElementById('panel-dashboard').innerHTML = '<div class="empty-state"><div class="icon">⚠️</div>' + escHtml(data.error) + '</div>';
|
||||
@@ -2179,16 +2224,53 @@ async function clearSvcLogs() {
|
||||
|
||||
// ---- 服务操作 ----
|
||||
async function svcAction(cmd, serviceId) {
|
||||
let url;
|
||||
if (cmd === 'start-all') url = '/api/services/start-all';
|
||||
else if (cmd === 'start-all-fresh') url = '/api/services/start-all-fresh';
|
||||
else if (cmd === 'stop-all') url = '/api/services/stop-all';
|
||||
else url = `/api/services/${serviceId}/${cmd}`;
|
||||
var actionLabels = { start: "启动", stop: "停止", restart: "重启", build: "编译", "start-all": "一键启动", "start-all-fresh": "强制重启全部", "stop-all": "全部停止" };
|
||||
var label = actionLabels[cmd] || cmd;
|
||||
var svcLabel = serviceId ? escapeId(serviceId) : "";
|
||||
var msg = svcLabel ? "正在" + label + " " + svcLabel + "..." : "正在执行: " + label + "...";
|
||||
showToast(msg, "info");
|
||||
|
||||
const method = ['start','stop','restart','build','start-all','start-all-fresh','stop-all'].includes(cmd) ? 'POST' : 'GET';
|
||||
const res = await api(url, { method });
|
||||
showToast(res.message || res.error || `${cmd} 完成`, res.error ? 'error' : 'success');
|
||||
refreshStatus();
|
||||
// 立即更新本地状态和 UI(乐观更新)
|
||||
if (serviceId) {
|
||||
var newStatus;
|
||||
if (cmd === "start") newStatus = "starting";
|
||||
else if (cmd === "stop") newStatus = "stopped";
|
||||
else if (cmd === "restart") newStatus = "starting";
|
||||
else if (cmd === "build") newStatus = "building";
|
||||
if (newStatus) {
|
||||
if (!STATE.serviceStatus[serviceId]) {
|
||||
STATE.serviceStatus[serviceId] = { name: escapeId(serviceId), status: newStatus, pid: null, port: "-", uptime: 0 };
|
||||
} else {
|
||||
STATE.serviceStatus[serviceId].status = newStatus;
|
||||
}
|
||||
if (STATE.activePanel === "services") renderServiceCards();
|
||||
if (STATE.activePanel === "dashboard") renderDashboardSvcCards(STATE.serviceStatus);
|
||||
}
|
||||
} else {
|
||||
// 批量操作:给所有服务设置过渡状态
|
||||
var newStatus = (cmd === "start-all" || cmd === "start-all-fresh") ? "starting" : "stopped";
|
||||
ALL_SVC_IDS.forEach(function(id) {
|
||||
if (!STATE.serviceStatus[id]) {
|
||||
STATE.serviceStatus[id] = { name: escapeId(id), status: newStatus, pid: null, port: "-", uptime: 0 };
|
||||
} else {
|
||||
STATE.serviceStatus[id].status = newStatus;
|
||||
}
|
||||
});
|
||||
if (STATE.activePanel === "services") renderServiceCards();
|
||||
if (STATE.activePanel === "dashboard") renderDashboardSvcCards(STATE.serviceStatus);
|
||||
}
|
||||
|
||||
// 发起 API 请求
|
||||
let url;
|
||||
if (cmd === "start-all") url = "/api/services/start-all";
|
||||
else if (cmd === "start-all-fresh") url = "/api/services/start-all-fresh";
|
||||
else if (cmd === "stop-all") url = "/api/services/stop-all";
|
||||
else url = "/api/services/" + serviceId + "/" + cmd;
|
||||
|
||||
var method = ["start","stop","restart","build","start-all","start-all-fresh","stop-all"].indexOf(cmd) >= 0 ? "POST" : "GET";
|
||||
var res = await api(url, { method: method });
|
||||
showToast(res.message || res.error || (label + " 完成"), res.error ? "error" : "success");
|
||||
refreshStatus();
|
||||
}
|
||||
|
||||
async function checkHealth(id) {
|
||||
|
||||
Reference in New Issue
Block a user