/**
* 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('、')}
`;
}).join('');
}
registerPage('settings', function(container) {
// Load saved settings
const settings = loadSettings();
const notificationRules = getNotificationRules(settings);
container.innerHTML = `
异常告警
设备、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] || '';
}