feat: 全链路优化 — 死锁修复、MD3主题、上下文持久化、群聊自然化、打字状态、知识库

**死锁根因修复**
- periodicThinkLoop:1015 orphaned lock → 删除(字段已原子化)
- RecordUserMessage 隔离为 recordMu
- atomic.Int64 替换 lastUserMessage/lastThinkTime 等

**MD3 / Android 17 主题**
- 毛玻璃卡片 (backdrop-filter)
- MD3 色彩令牌 (pink primary #f472b6)
- icons.js 独立矢量图标库 + 运行时 emoji 替换
- 无边框卡片、圆角按钮、阴影层次

**上下文持久化**
- AddMessage → saveToDB 异步写 PostgreSQL
- LoadFromDB 恢复 (admin-session-main + 懒加载)
- LLMMessage.Timestamp 字段

**群聊与适配器**
- group_ambient 模式: 非@消息让 LLM 自己判断是否插话
- 戳一戳动作消息总是回复
- NapCat 打字状态 (set_input_status, 最小3秒显示)
- HTTP API 配置 (http_url/http_token)

**知识库 & 防编造**
- knowledge.CanHandle 对 chat 意图也触发
- 关键词预筛选避免无关 embedding 调用
- persona + synthesizer 三重诚实规则
- 工具结果持久化到会话历史

**平台桥接器**
- detached:true Go进程独立存活
- ethend 重启自动接管已运行服务
- stop() 接管模式 taskkill/F/ PID
- Windows netstat 替代 fuser 获取 PID
- 重复适配器种子逻辑修复
- 失败转发日志 Direction: error

**崩溃诊断**
- crashlog 包 (Recover + WrapHTTP + LLMCall)
- /api/v1/debug/goroutines 端点
- thinker 操作日志 + 30s stats
- 日志写入 logs/ 目录持久化

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-28 11:29:29 +08:00
parent 0d6970a2d3
commit fd44b15d81
23 changed files with 1447 additions and 254 deletions
+25 -26
View File
@@ -20,8 +20,8 @@ async function fetchIoTDevices() {
}
var IOT_DEVICE_TYPES = {
'ac': '❄️', 'light': '💡', 'curtain': '🪟', 'door_lock': '🔒',
'camera': '📷', 'sensor': '📡', 'speaker': '🔊', 'thermostat': '🌡️',
'ac': 'ac_unit', 'light': 'lightbulb', 'curtain': 'blinds', 'door_lock': 'lock',
'camera': 'camera', 'sensor': 'sensors', 'speaker': 'volume_up', 'thermostat': 'thermostat',
};
var IOT_MODE_OPTIONS = ['cool', 'heat', 'auto', 'fan', 'dry'];
@@ -40,15 +40,15 @@ async function renderIoTPanel() {
// 更新操作栏 (只更新时间戳文本)
document.getElementById('panel-actions').innerHTML =
'<button class="btn btn-sm" onclick="renderIoTPanel()" id="iot-refresh-btn">🔄 刷新</button>' +
'<span class="iot-last-update"> 每3秒自动刷新 · 最后更新: ' + new Date().toLocaleTimeString('zh-CN', {hour12: false}) + '</span>';
'<button class="btn btn-sm" onclick="renderIoTPanel()" id="iot-refresh-btn">'+icon('refresh',14)+' 刷新</button>' +
'<span class="iot-last-update">'+icon('timer',12)+' 每3秒自动刷新 · 最后更新: ' + new Date().toLocaleTimeString('zh-CN', {hour12: false}) + '</span>';
if (result.error) {
var hint = '';
if (result.error.errorType === 'iot_not_running') {
hint = '<br><span style="font-size:11px">💡 提示: 请先在「服务管理」面板中启动 IoT Debug 服务</span>';
hint = '<br><span style="font-size:11px">'+icon('lightbulb',12)+' 提示: 请先在「服务管理」面板中启动 IoT Debug 服务</span>';
}
panel.innerHTML = '<div class="empty-state"><div class="icon">⚠️</div>' + escHtml(result.error.error) + hint + '</div>';
panel.innerHTML = '<div class="empty-state"><div class="icon">'+icon('warning_amber',24)+'</div>' + escHtml(result.error.error) + hint + '</div>';
STATE.iotInitialized = false;
return;
}
@@ -81,7 +81,7 @@ async function renderIoTPanel() {
if (firstRender || !grid) {
// 首次渲染: 创建完整结构
var html = '<div class="iot-refresh-bar">' +
'<span style="font-weight:600;font-size:14px" id="iot-device-count">📡 模拟 IoT 设备 (' + devices.length + ')</span>' +
'<span style="font-weight:600;font-size:14px" id="iot-device-count">'+icon('sensors',16)+' 模拟 IoT 设备 (' + devices.length + ')</span>' +
'<span style="font-size:11px;color:var(--text2)">通过 IoT 调试服务 (端口 8083) 管理</span>' +
'</div><div class="iot-device-grid" id="iot-device-grid">' +
devices.map(function(d) { return renderIoTDeviceCard(d); }).join('') +
@@ -91,7 +91,7 @@ async function renderIoTPanel() {
} else {
// 增量更新: 只更新设备数量、最后更新时间
var countEl = document.getElementById('iot-device-count');
if (countEl) countEl.textContent = '📡 模拟 IoT 设备 (' + devices.length + ')';
if (countEl) countEl.innerHTML = icon('sensors',16)+' 模拟 IoT 设备 (' + devices.length + ')';
// 对每个已有设备卡片做增量更新
devices.forEach(function(device) {
@@ -123,7 +123,7 @@ function updateIoTDeviceCardInPlace(device) {
var toggleBtn = card.querySelector('.iot-toggle-btn');
if (toggleBtn) {
toggleBtn.className = 'iot-toggle-btn ' + (isOn ? 'on' : 'off');
toggleBtn.textContent = isOn ? '⏻ 关闭' : '⏻ 开启';
toggleBtn.innerHTML = isOn ? icon('power_settings_new',14)+' 关闭' : icon('power_settings_new',14)+' 开启';
}
// 更新设备属性值 (温度、亮度、位置等)
@@ -182,8 +182,7 @@ function updateIoTDeviceCardInPlace(device) {
// 更新电量
var batteryEls = card.querySelectorAll('.iot-prop-value');
batteryEls.forEach(function(el) {
if (el.parentElement && el.parentElement.querySelector('.iot-prop-label') &&
el.parentElement.querySelector('.iot-prop-label').textContent.indexOf('🔋') !== -1) {
if (el.parentElement && el.parentElement.querySelector('.iot-prop-label[data-prop="battery"]')) {
if (device.battery != null) el.textContent = device.battery + '%';
}
});
@@ -194,9 +193,9 @@ function updateIoTDeviceCardInPlace(device) {
acBtns.forEach(function(btn) {
var text = btn.textContent.trim();
var currentTemp = device.temperature || 26;
if (text === ' -2°C') {
if (text === ''+icon('arrow_downward',12)+' -2°C') {
btn.setAttribute('onclick', "iotSetProperty('" + device.id + "', 'temperature', " + (currentTemp - 2) + ");refreshIoTDeviceCard('" + device.id + "')");
} else if (text === ' +2°C') {
} else if (text === ''+icon('arrow_upward',12)+' +2°C') {
btn.setAttribute('onclick', "iotSetProperty('" + device.id + "', 'temperature', " + (currentTemp + 2) + ");refreshIoTDeviceCard('" + device.id + "')");
}
});
@@ -205,13 +204,13 @@ function updateIoTDeviceCardInPlace(device) {
function renderIoTDeviceCard(device) {
var isOn = device.status === 'on';
var icon = IOT_DEVICE_TYPES[device.type] || '📦';
var iconName = IOT_DEVICE_TYPES[device.type] || 'inventory_2';
var propsHtml = '';
if (device.type === 'ac') {
propsHtml =
'<div class="iot-prop-row">' +
'<span class="iot-prop-label">🌡️ 温度</span>' +
'<span class="iot-prop-label">'+icon('thermostat',16)+' 温度</span>' +
'<div class="iot-prop-control">' +
'<input type="range" min="16" max="30" value="' + (device.temperature || 26) + '"' +
' onchange="iotSetProperty(\'' + device.id + '\', \'temperature\', parseInt(this.value)); this.nextElementSibling.textContent=this.value+\'°C\'">' +
@@ -219,7 +218,7 @@ function renderIoTDeviceCard(device) {
'</div>' +
'</div>' +
'<div class="iot-prop-row">' +
'<span class="iot-prop-label">🔄 模式</span>' +
'<span class="iot-prop-label">'+icon('refresh',14)+' 模式</span>' +
'<div class="iot-prop-control" style="gap:4px">' +
IOT_MODE_OPTIONS.map(function(m) {
var active = (device.mode || 'cool') === m ? ' active' : '';
@@ -230,7 +229,7 @@ function renderIoTDeviceCard(device) {
} else if (device.type === 'light') {
propsHtml =
'<div class="iot-prop-row">' +
'<span class="iot-prop-label">💡 亮度</span>' +
'<span class="iot-prop-label">'+icon('lightbulb',16)+' 亮度</span>' +
'<div class="iot-prop-control">' +
'<input type="range" min="1" max="100" value="' + (device.brightness || 80) + '"' +
' onchange="iotSetProperty(\'' + device.id + '\', \'brightness\', parseInt(this.value)); this.nextElementSibling.textContent=this.value+\'%\'">' +
@@ -238,7 +237,7 @@ function renderIoTDeviceCard(device) {
'</div>' +
'</div>' +
'<div class="iot-prop-row">' +
'<span class="iot-prop-label">🎨 颜色</span>' +
'<span class="iot-prop-label">'+icon('palette',16)+' 颜色</span>' +
'<div class="iot-prop-control" style="gap:4px">' +
IOT_COLOR_OPTIONS.map(function(c) {
var active = (device.color || 'warm_white') === c.value ? ' active' : '';
@@ -250,7 +249,7 @@ function renderIoTDeviceCard(device) {
} else if (device.type === 'curtain') {
propsHtml =
'<div class="iot-prop-row">' +
'<span class="iot-prop-label">🪟 位置</span>' +
'<span class="iot-prop-label">'+icon('blinds',16)+' 位置</span>' +
'<div class="iot-prop-control">' +
'<input type="range" min="0" max="100" value="' + (device.position != null ? device.position : 100) + '"' +
' onchange="iotSetProperty(\'' + device.id + '\', \'position\', parseInt(this.value)); this.nextElementSibling.textContent=this.value+\'%\'">' +
@@ -260,7 +259,7 @@ function renderIoTDeviceCard(device) {
} else if (device.temperature != null) {
propsHtml =
'<div class="iot-prop-row">' +
'<span class="iot-prop-label">🌡️ 温度</span>' +
'<span class="iot-prop-label">'+icon('thermostat',16)+' 温度</span>' +
'<span class="iot-prop-value">' + device.temperature + (device.unit || '°C') + '</span>' +
'</div>';
}
@@ -268,32 +267,32 @@ function renderIoTDeviceCard(device) {
if (device.battery != null) {
propsHtml +=
'<div class="iot-prop-row">' +
'<span class="iot-prop-label">🔋 电量</span>' +
'<span class="iot-prop-label" data-prop="battery">'+icon('battery_full',16)+' 电量</span>' +
'<span class="iot-prop-value">' + device.battery + '%</span>' +
'</div>';
}
var actionsHtml = '<div class="iot-device-actions">' +
'<button class="iot-toggle-btn ' + (isOn ? 'on' : 'off') + '" onclick="iotToggle(\'' + device.id + '\')">' +
(isOn ? '⏻ 关闭' : '⏻ 开启') +
(isOn ? ''+icon('power_settings_new',14)+' 关闭' : ''+icon('power_settings_new',14)+' 开启') +
'</button>';
if (device.type === 'ac') {
var currentTemp = device.temperature || 26;
actionsHtml +=
'<button class="btn btn-xs" onclick="iotSetProperty(\'' + device.id + '\', \'temperature\', ' + (currentTemp - 2) + ');refreshIoTDeviceCard(\'' + device.id + '\')"> -2°C</button>' +
'<button class="btn btn-xs" onclick="iotSetProperty(\'' + device.id + '\', \'temperature\', ' + (currentTemp + 2) + ');refreshIoTDeviceCard(\'' + device.id + '\')"> +2°C</button>';
'<button class="btn btn-xs" onclick="iotSetProperty(\'' + device.id + '\', \'temperature\', ' + (currentTemp - 2) + ');refreshIoTDeviceCard(\'' + device.id + '\')">'+icon('arrow_downward',12)+' -2°C</button>' +
'<button class="btn btn-xs" onclick="iotSetProperty(\'' + device.id + '\', \'temperature\', ' + (currentTemp + 2) + ');refreshIoTDeviceCard(\'' + device.id + '\')">'+icon('arrow_upward',12)+' +2°C</button>';
}
actionsHtml +=
'<button class="btn btn-xs" onclick="iotShowHistory(\'' + device.id + '\')" style="margin-left:auto">📋 历史</button>' +
'<button class="btn btn-xs" onclick="iotShowHistory(\'' + device.id + '\')" style="margin-left:auto">'+icon('list_alt',13)+' 历史</button>' +
'</div>' +
'<div id="iot-history-' + device.id + '" class="iot-history-panel" style="display:none"></div>';
return '<div class="iot-device-card ' + (isOn ? 'on' : 'off') + '" id="iot-card-' + device.id + '">' +
'<div class="iot-device-header">' +
'<div class="iot-device-name">' +
'<span style="font-size:24px">' + icon + '</span>' +
'<span style="font-size:24px">' + icon(iconName, 24) + '</span>' +
'<div>' +
'<div>' + escHtml(device.name) + '</div>' +
'<div class="iot-device-type">' + escHtml(device.type) + ' · ' + escHtml(device.id) + '</div>' +