From 96e3dd397ce54d3c70079a391a3b76fec11547c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Tue, 19 May 2026 18:48:17 +0800 Subject: [PATCH] Add files via upload --- web/static/css/style.css | 389 ++++++++++++++++++++++++++++++++++ web/static/i18n/en-US.json | 27 ++- web/static/i18n/zh-CN.json | 27 ++- web/static/js/settings.js | 26 +++ web/static/js/wechat-robot.js | 350 ++++++++++++++++++++++++++++++ web/templates/index.html | 68 ++++++ 6 files changed, 885 insertions(+), 2 deletions(-) create mode 100644 web/static/js/wechat-robot.js diff --git a/web/static/css/style.css b/web/static/css/style.css index 7f94cde1..89c04f27 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -20168,3 +20168,392 @@ button.chat-files-dropdown-item:hover:not(:disabled) { word-break: break-all; } +/* 微信 iLink 机器人 */ +.robot-wechat-card { + margin-bottom: 28px; + padding: 20px 22px; + border: 1px solid var(--border-color); + border-radius: 12px; + background: linear-gradient(145deg, var(--bg-primary) 0%, var(--bg-secondary) 100%); + box-shadow: var(--shadow-sm); +} + +.robot-wechat-card h4 { + margin: 0; + font-size: 1.05rem; +} + +.robot-wechat-header { + display: flex; + align-items: flex-start; + gap: 14px; + margin-bottom: 18px; + padding-bottom: 16px; + border-bottom: 1px solid var(--border-color); +} + +.robot-wechat-header-text { + flex: 1; + min-width: 0; +} + +.robot-wechat-subtitle { + margin: 4px 0 0; + font-size: 0.8125rem; + color: var(--text-secondary); + line-height: 1.45; +} + +.robot-wechat-badge { + flex-shrink: 0; + align-self: center; + padding: 4px 10px; + border-radius: 999px; + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.02em; +} + +.robot-wechat-badge--idle { + background: var(--bg-tertiary); + color: var(--text-secondary); + border: 1px solid var(--border-color); +} + +.robot-wechat-badge--bound { + background: rgba(40, 167, 69, 0.12); + color: var(--success-color); + border: 1px solid rgba(40, 167, 69, 0.35); +} + +.robot-wechat-badge--scanning { + background: rgba(0, 102, 255, 0.1); + color: var(--accent-color); + border: 1px solid rgba(0, 102, 255, 0.25); +} + +.robot-wechat-form { + display: flex; + flex-direction: column; + gap: 0; +} + +.robot-wechat-toolbar { + margin-bottom: 14px; +} + +.robot-wechat-action-row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 12px 16px; + margin-bottom: 4px; +} + +.robot-wechat-action-row .btn-primary { + min-width: 160px; + padding: 10px 20px; + border-radius: 8px; + font-weight: 500; +} + +.robot-wechat-card.is-bound .robot-wechat-action-row .btn-primary { + background: var(--bg-primary); + color: var(--accent-color); + border: 1px solid var(--accent-color); + box-shadow: none; +} + +.robot-wechat-card.is-bound .robot-wechat-action-row .btn-primary:hover { + background: rgba(0, 102, 255, 0.06); +} + +.robot-wechat-hint { + margin: 0; + flex: 1; + min-width: 200px; + font-size: 0.8125rem; + color: var(--text-secondary); + line-height: 1.45; +} + +.robot-wechat-card.is-bound .robot-wechat-hint { + display: none; +} + +.robot-wechat-panel { + margin-top: 16px; + padding: 20px; + border-radius: 10px; + background: var(--bg-tertiary); + border: 1px dashed var(--border-color); +} + +.robot-wechat-bound-panel { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 12px 8px; + gap: 10px; +} + +.robot-wechat-bound-icon { + width: 56px; + height: 56px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: rgba(40, 167, 69, 0.12); + color: var(--success-color); +} + +.robot-wechat-bound-msg { + margin: 0; + font-size: 1rem; + font-weight: 600; + color: var(--success-color); +} + +.robot-wechat-bound-id { + margin: 0; + max-width: 100%; + padding: 6px 12px; + font-size: 0.75rem; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + color: var(--text-secondary); + background: var(--bg-primary); + border-radius: 6px; + border: 1px solid var(--border-color); + word-break: break-all; +} + +.robot-wechat-scan-panel { + text-align: center; +} + +.robot-wechat-steps { + display: flex; + justify-content: center; + gap: 0; + margin: 0 0 20px; + padding: 0; + list-style: none; + font-size: 0.75rem; + color: var(--text-muted); +} + +.robot-wechat-step { + position: relative; + padding: 0 14px; + white-space: nowrap; +} + +.robot-wechat-step:not(:last-child)::after { + content: '›'; + position: absolute; + right: -2px; + color: var(--border-color); + font-weight: 600; +} + +.robot-wechat-step.is-active { + color: var(--accent-color); + font-weight: 600; +} + +.robot-wechat-step.is-done { + color: var(--success-color); +} + +.robot-wechat-qr-loading { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + padding: 24px; + font-size: 0.875rem; + color: var(--text-secondary); +} + +.robot-wechat-qr-loading[hidden] { + display: none !important; +} + +.robot-wechat-spinner { + width: 32px; + height: 32px; + border: 3px solid var(--border-color); + border-top-color: var(--accent-color); + border-radius: 50%; + animation: robot-wechat-spin 0.75s linear infinite; +} + +@keyframes robot-wechat-spin { + to { transform: rotate(360deg); } +} + +.robot-wechat-qr-frame { + position: relative; + display: inline-block; + margin: 0 auto 12px; + padding: 14px; + background: #fff; + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); +} + +.robot-wechat-qr-frame::before, +.robot-wechat-qr-frame::after { + content: ''; + position: absolute; + width: 18px; + height: 18px; + border: 2px solid var(--accent-color); + opacity: 0.5; + pointer-events: none; +} + +.robot-wechat-qr-frame::before { + top: 6px; + left: 6px; + border-right: none; + border-bottom: none; + border-radius: 4px 0 0 0; +} + +.robot-wechat-qr-frame::after { + bottom: 6px; + right: 6px; + border-left: none; + border-top: none; + border-radius: 0 0 4px 0; +} + +.robot-wechat-qr-img { + display: block; + width: 200px; + height: 200px; + border-radius: 4px; +} + +.robot-wechat-qr-img[hidden] { + display: none; +} + +.robot-wechat-qr-placeholder { + width: 200px; + height: 200px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-muted); +} + +.robot-wechat-qr-placeholder[hidden] { + display: none !important; +} + +.robot-wechat-qr-fallback { + margin: 0 0 8px; + font-size: 0.8125rem; +} + +.robot-wechat-qr-fallback a { + color: var(--accent-color); + text-decoration: none; +} + +.robot-wechat-qr-fallback a:hover { + text-decoration: underline; +} + +.robot-wechat-qr-status { + margin: 0; + font-size: 0.875rem; + color: var(--text-secondary); + line-height: 1.5; +} + +.robot-wechat-qr-status.is-success { + color: var(--accent-color); + font-weight: 500; +} + +.robot-wechat-qr-status.is-error { + color: var(--error-color); +} + +.robot-wechat-verify-wrap { + margin-top: 16px; + padding-top: 16px; + border-top: 1px solid var(--border-color); + text-align: left; + max-width: 320px; + margin-left: auto; + margin-right: auto; +} + +.robot-wechat-verify-wrap[hidden] { + display: none !important; +} + +.robot-wechat-verify-wrap label { + display: block; + margin-bottom: 8px; + font-size: 0.8125rem; + color: var(--text-secondary); +} + +.robot-wechat-verify-row { + display: flex; + gap: 8px; + align-items: stretch; +} + +.robot-wechat-verify-row input { + flex: 1; + min-width: 0; + padding: 10px 12px; + border: 1px solid var(--border-color); + border-radius: 8px; + font-size: 1.125rem; + letter-spacing: 0.2em; + text-align: center; + font-variant-numeric: tabular-nums; +} + +.robot-wechat-advanced { + margin-top: 16px; + padding-top: 12px; + border-top: 1px solid var(--border-color); +} + +.robot-wechat-advanced summary { + cursor: pointer; + font-size: 0.8125rem; + color: var(--text-secondary); + user-select: none; +} + +.robot-wechat-advanced[open] summary { + margin-bottom: 12px; + color: var(--text-primary); +} + +.settings-collapsible { + margin-top: 12px; +} + +.settings-collapsible summary { + cursor: pointer; + color: var(--text-muted, #8b9cb3); + font-size: 13px; + user-select: none; +} + +.settings-collapsible[open] summary { + margin-bottom: 12px; +} + diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index 3d8ffd12..e5efed04 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -836,7 +836,32 @@ }, "robots": { "title": "Bot settings", - "description": "Configure WeCom, DingTalk and Lark bots so you can chat with CyberStrikeAI on your phone without opening the web UI.", + "description": "Configure WeChat (iLink), WeCom, DingTalk and Lark bots so you can chat with CyberStrikeAI on your phone without opening the web UI.", + "wechat": { + "title": "WeChat / iLink", + "subtitle": "Bind personal WeChat via QR code and chat with CyberStrikeAI on your phone", + "statusIdle": "Not bound", + "statusBound": "Connected", + "statusScanning": "Binding…", + "step1": "Generate QR", + "step2": "Scan in WeChat", + "step3": "Confirm", + "enabled": "Enable WeChat bot", + "bindButton": "Generate QR code and bind", + "bindHint": "Scan with WeChat to confirm; settings are saved automatically.", + "qrLoading": "Generating QR code…", + "verifyCodeLabel": "Code on your phone (only if WeChat asks)", + "rebindButton": "Re-bind", + "boundBotId": "Bound Bot ID: ", + "verifyCodeSubmit": "Submit", + "advanced": "Advanced settings", + "baseUrl": "API Base URL", + "botType": "Bot Type", + "botAgent": "Bot Agent", + "ilinkBotId": "iLink Bot ID (filled after bind)", + "boundSuccess": "Binding successful. WeChat bot is enabled.", + "openLink": "QR not showing? Open link in WeChat on your phone" + }, "wecom": { "title": "WeCom", "enabled": "Enable WeCom bot", diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index d543c2b2..7c920f94 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -825,7 +825,32 @@ }, "robots": { "title": "机器人设置", - "description": "配置企业微信、钉钉、飞书等机器人,在手机端直接与 CyberStrikeAI 对话,无需在服务器上打开网页。", + "description": "配置微信、企业微信、钉钉、飞书等机器人,在手机端直接与 CyberStrikeAI 对话,无需在服务器上打开网页。", + "wechat": { + "title": "微信 / iLink", + "subtitle": "扫码绑定个人微信,在手机端直接与 CyberStrikeAI 对话", + "statusIdle": "未绑定", + "statusBound": "已连接", + "statusScanning": "绑定中…", + "step1": "生成二维码", + "step2": "微信扫码", + "step3": "确认绑定", + "enabled": "启用微信机器人", + "bindButton": "生成二维码并绑定", + "bindHint": "用微信扫码确认后会自动保存并启用。", + "qrLoading": "正在生成二维码…", + "verifyCodeLabel": "手机显示的数字(仅部分账号需要)", + "rebindButton": "重新绑定", + "boundBotId": "已绑定 Bot ID:", + "verifyCodeSubmit": "提交", + "advanced": "高级设置", + "baseUrl": "API Base URL", + "botType": "Bot Type", + "botAgent": "Bot Agent", + "ilinkBotId": "iLink Bot ID(绑定后自动填充)", + "boundSuccess": "绑定成功,微信机器人已启用。", + "openLink": "无法显示二维码?点击用手机微信打开链接" + }, "wecom": { "title": "企业微信", "enabled": "启用企业微信机器人", diff --git a/web/static/js/settings.js b/web/static/js/settings.js index ec47e9b2..47c4b766 100644 --- a/web/static/js/settings.js +++ b/web/static/js/settings.js @@ -339,9 +339,23 @@ async function loadConfig(loadTools = true) { // 填充机器人配置 const robots = currentConfig.robots || {}; + const wechat = robots.wechat || {}; const wecom = robots.wecom || {}; const dingtalk = robots.dingtalk || {}; const lark = robots.lark || {}; + const wechatEnabled = document.getElementById('robot-wechat-enabled'); + if (wechatEnabled) wechatEnabled.checked = wechat.enabled === true; + const wechatBase = document.getElementById('robot-wechat-base-url'); + if (wechatBase) wechatBase.value = wechat.base_url || 'https://ilinkai.weixin.qq.com'; + const wechatBotType = document.getElementById('robot-wechat-bot-type'); + if (wechatBotType) wechatBotType.value = wechat.bot_type || '3'; + const wechatBotAgent = document.getElementById('robot-wechat-bot-agent'); + if (wechatBotAgent) wechatBotAgent.value = wechat.bot_agent || 'CyberStrikeAI/1.0'; + const wechatBotId = document.getElementById('robot-wechat-ilink-bot-id'); + if (wechatBotId) wechatBotId.value = wechat.ilink_bot_id || ''; + if (typeof refreshWechatRobotBoundUI === 'function') { + refreshWechatRobotBoundUI({ ...wechat, bound: !!(wechat.bot_token && wechat.ilink_bot_id) }); + } const wecomEnabled = document.getElementById('robot-wecom-enabled'); if (wecomEnabled) wecomEnabled.checked = wecom.enabled === true; const wecomToken = document.getElementById('robot-wecom-token'); @@ -1129,6 +1143,18 @@ async function applySettings() { }, robots: { ...(prevRobots.session && typeof prevRobots.session === 'object' ? { session: prevRobots.session } : {}), + wechat: { + enabled: document.getElementById('robot-wechat-enabled')?.checked === true, + base_url: document.getElementById('robot-wechat-base-url')?.value.trim() || 'https://ilinkai.weixin.qq.com', + bot_type: document.getElementById('robot-wechat-bot-type')?.value.trim() || '3', + bot_agent: document.getElementById('robot-wechat-bot-agent')?.value.trim() || 'CyberStrikeAI/1.0', + ilink_bot_id: document.getElementById('robot-wechat-ilink-bot-id')?.value.trim() || (prevRobots.wechat && prevRobots.wechat.ilink_bot_id) || '', + ...(prevRobots.wechat && typeof prevRobots.wechat === 'object' ? { + bot_token: prevRobots.wechat.bot_token || '', + ilink_user_id: prevRobots.wechat.ilink_user_id || '', + get_updates_buf: prevRobots.wechat.get_updates_buf || '' + } : {}) + }, wecom: { enabled: document.getElementById('robot-wecom-enabled')?.checked === true, token: document.getElementById('robot-wecom-token')?.value.trim() || '', diff --git a/web/static/js/wechat-robot.js b/web/static/js/wechat-robot.js new file mode 100644 index 00000000..2446f317 --- /dev/null +++ b/web/static/js/wechat-robot.js @@ -0,0 +1,350 @@ +// 微信 iLink 机器人:扫码绑定与状态轮询 + +let wechatBindSessionKey = null; +let wechatBindPollTimer = null; + +function wechatT(key, fallback) { + return typeof t === 'function' ? t(key) : fallback; +} + +function getWechatCard() { + return document.getElementById('robot-wechat-subsection'); +} + +function setWechatBadge(mode) { + const badge = document.getElementById('robot-wechat-status-badge'); + if (!badge) return; + badge.classList.remove('robot-wechat-badge--idle', 'robot-wechat-badge--bound', 'robot-wechat-badge--scanning'); + if (mode === 'bound') { + badge.classList.add('robot-wechat-badge--bound'); + badge.textContent = wechatT('settings.robots.wechat.statusBound', '已连接'); + } else if (mode === 'scanning') { + badge.classList.add('robot-wechat-badge--scanning'); + badge.textContent = wechatT('settings.robots.wechat.statusScanning', '绑定中…'); + } else { + badge.classList.add('robot-wechat-badge--idle'); + badge.textContent = wechatT('settings.robots.wechat.statusIdle', '未绑定'); + } +} + +function setWechatCardBound(isBound) { + const card = getWechatCard(); + if (card) card.classList.toggle('is-bound', !!isBound); +} + +function updateWechatSteps(phase) { + const steps = document.querySelectorAll('.robot-wechat-step'); + if (!steps.length) return; + const order = ['generate', 'scan', 'confirm']; + const idx = order.indexOf(phase); + steps.forEach((el, i) => { + el.classList.remove('is-active', 'is-done'); + if (idx < 0) { + if (i === 0) el.classList.add('is-active'); + } else if (i < idx) { + el.classList.add('is-done'); + } else if (i === idx) { + el.classList.add('is-active'); + } + }); +} + +function ensureWechatSteps() { + const panel = document.getElementById('robot-wechat-scan-panel'); + if (!panel || panel.querySelector('.robot-wechat-steps')) return; + const ol = document.createElement('ol'); + ol.className = 'robot-wechat-steps'; + ol.innerHTML = ` +
  • ${wechatT('settings.robots.wechat.step1', '生成二维码')}
  • +
  • ${wechatT('settings.robots.wechat.step2', '微信扫码')}
  • +
  • ${wechatT('settings.robots.wechat.step3', '确认绑定')}
  • `; + panel.insertBefore(ol, panel.firstChild); +} + +function ensureWechatQrFrame() { + const img = document.getElementById('robot-wechat-qr-img'); + if (!img || img.parentElement?.classList.contains('robot-wechat-qr-frame')) return; + const frame = document.createElement('div'); + frame.className = 'robot-wechat-qr-frame'; + img.parentNode.insertBefore(frame, img); + frame.appendChild(img); + let ph = document.getElementById('robot-wechat-qr-placeholder'); + if (!ph) { + ph = document.createElement('div'); + ph.id = 'robot-wechat-qr-placeholder'; + ph.className = 'robot-wechat-qr-placeholder'; + ph.setAttribute('aria-hidden', 'true'); + ph.innerHTML = ''; + frame.appendChild(ph); + } else { + frame.appendChild(ph); + } +} + +function stopWechatBindPoll() { + if (wechatBindPollTimer) { + clearTimeout(wechatBindPollTimer); + wechatBindPollTimer = null; + } +} + +/** 已绑定:仅展示成功状态,不显示二维码/配对码 */ +function showWechatBoundUI(wechat) { + const wc = wechat || {}; + const wrap = document.getElementById('robot-wechat-qr-wrap'); + const boundPanel = document.getElementById('robot-wechat-bound-panel'); + const scanPanel = document.getElementById('robot-wechat-scan-panel'); + const boundId = document.getElementById('robot-wechat-bound-id'); + const btn = document.getElementById('robot-wechat-bind-btn'); + + stopWechatBindPoll(); + wechatBindSessionKey = null; + setWechatBadge('bound'); + setWechatCardBound(true); + + if (wrap) wrap.hidden = false; + if (boundPanel) boundPanel.hidden = false; + if (scanPanel) scanPanel.hidden = true; + + const verifyWrap = document.getElementById('robot-wechat-verify-wrap'); + if (verifyWrap) verifyWrap.hidden = true; + + const img = document.getElementById('robot-wechat-qr-img'); + const ph = document.getElementById('robot-wechat-qr-placeholder'); + if (img) { + img.removeAttribute('src'); + img.hidden = true; + } + if (ph) ph.hidden = false; + + if (boundId) { + const id = wc.ilink_bot_id || document.getElementById('robot-wechat-ilink-bot-id')?.value?.trim() || ''; + if (id) { + boundId.textContent = wechatT('settings.robots.wechat.boundBotId', '已绑定 Bot ID:') + id; + boundId.hidden = false; + } else { + boundId.textContent = ''; + boundId.hidden = true; + } + } + + if (btn) { + btn.textContent = wechatT('settings.robots.wechat.rebindButton', '重新绑定'); + } +} + +/** 扫码绑定进行中 */ +function showWechatScanUI() { + const wrap = document.getElementById('robot-wechat-qr-wrap'); + const boundPanel = document.getElementById('robot-wechat-bound-panel'); + const scanPanel = document.getElementById('robot-wechat-scan-panel'); + const btn = document.getElementById('robot-wechat-bind-btn'); + + setWechatBadge('scanning'); + setWechatCardBound(false); + ensureWechatSteps(); + updateWechatSteps('generate'); + + if (wrap) wrap.hidden = false; + if (boundPanel) boundPanel.hidden = true; + if (scanPanel) scanPanel.hidden = false; + + const verifyWrap = document.getElementById('robot-wechat-verify-wrap'); + if (verifyWrap) verifyWrap.hidden = true; + + const verifyInput = document.getElementById('robot-wechat-verify-code'); + if (verifyInput) verifyInput.value = ''; + + if (btn) { + btn.textContent = wechatT('settings.robots.wechat.bindButton', '生成二维码并绑定'); + } +} + +/** 未绑定且未在扫码:隐藏面板 */ +function hideWechatQrWrap() { + const wrap = document.getElementById('robot-wechat-qr-wrap'); + if (wrap) wrap.hidden = true; + setWechatBadge('idle'); + setWechatCardBound(false); +} + +function setWechatQrImage(data) { + ensureWechatQrFrame(); + const img = document.getElementById('robot-wechat-qr-img'); + const ph = document.getElementById('robot-wechat-qr-placeholder'); + const linkEl = document.getElementById('robot-wechat-qr-link'); + const openUrl = data.qrcode_open_url || data.qrcode_img_url || ''; + + if (img) { + if (data.qrcode_image_data_url) { + img.onload = () => { + img.hidden = false; + if (ph) ph.hidden = true; + }; + img.onerror = () => { + img.hidden = true; + if (ph) ph.hidden = false; + }; + img.src = data.qrcode_image_data_url; + updateWechatSteps('scan'); + } else { + img.removeAttribute('src'); + img.hidden = true; + if (ph) ph.hidden = false; + } + } + if (linkEl) { + if (openUrl) { + linkEl.href = openUrl; + linkEl.hidden = false; + } else { + linkEl.hidden = true; + } + } +} + +function setWechatQrStatus(text, isError) { + const el = document.getElementById('robot-wechat-qr-status'); + if (!el) return; + el.textContent = text || ''; + el.classList.toggle('is-error', !!isError); + el.classList.toggle('is-success', !isError && !!text); +} + +async function startWechatRobotBind() { + stopWechatBindPoll(); + wechatBindSessionKey = null; + showWechatScanUI(); + ensureWechatQrFrame(); + + const loading = document.getElementById('robot-wechat-qr-loading'); + const img = document.getElementById('robot-wechat-qr-img'); + const ph = document.getElementById('robot-wechat-qr-placeholder'); + const btn = document.getElementById('robot-wechat-bind-btn'); + + if (loading) loading.hidden = false; + if (img) { + img.removeAttribute('src'); + img.hidden = true; + } + if (ph) ph.hidden = false; + setWechatQrStatus('', false); + if (btn) btn.disabled = true; + + const botType = document.getElementById('robot-wechat-bot-type')?.value.trim() || '3'; + + try { + const res = await apiFetch('/api/robot/wechat/qrcode', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ bot_type: botType }) + }); + const data = await res.json(); + if (!res.ok) { + throw new Error(data.error || data.message || '获取二维码失败'); + } + wechatBindSessionKey = data.session_key; + setWechatQrImage(data); + setWechatQrStatus(data.message || '请使用微信扫描二维码', false); + pollWechatBindStatus(); + } catch (e) { + setWechatQrStatus(e.message || String(e), true); + setWechatBadge('idle'); + } finally { + if (loading) loading.hidden = true; + if (btn) btn.disabled = false; + } +} + +async function pollWechatBindStatus() { + if (!wechatBindSessionKey) return; + + try { + const url = `/api/robot/wechat/qrcode/status?session_key=${encodeURIComponent(wechatBindSessionKey)}`; + const res = await apiFetch(url, { method: 'GET' }); + const data = await res.json(); + if (!res.ok) { + throw new Error(data.error || '轮询失败'); + } + + const verifyWrap = document.getElementById('robot-wechat-verify-wrap'); + + switch (data.status) { + case 'confirmed': + stopWechatBindPoll(); + updateWechatSteps('confirm'); + document.getElementById('robot-wechat-enabled').checked = true; + if (data.ilink_bot_id) { + const idEl = document.getElementById('robot-wechat-ilink-bot-id'); + if (idEl) idEl.value = data.ilink_bot_id; + } + if (typeof loadConfig === 'function') { + await loadConfig(false); + } else { + showWechatBoundUI({ + ilink_bot_id: data.ilink_bot_id, + bound: true + }); + } + return; + case 'need_verifycode': + updateWechatSteps('scan'); + if (verifyWrap) verifyWrap.hidden = false; + setWechatQrStatus(data.message || '请输入手机微信显示的数字', false); + break; + case 'scaned': + updateWechatSteps('confirm'); + if (verifyWrap) verifyWrap.hidden = true; + setWechatQrStatus('已扫码,请在手机上确认…', false); + break; + case 'binded_redirect': + stopWechatBindPoll(); + showWechatBoundUI({ bound: true }); + return; + case 'expired': + setWechatQrStatus('二维码已过期,请重新点击「生成二维码并绑定」', true); + setWechatBadge('scanning'); + stopWechatBindPoll(); + return; + default: + if (verifyWrap) verifyWrap.hidden = true; + break; + } + } catch (e) { + setWechatQrStatus(e.message || String(e), true); + } + + wechatBindPollTimer = setTimeout(pollWechatBindStatus, 1500); +} + +async function submitWechatVerifyCode() { + const code = document.getElementById('robot-wechat-verify-code')?.value.trim(); + if (!code || !wechatBindSessionKey) return; + try { + const res = await apiFetch('/api/robot/wechat/qrcode/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ session_key: wechatBindSessionKey, verify_code: code }) + }); + const data = await res.json(); + if (!res.ok) throw new Error(data.error || '提交失败'); + setWechatQrStatus(data.message || '已提交配对码,等待确认…', false); + pollWechatBindStatus(); + } catch (e) { + setWechatQrStatus(e.message || String(e), true); + } +} + +function refreshWechatRobotBoundUI(wechat) { + const wc = wechat || {}; + const isBound = wc.bound || (wc.bot_token && wc.ilink_bot_id) || !!(wc.ilink_bot_id && wc.enabled); + if (isBound) { + showWechatBoundUI(wc); + } else { + hideWechatQrWrap(); + const btn = document.getElementById('robot-wechat-bind-btn'); + if (btn) { + btn.textContent = wechatT('settings.robots.wechat.bindButton', '生成二维码并绑定'); + } + } +} diff --git a/web/templates/index.html b/web/templates/index.html index 2c3c5cbc..52ff8e40 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -2379,6 +2379,73 @@

    配置企业微信、钉钉、飞书等机器人,在手机端直接与 CyberStrikeAI 对话,无需在服务器上打开网页。

    + +
    +
    +
    +

    微信 / iLink

    +

    扫码绑定个人微信,在手机端直接与 CyberStrikeAI 对话

    +
    + 未绑定 +
    +
    +
    + +
    +
    + +

    用微信扫码确认后会自动保存并启用。

    +
    + +
    + 高级设置 +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    +

    企业微信

    @@ -3548,6 +3615,7 @@ +