/** * TalkingQ Platform - Settings Page */ function getNotificationCatalog() { return window.TQ_NOTIFICATION_EVENT_CATALOG || [ { type: 'device.offline_rate_exceeded', category: '设备健康', name: '设备离线率异常', severity: 'critical', trigger: '公司设备离线率连续 10 分钟超过阈值', ruleSummary: '离线率 > 5%,且连续 10 分钟未恢复', window: '10 分钟连续检测', cooldown: '30 分钟内同公司同规则只通知 1 次', contentFields: ['公司名称', '当前离线率', '影响设备数', '最近检测时间', '建议处理动作'], defaultThreshold: 5, thresholdUnit: '%', defaultChannels: ['in_app', 'webhook'], }, ]; } function getNotificationInputKey(type) { return String(type).replace(/[^a-zA-Z0-9]/g, '_'); } function getNotificationSeverityLabel(severity) { return { critical: '高危', warning: '预警', info: '提醒', success: '完成', }[severity] || '提醒'; } function getDefaultNotificationRules(settings = {}) { const legacyBadcaseEnabled = settings.notifyBadcase !== false; const legacyOfflineEnabled = settings.notifyOffline !== false; const legacyOTAEnabled = settings.notifyOTA !== false; return getNotificationCatalog().reduce((rules, item) => { let enabled = item.defaultEnabled !== false; if (item.type === 'device.offline_rate_exceeded') enabled = legacyOfflineEnabled; if (item.type === 'ota.task_failed') enabled = legacyOTAEnabled; if (item.type === 'ota.failure_rate_exceeded') enabled = legacyOTAEnabled; if (item.type === 'ai.badcase_rate_exceeded') enabled = legacyBadcaseEnabled; rules[item.type] = { enabled, threshold: item.type === 'ai.badcase_rate_exceeded' ? (settings.badcaseThreshold || item.defaultThreshold || '') : (item.defaultThreshold || ''), channels: item.defaultChannels || ['in_app'], }; return rules; }, {}); } function getNotificationRules(settings = {}) { const defaults = getDefaultNotificationRules(settings); const saved = settings.notificationRules || {}; return Object.fromEntries(Object.entries(defaults).map(([type, rule]) => [ type, { ...rule, ...(saved[type] || {}), channels: saved[type]?.channels || rule.channels, }, ])); } function renderNotificationRuleRows(rules) { return getNotificationCatalog().map(item => { const key = getNotificationInputKey(item.type); const rule = rules[item.type] || {}; const channels = new Set(rule.channels || item.defaultChannels || ['in_app']); const hasThreshold = item.defaultThreshold !== undefined; const thresholdText = item.thresholdLabel || (hasThreshold ? `${rule.threshold || item.defaultThreshold}${item.thresholdUnit || ''}` : '发生即通知'); return `
${item.name}
${item.trigger}
${getNotificationSeverityLabel(item.severity)}
规则:${item.ruleSummary || thresholdText} 窗口:${item.window || '实时'} 冷却:${item.cooldown || '无'}
通知内容:${item.contentFields.join('、')}
${hasThreshold ? ` ` : ''} ${!hasThreshold ? `
阈值 ${thresholdText}
` : ''}
`; }).join(''); } registerPage('settings', function(container) { // Load saved settings const settings = loadSettings(); const notificationRules = getNotificationRules(settings); container.innerHTML = `
API 密钥配置
配置 AI 模型服务的 API 密钥
用于 GPT-4o、GPT-4o mini 等模型调用
用于通义千问等阿里云大模型服务
用于 Gemini 1.5 Flash 等模型
平台配置
配置系统级参数
通知设置
只通知需要关注或处理的事件,包含异常、异步任务和关键生命周期操作
异常告警 设备、AI 安全、OTA 失败等需要处理的异常。
异步结果 应用发布、知识库索引等耗时任务的失败或完成结果。
安全权限 异常登录、管理员变更、API Key 创建/撤销、权限异常变更。
${renderNotificationRuleRows(notificationRules)}
`; lucide.createIcons({ elements: [container] }); }); // Load settings from in-memory demo state function loadSettings() { if (window.__talkingqSettings) { return JSON.parse(JSON.stringify(window.__talkingqSettings)); } try { localStorage.removeItem('talkingq_settings'); } catch (e) { console.warn('Failed to clear legacy settings cache', e); } return {}; } // Save settings to in-memory demo state function saveSettings(settings) { window.__talkingqSettings = JSON.parse(JSON.stringify(settings || {})); } // Toggle API key visibility function toggleApiKeyVisibility(inputId, btn) { const input = document.getElementById(inputId); if (input) { const isPassword = input.type === 'password'; input.type = isPassword ? 'text' : 'password'; btn.innerHTML = ``; btn.setAttribute('title', isPassword ? '隐藏密钥' : '显示密钥'); lucide.createIcons({ elements: [btn] }); } } async function testApiConnection(btn) { const keys = ['apiKeyOpenai', 'apiKeyAli', 'apiKeyGemini'] .map(id => document.getElementById(id)?.value.trim()) .filter(Boolean); if (keys.length === 0) { Toast.warning('请先填写至少一个 API Key'); return; } const original = btn.innerHTML; btn.disabled = true; btn.innerHTML = ' 测试中...'; lucide.createIcons({ elements: [btn] }); await new Promise(resolve => setTimeout(resolve, 800)); btn.disabled = false; btn.innerHTML = original; lucide.createIcons({ elements: [btn] }); Toast.success('API 连接测试通过'); } // Save API Keys function saveApiKeys() { const settings = loadSettings(); settings.apiKeys = { openai: document.getElementById('apiKeyOpenai')?.value || '', ali: document.getElementById('apiKeyAli')?.value || '', gemini: document.getElementById('apiKeyGemini')?.value || '' }; saveSettings(settings); Toast.success('API 密钥已保存'); } // Save Platform Settings function savePlatformSettings() { const settings = loadSettings(); settings.platformName = document.getElementById('platformName')?.value || 'TalkingQ'; settings.refreshInterval = parseInt(document.getElementById('refreshInterval')?.value || '300'); settings.defaultModel = document.getElementById('defaultModel')?.value || 'gpt-4o-mini'; settings.enableDebug = document.getElementById('enableDebug')?.checked || false; saveSettings(settings); Toast.success('平台配置已保存'); } // Save Notification Settings async function saveNotificationSettings() { const settings = loadSettings(); settings.notificationRules = {}; getNotificationCatalog().forEach(item => { const key = getNotificationInputKey(item.type); const channels = ['in_app']; if (document.getElementById(`notif_${key}_channel_email`)?.checked) channels.push('email'); if (document.getElementById(`notif_${key}_channel_webhook`)?.checked) channels.push('webhook'); settings.notificationRules[item.type] = { enabled: document.getElementById(`notif_${key}_enabled`)?.checked || false, threshold: item.defaultThreshold !== undefined ? parseInt(document.getElementById(`notif_${key}_threshold`)?.value || item.defaultThreshold, 10) : undefined, thresholdUnit: item.thresholdUnit || undefined, thresholdLabel: item.thresholdLabel || undefined, window: item.window || undefined, cooldown: item.cooldown || undefined, channels, }; }); settings.notifyBadcase = settings.notificationRules['ai.badcase_rate_exceeded']?.enabled !== false; settings.badcaseThreshold = settings.notificationRules['ai.badcase_rate_exceeded']?.threshold || 5; settings.notifyOffline = settings.notificationRules['device.offline_rate_exceeded']?.enabled !== false; settings.notifyOTA = settings.notificationRules['ota.task_failed']?.enabled !== false; saveSettings(settings); try { await API.settings.updateNotifications({ rules: settings.notificationRules, tenantScope: window.__getTenantScopeParams ? window.__getTenantScopeParams() : {}, }); } catch (error) { Toast.warning('通知设置已保存在前端,后端接口接入后会同步保存'); return; } window.__refreshNotifications?.(); Toast.success('通知设置已保存'); } // Get API Key helper function getApiKey(provider) { const settings = loadSettings(); return settings.apiKeys?.[provider] || ''; }