/* v38 (2026-09-18 起, 2026-09-19 M3/M4 修订) 手机端与桌面端对齐: 1) uid 归一化: **id 优先**(与桌面端 index.html:1701 同口径) -> admin-001 而非 admin, 修「识别不到我的账号 / 管理员豁免不生效 / 会话桶分裂(手机写 admin, 桌面写 admin-001)」 2) 权威修复: 带 token 时调 /api/auth/userinfo 覆盖 user_info.id -> 自动纠正历史被 v38 旧版改写过的 id 3) 会话历史回显: 不再一次性放弃; uid 就绪/变化时重试, 失败可重来 4) 胶囊按钮接通后端 AI: 语音真发送; 打电话/视频通话真转人工 幂等; 独立文件, 不改任何既有文件逻辑; 回滚 = 还原本文件 */ (function () { 'use strict'; if (window.__dwyV38) return; window.__dwyV38 = true; var API = 'https://api.gateway.invalid'; API = 'https://api-gateway.duoweiying.cn'; var uidCache = ''; /* 与桌面端一致: id 优先, 其次 username / nickname */ function pick(o) { return (o && (o.id || o.username || o.nickname)) || ''; } function norm(o) { if (!o || typeof o !== 'object') return o; var c = pick(o); if (c) { o.id = c; if (!o.username) o.username = c; } return o; } try { var raw = localStorage.getItem('user_info'); if (raw) { var o0 = norm(JSON.parse(raw)); uidCache = o0.id || ''; localStorage.setItem('user_info', JSON.stringify(o0)); } var u1 = uidCache || pick(JSON.parse(localStorage.getItem('user_info') || '{}')); if (u1) { localStorage.setItem('user_id', u1); uidCache = u1; } } catch (e) {} try { var _set = localStorage.setItem.bind(localStorage); localStorage.setItem = function (k, v) { try { if (k === 'user_info' && typeof v === 'string') { var o = norm(JSON.parse(v)); if (o && o.id) uidCache = o.id; v = JSON.stringify(o); } else if (k === 'user_id' && v) { uidCache = String(v); } } catch (e) {} return _set(k, v); }; } catch (e) {} window.__dwyUid = function () { if (uidCache) return uidCache; try { var u = JSON.parse(localStorage.getItem('user_info') || '{}'); return pick(u) || localStorage.getItem('user_id') || 'anonymous'; } catch (e) { return 'anonymous'; } }; window.__dwyUid(); function tip(t) { try { if (typeof window.v2tip === 'function') { window.v2tip(t); return; } } catch (e) {} try { var d = document.createElement('div'); d.style.cssText = 'position:fixed;left:50%;bottom:120px;transform:translateX(-50%);z-index:49000;background:#0f172a;color:#fff;font-size:13px;padding:9px 16px;border-radius:18px;max-width:84%;text-align:center;'; d.textContent = t; document.body.appendChild(d); setTimeout(function () { d.remove(); }, 2400); } catch (e) {} } function sendNow(text) { var inp = document.getElementById('chat-input'); if (inp) { inp.value = text; } if (typeof window.v2send === 'function') { try { window.v2send(text); return true; } catch (e) {} } if (typeof window.sendMessage === 'function') { setTimeout(function () { try { window.sendMessage(); } catch (e) {} }, 260); return true; } return false; } /* ---------- M3: 带 token 时用服务端权威身份纠正本地 uid ---------- */ var repaired = false; function repairIdentity() { if (repaired) return; try { var tok = localStorage.getItem('user_token') || localStorage.getItem('authToken') || localStorage.getItem('token'); if (!tok) return; repaired = true; fetch(API + '/api/auth/userinfo', { headers: { 'Authorization': 'Bearer ' + tok } }) .then(function (r) { return r.json(); }) .then(function (d) { var u = d && (d.user || d.data || d); if (!u || !(u.id || u.username)) return; var prev = {}; try { prev = JSON.parse(localStorage.getItem('user_info') || '{}') || {}; } catch (e) {} prev.id = u.id || u.username || prev.id; if (!prev.username) prev.username = u.username || prev.id; if (u.role) prev.role = u.role; localStorage.setItem('user_info', JSON.stringify(prev)); localStorage.setItem('user_id', prev.id); uidCache = prev.id; if (renderedFor && renderedFor !== prev.id) { renderedFor = ''; } tryRestore(); }).catch(function () { repaired = false; }); } catch (e) {} } /* ---------- M4: 会话历史回显(可重试, 不再一次性放弃) ---------- */ var histBusy = false, renderedFor = '', tries = 0; function doFetchHistory(uid) { histBusy = true; return fetch(API + '/ai-army/conversations/history?limit=30&user_id=' + encodeURIComponent(uid)) .then(function (r) { return r.json(); }) .then(function (d) { histBusy = false; var msgs = (d && d.data && d.data.messages) || []; renderedFor = uid; if (!msgs.length) return; var wrap = document.getElementById('ai-chat-wrapper') || document.querySelector('.chat-messages'); if (!wrap) return; renderMsgs(wrap, msgs); tip('已恢复最近 ' + msgs.length + ' 条对话'); }) .catch(function () { histBusy = false; }); } function renderMsgs(wrap, msgs) { if (typeof window.addMessageToChat === 'function') { msgs.forEach(function (m) { try { window.addMessageToChat(m.content, m.role === 'user' ? 'user' : 'ai'); } catch (e) {} }); return; } var esc = function (s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]; }); }; wrap.insertAdjacentHTML('afterbegin', msgs.map(function (m) { return '
'; }).join('')); } function tryRestore() { try { var uid = window.__dwyUid(); if (uid && uid !== 'anonymous' && renderedFor !== uid && !histBusy) { var wrap = document.getElementById('ai-chat-wrapper') || document.querySelector('.chat-messages'); if (wrap) { if (!wrap.querySelector('.message-bubble, .message-user, .message-ai, [data-role]')) { doFetchHistory(uid); } else { return; /* 用户已经开聊, 不覆盖 */ } } } if (renderedFor && renderedFor === window.__dwyUid()) return; if (++tries < 40) setTimeout(tryRestore, 1500); } catch (e) {} } var chipTries = 0; function patchChips() { var orig = window.v2chip; if (!orig) { chipTries++; if (chipTries < 40) return false; return true; } if (orig.__dwyP) return true; window.v2chip = function (act) { if (act === 'voice') { try { var SR = window.SpeechRecognition || window.webkitSpeechRecognition; if (!SR) { tip('当前浏览器不支持语音听写,可用输入法麦克风说话'); return; } var rec = new SR(); rec.lang = 'zh-CN'; rec.interimResults = false; rec.onresult = function (ev) { var txt = ev.results && ev.results[0] && ev.results[0][0] && ev.results[0][0].transcript; if (txt) { tip('🎤 已识别,正在发送…'); sendNow(txt); } else { tip('未识别到语音内容,请重试'); } }; rec.onerror = function (e) { tip('语音识别失败:' + ((e && e.error) || '')); }; tip('🎤 请说话…'); rec.start(); } catch (e) { tip('语音暂不可用,可用输入法麦克风'); } return; } if (act === 'call' || act === 'videocall') { var label = (act === 'call') ? '电话答疑' : '视频通话答疑'; tip('正在为你转接人工客服(' + label + ')…'); if (!sendNow('我需要人工客服协助(' + label + ')。请先了解我的问题,并按流程帮我转人工。')) { tip('请先在输入框写下你的问题并发送,我来转人工'); } return; } if (act === 'attach') { var t = '请帮我生成一份文档附件(内容与格式:Word / Excel / PPT)'; var i2 = document.getElementById('chat-input'); if (i2) { i2.value = t; try { i2.focus(); } catch (e) {} } tip('请输入要生成的附件内容与格式,然后点「发送」'); return; } return orig.apply(window, arguments); }; window.v2chip.__dwyP = true; return true; } function boot() { repairIdentity(); if (!patchChips()) { setTimeout(boot, 300); return; } tryRestore(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { setTimeout(boot, 200); }); } else { setTimeout(boot, 200); } setTimeout(boot, 3000); setTimeout(function () { repairIdentity(); }, 5000); })();