(() => { const API_BASE = '/api/language_behavior_lock' const DEFAULT_USERNAME = 'admin' const $ = id => document.getElementById(id) const now = () => Date.now() const genId = () => `${Date.now()}_${Math.random().toString(36).slice(2, 8)}` const state = { auth: { currentUser: '' }, db: { records: [], keywords: [] } } const defaultKeywords = () => ([ { id: genId(), text: '你怎么这么', type: 'speech', level: 'high', extraMinutes: 20, tip: '你触发了高风险表达,建议延长冷静期。', enabled: true, createdAt: now() }, { id: genId(), text: '早就说了', type: 'speech', level: 'medium', extraMinutes: 15, tip: '这类表达容易升级冲突,建议继续冷静。', enabled: true, createdAt: now() }, { id: genId(), text: '你一直都', type: 'speech', level: 'high', extraMinutes: 20, tip: '绝对化表达风险较高,建议先改写再决定。', enabled: true, createdAt: now() }, { id: genId(), text: '拿回家', type: 'behavior', level: 'high', extraMinutes: 25, tip: '涉及边界风险行为,建议延长冷静并复核后果。', enabled: true, createdAt: now() } ]) const normalizeKeyword = row => { const levelRaw = String(row && row.level || 'medium') const level = ['low', 'medium', 'high'].includes(levelRaw) ? levelRaw : 'medium' const typeRaw = String(row && row.type || 'all') const type = ['all', 'speech', 'behavior', 'securities'].includes(typeRaw) ? typeRaw : 'all' return { id: String(row && row.id || genId()), text: String(row && row.text || '').trim(), type, level, extraMinutes: Math.max(0, Number(row && row.extraMinutes) || 0), tip: String(row && row.tip || '').trim(), enabled: row && row.enabled !== false, createdAt: Number(row && row.createdAt) || now() } } const normalizeDB = raw => { const db = raw && typeof raw === 'object' ? raw : {} const records = Array.isArray(db.records) ? db.records : [] const keywordsRaw = Array.isArray(db.keywords) ? db.keywords : [] const keywords = keywordsRaw.map(normalizeKeyword).filter(k => k.text) return { records, keywords: keywords.length ? keywords : defaultKeywords() } } const setAuthStatus = text => { $('auth-status').textContent = text || '' } const setFormStatus = text => { $('form-status').textContent = text || '' } const openModal = ({ title, message, okText = '知道了', cancelText = '', tone = 'normal' }) => { return new Promise(resolve => { const mask = $('app-modal') const titleBox = $('modal-title') const msgBox = $('modal-message') const actions = $('modal-actions') actions.innerHTML = '' titleBox.textContent = title || '' msgBox.textContent = message || '' const close = val => { mask.style.display = 'none' resolve(val) } if (cancelText) { const cancelBtn = document.createElement('button') cancelBtn.className = 'btn' cancelBtn.type = 'button' cancelBtn.textContent = cancelText cancelBtn.addEventListener('click', () => close(false)) actions.appendChild(cancelBtn) } const okBtn = document.createElement('button') okBtn.className = tone === 'primary' ? 'btn btn-primary' : 'btn' okBtn.type = 'button' okBtn.textContent = okText okBtn.addEventListener('click', () => close(true)) actions.appendChild(okBtn) mask.style.display = '' }) } const modalAlert = (title, message) => openModal({ title, message, okText: '我知道了' }) const modalConfirm = (title, message, okText = '继续', cancelText = '取消') => openModal({ title, message, okText, cancelText, tone: 'primary' }) const apiRequest = async (path, options = {}) => { const res = await fetch(`${API_BASE}${path}`, { method: options.method || 'GET', headers: { 'Content-Type': 'application/json', ...(options.headers || {}) }, body: options.body === undefined ? undefined : JSON.stringify(options.body), credentials: 'include' }) let payload = null try { payload = await res.json() } catch { payload = null } if (!res.ok) { const msg = payload && payload.error ? String(payload.error) : `http_${res.status}` throw new Error(msg) } return payload || {} } const loadDB = async () => { const payload = await apiRequest('/data') state.db = normalizeDB(payload.db) } const saveDB = async () => { await apiRequest('/data', { method: 'PUT', body: { db: state.db, draft: null } }) } const levelText = level => ({ low: '低风险', medium: '中风险', high: '高风险' })[level] || '中风险' const renderAuth = () => { const user = state.auth.currentUser $('auth-login-box').style.display = user ? 'none' : '' $('auth-logged-box').style.display = user ? '' : 'none' $('app-content').style.display = user ? '' : 'none' $('auth-current-user').textContent = user ? `当前账号:${user}` : '' } const renderKeywordRules = () => { const box = $('keyword-list') box.innerHTML = '' if (!state.db.keywords.length) { box.textContent = '暂无关键词规则' return } state.db.keywords.slice().sort((a, b) => b.createdAt - a.createdAt).forEach(rule => { const row = document.createElement('div') row.className = 'row' const typeLabel = rule.type === 'speech' ? '发言' : rule.type === 'behavior' ? '行为' : rule.type === 'securities' ? '证券' : '全部' row.innerHTML = `