// ==UserScript== // @name 全能网页悬浮助手 (v4.0 彻底解决解析失败) // @namespace https://scriptcat.org/ // @version 4.0 // @description 彻底修复接口解析失败问题,支持日历、深度思考折叠、模型切换 // @author You // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @connect text.pollinations.ai // @connect * // @run-at document-idle // ==/UserScript== (function () { 'use strict'; if (window.top !== window.self) return; if (document.getElementById('cyber-assistant-host')) return; // ==================== 创建 Shadow DOM ==================== const host = document.createElement('div'); host.id = 'cyber-assistant-host'; host.style.cssText = 'position:fixed; top:0; left:0; width:0; height:0; z-index: 2147483647;'; document.documentElement.appendChild(host); const shadow = host.attachShadow({ mode: 'open' }); // ==================== 注入 CSS ==================== const style = document.createElement('style'); style.textContent = ` :host { all: initial; } * { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; user-select: none; } .hidden { opacity: 0 !important; pointer-events: none !important; } #ball-wrapper { position: fixed; bottom: 50px; right: 30px; display: flex; flex-direction: column; align-items: center; gap: 6px; z-index: 10000; cursor: grab; } #ball-wrapper:active { cursor: grabbing; } #ball-time { font-size: 12px; font-weight: bold; color: #00d2ff; font-family: monospace; background: rgba(16, 23, 38, 0.85); padding: 2px 8px; border-radius: 10px; border: 1px solid rgba(0, 210, 255, 0.3); pointer-events: none; box-shadow: 0 2px 10px rgba(0,0,0,0.3); } #float-ball { width: 50px; height: 50px; background: rgba(16, 23, 38, 0.85); backdrop-filter: blur(10px); border: 1px solid rgba(0, 210, 255, 0.4); border-radius: 50%; color: #00d2ff; font-size: 24px; display: flex; justify-content: center; align-items: center; box-shadow: 0 4px 15px rgba(0,0,0,0.5); transition: all 0.2s; pointer-events: none; } #ball-wrapper:hover #float-ball { transform: scale(1.05); box-shadow: 0 0 15px rgba(0, 210, 255, 0.6); } #panel { position: fixed; width: 340px; height: 560px; background: rgba(7, 11, 20, 0.9); backdrop-filter: blur(12px); border: 1px solid rgba(0, 210, 255, 0.3); border-radius: 16px; box-shadow: 0 10px 30px rgba(0,0,0,0.7); display: none; flex-direction: column; overflow: hidden; z-index: 9999; color: #e0e6ed; transition: opacity 0.3s; } #panel.open { display: flex; } .nav-tabs { display: flex; border-bottom: 1px solid rgba(0, 210, 255, 0.15); background: rgba(16, 23, 38, 0.5); } .nav-item { flex: 1; padding: 10px 0; text-align: center; font-size: 12px; cursor: pointer; color: #8a9bb8; transition: all 0.2s; } .nav-item.active { color: #00d2ff; border-bottom: 2px solid #00d2ff; background: rgba(0, 210, 255, 0.05); } .content-area { flex: 1; padding: 15px; overflow-y: auto; position: relative; } .tab-pane { display: none; height: 100%; flex-direction: column; } .tab-pane.active { display: flex; } input, select { width: 100%; background: rgba(16, 23, 38, 0.8); border: 1px solid rgba(0, 210, 255, 0.2); color: #e0e6ed; border-radius: 8px; padding: 8px 12px; outline: none; font-size: 13px; transition: border 0.2s; user-select: text; } input:focus, select:focus { border-color: #00d2ff; box-shadow: 0 0 8px rgba(0, 210, 255, 0.3); } select { cursor: pointer; appearance: none; background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%2300d2ff%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E"); background-repeat: no-repeat; background-position: right 10px top 50%; background-size: 10px auto; padding-right: 30px; } button { background: linear-gradient(135deg, #0055ff, #00d2ff); color: #fff; border: none; border-radius: 8px; padding: 8px 15px; cursor: pointer; font-weight: bold; font-size: 13px; box-shadow: 0 0 10px rgba(0, 210, 255, 0.4); transition: all 0.2s; } button:hover { box-shadow: 0 0 15px rgba(0, 210, 255, 0.7); transform: translateY(-1px); } /* 1. 时间 & 日历 */ .clock-display { text-align: center; font-size: 36px; font-weight: bold; color: #00d2ff; margin: 0 0 5px; text-shadow: 0 0 15px rgba(0,210,255,0.4); font-family: monospace; } .date-display { text-align: center; font-size: 12px; color: #8a9bb8; margin-bottom: 10px; } .calendar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; font-weight: bold; color: #00d2ff; font-size: 14px; } .calendar-btn { background: transparent; color: #00d2ff; border: none; cursor: pointer; font-size: 16px; padding: 0 10px; box-shadow: none; } .calendar-btn:hover { color: #fff; background: rgba(0,210,255,0.2); border-radius: 4px; } .calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; text-align: center; font-size: 12px; margin-bottom: 15px; } .cal-day-name { color: #8a9bb8; margin-bottom: 5px; } .cal-day { padding: 6px 0; border-radius: 4px; cursor: pointer; color: #e0e6ed; } .cal-day:hover { background: rgba(0,210,255,0.1); } .cal-day.other-month { color: #4a5b78; } .cal-day.today { background: #00d2ff; color: #000; font-weight: bold; box-shadow: 0 0 10px rgba(0,210,255,0.5); } .stopwatch-box { background: rgba(0,0,0,0.3); padding: 10px; border-radius: 12px; border: 1px solid rgba(0,255,204,0.2); margin-bottom: 15px; text-align: center; } .stopwatch-title { font-size: 12px; color: #00ffcc; margin-bottom: 5px; font-weight: bold; } .stopwatch-display { font-size: 28px; font-weight: bold; color: #00ffcc; font-family: monospace; text-shadow: 0 0 10px rgba(0,255,204,0.3); margin-bottom: 10px; } .sw-controls { display: flex; justify-content: center; gap: 10px; } .sw-controls button { background: transparent; color: #00ffcc; border: 1px solid #00ffcc; box-shadow: none; padding: 4px 12px; } .alarm-picker { display: flex; flex-direction: column; justify-content: center; align-items: center; margin-bottom: 10px; background: rgba(16,23,38,0.6); padding: 8px; border-radius: 12px; border: 1px solid rgba(0,210,255,0.1); } .picker-row { display: flex; align-items: center; gap: 10px; } .picker-col { display: flex; flex-direction: column; align-items: center; } .picker-colon { font-size: 20px; font-weight: bold; color: #00d2ff; } .time-num { font-size: 22px; font-weight: bold; color: #e0e6ed; font-family: monospace; margin: 4px 0; background: rgba(0,0,0,0.3); padding: 2px 8px; border-radius: 6px; width: 50px; text-align: center; cursor: ns-resize; } .btn-arrow { background: rgba(0, 210, 255, 0.1); color: #00d2ff; border: 1px solid rgba(0, 210, 255, 0.2); border-radius: 4px; padding: 4px 10px; cursor: pointer; font-size: 12px; box-shadow: none; } .btn-arrow:hover { background: #00d2ff; color: #000; } .btn-add-alarm { width: 100%; margin-bottom: 10px; padding: 8px; } .alarm-list { flex: 1; overflow-y: auto; } .alarm-item { display: flex; justify-content: space-between; background: rgba(16,23,38,0.6); padding: 6px 12px; border-radius: 6px; margin-bottom: 6px; border: 1px solid rgba(0,210,255,0.1); color: #e0e6ed; font-family: monospace; font-size: 14px;} .alarm-item button { padding: 2px 8px; font-size: 12px; background: #ff3366; box-shadow: none; } /* 2. 笔记 */ #note-textarea { flex: 1; resize: none; margin-bottom: 10px; background: rgba(16, 23, 38, 0.8); border: 1px solid rgba(0, 210, 255, 0.2); color: #e0e6ed; border-radius: 8px; padding: 10px; outline: none; font-size: 13px; user-select: text; } .note-actions { display: flex; justify-content: space-between; align-items: center; } .note-status { font-size: 11px; color: #00ffcc; transition: opacity 0.3s; opacity: 0; } #btn-export-note { padding: 6px 12px; font-size: 12px; background: transparent; border: 1px solid #00d2ff; color: #00d2ff; box-shadow: none; } /* 3. AI */ #ai-chat-box { flex: 1; overflow-y: auto; background: rgba(0,0,0,0.3); border-radius: 8px; padding: 10px; margin-bottom: 10px; display: flex; flex-direction: column; gap: 10px; } .msg { max-width: 90%; padding: 8px 12px; border-radius: 8px; font-size: 13px; line-height: 1.5; word-break: break-word; user-select: text; } .msg.user { align-self: flex-end; background: rgba(0, 210, 255, 0.2); border: 1px solid rgba(0, 210, 255, 0.3); } .msg.ai { align-self: flex-start; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255,255,255,0.1); width: 100%; } /* 深度思考折叠框 */ .think-box { background: rgba(0,0,0,0.2); border-left: 2px solid #8a9bb8; padding: 8px; margin-bottom: 8px; font-size: 12px; color: #8a9bb8; border-radius: 4px; } .think-title { cursor: pointer; user-select: none; display: flex; justify-content: space-between; font-weight: bold; margin-bottom: 4px; } .think-title:hover { color: #00d2ff; } .think-content { display: none; white-space: pre-wrap; font-family: monospace; margin-top: 4px; border-top: 1px solid rgba(255,255,255,0.05); padding-top: 4px;} .think-box.open .think-content { display: block; } .ai-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; } .btn-logout { font-size: 11px; padding: 2px 8px; background: transparent; color: #8a9bb8; border: 1px solid #8a9bb8; box-shadow: none; } .web-mode-tip { font-size: 11px; color: #00ffcc; background: rgba(0,255,204,0.1); padding: 4px 8px; border-radius: 4px; margin-bottom: 8px; text-align: center; border: 1px solid rgba(0,255,204,0.3); } .ai-toolbar { display: flex; gap: 8px; margin-bottom: 8px; } .ai-toolbar select { flex: 1; padding: 4px 8px; font-size: 11px; background-color: rgba(16, 23, 38, 0.9); } .ai-input-group { display: flex; gap: 8px; } .ai-config { display: flex; flex-direction: column; gap: 8px; } /* 4. 截图 */ .screenshot-btn-area { display: flex; flex-direction: column; gap: 15px; align-items: center; justify-content: center; height: 100%; } .screenshot-btn-area button { width: 80%; padding: 12px; font-size: 14px; } #btn-screenshot-save-as { background: transparent; border: 1px solid #00d2ff; color: #00d2ff; box-shadow: none; } ::-webkit-scrollbar { width: 4px; } ::-webkit-scrollbar-thumb { background: #00d2ff; border-radius: 2px; } `; shadow.appendChild(style); // ==================== HTML ==================== const ui = document.createElement('div'); ui.innerHTML = `
⚡
--:--
00:00:00
2023年01月01日
2023年 10月
⏱️ 正计时
00:00:00.00
支持长按/滚轮
08
:
00
已保存
AI 助手
🟢 已启用免费公共接口
你好!我是你的多功能助手。已经修复了网络错误。

截图保存

`; shadow.appendChild(ui); // ==================== 元素引用 ==================== const ballWrapper = shadow.getElementById('ball-wrapper'); const ballTime = shadow.getElementById('ball-time'); const panel = shadow.getElementById('panel'); const navItems = shadow.querySelectorAll('.nav-item'); const panes = shadow.querySelectorAll('.tab-pane'); // ==================== 拖拽与Tab ==================== const posKey = 'cyber_pos_' + location.hostname; const savedPos = GM_getValue(posKey, null); if (savedPos) { ballWrapper.style.left = savedPos.x + 'px'; ballWrapper.style.top = savedPos.y + 'px'; ballWrapper.style.right = 'auto'; ballWrapper.style.bottom = 'auto'; } let isDragging = false, hasDragged = false, startX, startY, initialX, initialY; ballWrapper.addEventListener('mousedown', (e) => { isDragging = true; hasDragged = false; startX = e.clientX; startY = e.clientY; const rect = ballWrapper.getBoundingClientRect(); initialX = rect.left; initialY = rect.top; ballWrapper.style.transition = 'none'; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); }); function onMouseMove(e) { if (!isDragging) return; const dx = e.clientX - startX, dy = e.clientY - startY; if (Math.abs(dx) > 3 || Math.abs(dy) > 3) hasDragged = true; let newX = Math.max(10, Math.min(window.innerWidth - 60, initialX + dx)); let newY = Math.max(10, Math.min(window.innerHeight - 80, initialY + dy)); ballWrapper.style.left = `${newX}px`; ballWrapper.style.top = `${newY}px`; if (panel.classList.contains('open')) positionPanel(newX, newY); } function onMouseUp() { isDragging = false; ballWrapper.style.transition = 'opacity 0.3s'; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); if (hasDragged) { const rect = ballWrapper.getBoundingClientRect(); GM_setValue(posKey, { x: rect.left, y: rect.top }); } } function positionPanel(ballX, ballY) { const panelW = 340, panelH = 560; let pLeft = ballX < window.innerWidth / 2 ? ballX + 60 : ballX - panelW - 10; let pTop = ballY > window.innerHeight - panelH ? window.innerHeight - panelH - 10 : ballY; panel.style.left = `${Math.max(10, pLeft)}px`; panel.style.top = `${Math.max(10, pTop)}px`; panel.style.right = 'auto'; panel.style.bottom = 'auto'; } ballWrapper.addEventListener('click', () => { if (hasDragged) return; if (panel.classList.contains('open')) panel.classList.remove('open'); else { const rect = ballWrapper.getBoundingClientRect(); positionPanel(rect.left, rect.top); panel.classList.add('open'); } }); navItems.forEach(item => { item.addEventListener('click', () => { navItems.forEach(i => i.classList.remove('active')); panes.forEach(p => p.classList.remove('active')); item.classList.add('active'); shadow.getElementById(`pane-${item.dataset.tab}`).classList.add('active'); }); }); // ==================== 1. 时钟与日历 ==================== const clockTime = shadow.getElementById('clock-time'); const clockDate = shadow.getElementById('clock-date'); setInterval(() => { const now = new Date(); const timeStr = now.toTimeString().split(' ')[0]; ballTime.textContent = timeStr.substring(0, 5); clockTime.textContent = timeStr; clockDate.textContent = now.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' }); }, 1000); let currentCalDate = new Date(); function renderCalendar() { const year = currentCalDate.getFullYear(); const month = currentCalDate.getMonth(); shadow.getElementById('cal-month-year').textContent = `${year}年 ${month + 1}月`; const firstDay = new Date(year, month, 1).getDay(); const daysInMonth = new Date(year, month + 1, 0).getDate(); const prevMonthDays = new Date(year, month, 0).getDate(); const grid = shadow.getElementById('cal-grid'); grid.innerHTML = ''; ['日', '一', '二', '三', '四', '五', '六'].forEach(d => { const el = document.createElement('div'); el.className = 'cal-day-name'; el.textContent = d; grid.appendChild(el); }); const today = new Date(); const isCurrentMonth = (today.getFullYear() === year && today.getMonth() === month); for (let i = firstDay - 1; i >= 0; i--) { const el = document.createElement('div'); el.className = 'cal-day other-month'; el.textContent = prevMonthDays - i; grid.appendChild(el); } for (let i = 1; i <= daysInMonth; i++) { const el = document.createElement('div'); el.className = 'cal-day'; el.textContent = i; if (isCurrentMonth && today.getDate() === i) el.classList.add('today'); grid.appendChild(el); } const totalCells = firstDay + daysInMonth; const nextDays = totalCells % 7 === 0 ? 0 : 7 - (totalCells % 7); for (let i = 1; i <= nextDays; i++) { const el = document.createElement('div'); el.className = 'cal-day other-month'; el.textContent = i; grid.appendChild(el); } } renderCalendar(); shadow.getElementById('cal-prev').onclick = () => { currentCalDate.setMonth(currentCalDate.getMonth() - 1); renderCalendar(); }; shadow.getElementById('cal-next').onclick = () => { currentCalDate.setMonth(currentCalDate.getMonth() + 1); renderCalendar(); }; shadow.getElementById('cal-today').onclick = () => { currentCalDate = new Date(); renderCalendar(); }; // 秒表 let swStartTime = 0, swElapsed = 0, swTimer = null, isSwRunning = false; const swDisplay = shadow.getElementById('sw-display'); shadow.getElementById('sw-btn-start').onclick = () => { const btn = shadow.getElementById('sw-btn-start'); if (isSwRunning) { isSwRunning = false; clearInterval(swTimer); btn.textContent = '继续'; btn.style.color = '#00d2ff'; } else { isSwRunning = true; swStartTime = Date.now() - swElapsed; swTimer = setInterval(() => { swElapsed = Date.now() - swStartTime; const s = Math.floor(swElapsed/1000); swDisplay.textContent = `${String(Math.floor(s/3600)).padStart(2,'0')}:${String(Math.floor((s%3600)/60)).padStart(2,'0')}:${String(s%60).padStart(2,'0')}.${String(Math.floor((swElapsed%1000)/10)).padStart(2,'0')}`; }, 31); btn.textContent = '暂停'; btn.style.color = '#ff3366'; } }; shadow.getElementById('sw-btn-reset').onclick = () => { isSwRunning = false; clearInterval(swTimer); swElapsed = 0; swDisplay.textContent = '00:00:00.00'; const btn = shadow.getElementById('sw-btn-start'); btn.textContent = '开始'; btn.style.color = '#00ffcc'; }; // 闹钟 let alHour = 8, alMin = 0; const elHour = shadow.getElementById('alarm-hour'), elMin = shadow.getElementById('alarm-min'); const upAlarm = () => { elHour.textContent = String(alHour).padStart(2, '0'); elMin.textContent = String(alMin).padStart(2, '0'); }; function bindStep(id, isUp, getV, setV, max) { const b = shadow.getElementById(id); let t, i; const act = () => { let v = getV() + (isUp ? 1 : -1); setV(v > max ? 0 : (v < 0 ? max : v)); upAlarm(); }; b.onmousedown = (e) => { e.preventDefault(); act(); t = setTimeout(() => { i = setInterval(act, 80); }, 300); }; b.onmouseup = b.onmouseleave = () => { clearTimeout(t); clearInterval(i); }; } bindStep('hour-up', 1, ()=>alHour, (v)=>alHour=v, 23); bindStep('hour-down', 0, ()=>alHour, (v)=>alHour=v, 23); bindStep('min-up', 1, ()=>alMin, (v)=>alMin=v, 59); bindStep('min-down', 0, ()=>alMin, (v)=>alMin=v, 59); elHour.onwheel = (e) => { e.preventDefault(); alHour = (alHour + (e.deltaY < 0 ? 1 : -1) + 24) % 24; upAlarm(); }; elMin.onwheel = (e) => { e.preventDefault(); alMin = (alMin + (e.deltaY < 0 ? 1 : -1) + 60) % 60; upAlarm(); }; let alarms = GM_getValue('cyber_alarms', []); setInterval(() => { const hm = new Date().toTimeString().substring(0, 5); if (alarms.includes(hm) && new Date().getSeconds() === 0) { const ac = new (window.AudioContext || window.webkitAudioContext)(); const osc = ac.createOscillator(); osc.connect(ac.destination); osc.frequency.value = 880; osc.start(); osc.stop(ac.currentTime + 1); alert(`⏰ 闹钟时间到!(${hm})`); } }, 1000); const renderAlarms = () => { const list = shadow.getElementById('alarm-list'); list.innerHTML = ''; alarms.sort().forEach(t => { const d = document.createElement('div'); d.className = 'alarm-item'; d.innerHTML = `⏰ ${t}`; d.querySelector('button').onclick = () => { alarms = alarms.filter(x => x !== t); GM_setValue('cyber_alarms', alarms); renderAlarms(); }; list.appendChild(d); }); }; shadow.getElementById('btn-add-alarm').onclick = () => { const v = `${String(alHour).padStart(2, '0')}:${String(alMin).padStart(2, '0')}`; if (!alarms.includes(v)) { alarms.push(v); GM_setValue('cyber_alarms', alarms); renderAlarms(); } }; renderAlarms(); // ==================== 2. 笔记 ==================== const noteTextarea = shadow.getElementById('note-textarea'); noteTextarea.value = GM_getValue('cyber_notes', ''); let nt; noteTextarea.oninput = () => { clearTimeout(nt); const s = shadow.getElementById('note-status'); s.style.opacity = '0'; nt = setTimeout(() => { GM_setValue('cyber_notes', noteTextarea.value); s.style.opacity = '1'; setTimeout(() => s.style.opacity = '0', 2000); }, 800); }; shadow.getElementById('btn-export-note').onclick = () => { if (!noteTextarea.value) return; const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([noteTextarea.value], {type: 'text/plain;charset=utf-8'})); a.download = `笔记_${Date.now()}.txt`; a.click(); }; // ==================== 3. AI 逻辑 ==================== const aiModeSelect = shadow.getElementById('ai-mode-select'); const aiKeyInput = shadow.getElementById('ai-key'), aiUrlInput = shadow.getElementById('ai-url'); const btnSaveAi = shadow.getElementById('btn-save-ai'), aiConfigPanel = shadow.getElementById('ai-config-panel'); const aiInputPanel = shadow.getElementById('ai-input-panel'), aiInput = shadow.getElementById('ai-input'); const btnSendAi = shadow.getElementById('btn-send-ai'), aiChatBox = shadow.getElementById('ai-chat-box'); const btnAiLogout = shadow.getElementById('btn-ai-logout'), webModeTip = shadow.getElementById('web-mode-tip'); const quickModelSelect = shadow.getElementById('quick-model-select'); let aiMode = GM_getValue('cyber_ai_mode', 'free'); let aiKey = GM_getValue('cyber_ai_key', ''); let aiUrl = GM_getValue('cyber_ai_url', ''); let aiModel = GM_getValue('cyber_ai_model', 'openai'); const SYSTEM_PROMPT = "你是一个乐于助人的AI助手,请始终使用简体中文回答用户的问题。"; function updateAiUI() { if (aiMode === 'free') { aiModeSelect.value = 'free'; aiKeyInput.style.display = 'none'; aiUrlInput.style.display = 'none'; webModeTip.textContent = '🟢 已启用免费公共接口'; webModeTip.style.display = 'block'; quickModelSelect.innerHTML = ` `; } else { aiModeSelect.value = 'custom'; aiKeyInput.style.display = 'block'; aiUrlInput.style.display = 'block'; aiKeyInput.value = aiKey; aiUrlInput.value = aiUrl; webModeTip.textContent = '🔵 正在使用自定义 API'; webModeTip.style.display = 'block'; quickModelSelect.innerHTML = ` `; } quickModelSelect.value = aiModel; if (quickModelSelect.selectedIndex === -1) { quickModelSelect.value = aiMode === 'free' ? 'openai' : 'deepseek-chat'; aiModel = quickModelSelect.value; GM_setValue('cyber_ai_model', aiModel); } } quickModelSelect.onchange = () => { aiModel = quickModelSelect.value; GM_setValue('cyber_ai_model', aiModel); addMessage(`已切换模型至: ${aiModel}`, 'ai'); }; aiModeSelect.onchange = () => { if (aiModeSelect.value === 'custom') { aiKeyInput.style.display = 'block'; aiUrlInput.style.display = 'block'; } else { aiKeyInput.style.display = 'none'; aiUrlInput.style.display = 'none'; } }; updateAiUI(); btnAiLogout.onclick = () => { if (aiConfigPanel.style.display === 'none') { aiConfigPanel.style.display = 'flex'; aiInputPanel.style.display = 'none'; btnAiLogout.textContent = '取消'; } else { aiConfigPanel.style.display = 'none'; aiInputPanel.style.display = 'flex'; btnAiLogout.textContent = '配置'; updateAiUI(); } }; btnSaveAi.onclick = () => { aiMode = aiModeSelect.value; GM_setValue('cyber_ai_mode', aiMode); if (aiMode === 'custom') { aiKey = aiKeyInput.value.trim(); aiUrl = aiUrlInput.value.trim(); if (!aiKey || !aiUrl) return alert('请填写完整 API 配置'); GM_setValue('cyber_ai_key', aiKey); GM_setValue('cyber_ai_url', aiUrl); } aiConfigPanel.style.display = 'none'; aiInputPanel.style.display = 'flex'; btnAiLogout.textContent = '配置'; updateAiUI(); addMessage('配置已保存。', 'ai'); }; function addMessage(text, type) { const div = document.createElement('div'); div.className = `msg ${type}`; div.textContent = text; aiChatBox.appendChild(div); aiChatBox.scrollTop = aiChatBox.scrollHeight; return div; } function parseAiResponse(text, container) { if (!text) { container.textContent = "AI没有返回内容。"; return; } const thinkRegex = /([\s\S]*?)<\/thought>/i; const match = text.match(thinkRegex); let finalAnswer = text; if (match) { const thinkContent = match[1].trim(); finalAnswer = text.replace(thinkRegex, '').trim(); container.innerHTML = ''; const thinkBox = document.createElement('div'); thinkBox.className = 'think-box'; thinkBox.innerHTML = `
🤔 深度思考过程 ▼
${thinkContent}
`; thinkBox.querySelector('.think-title').onclick = () => { thinkBox.classList.toggle('open'); }; container.appendChild(thinkBox); const answerDiv = document.createElement('div'); answerDiv.textContent = finalAnswer; container.appendChild(answerDiv); } else { container.textContent = finalAnswer; } } function sendAiMessage() { const text = aiInput.value.trim(); if (!text) return; addMessage(text, 'user'); aiInput.value = ''; const aiMsgDiv = addMessage('思考中...', 'ai'); if (aiMode === 'free') { // 稳定免费的 GET 接口 const fullPrompt = `${SYSTEM_PROMPT}\n用户:${text}\nAI:`; const url = `https://text.pollinations.ai/${encodeURIComponent(fullPrompt)}`; GM_xmlhttpRequest({ method: "GET", url: url, headers: { "User-Agent": navigator.userAgent, "Accept": "text/plain" }, onload: function(res) { if (res.status === 200) { let reply = res.responseText; if (reply.includes("AI:")) { reply = reply.split("AI:").pop().trim(); } parseAiResponse(reply, aiMsgDiv); } else { aiMsgDiv.textContent = `请求失败 (${res.status}),接口可能繁忙。如果一直失败,请在右上角配置中填入 DeepSeek 免费的 API Key。`; } aiChatBox.scrollTop = aiChatBox.scrollHeight; }, onerror: function() { aiMsgDiv.textContent = "网络请求失败,请检查你的网络环境或代理。"; } }); } else { GM_xmlhttpRequest({ method: "POST", url: aiUrl, headers: { "Content-Type": "application/json", "Authorization": `Bearer ${aiKey}` }, data: JSON.stringify({ model: aiModel, messages: [ { role: "system", content: SYSTEM_PROMPT }, { role: "user", content: text } ], stream: false }), onload: function(res) { if (res.status === 200) { try { const data = JSON.parse(res.responseText); const msg = data.choices[0].message; let reply = msg.content || ""; // 提取 DeepSeek R1 的思考过程 if (msg.reasoning_content) { reply = `${msg.reasoning_content}\n${reply}`; } parseAiResponse(reply, aiMsgDiv); } catch (e) { aiMsgDiv.textContent = "数据解析出错,请确保接口格式兼容 OpenAI 规范。"; } } else if (res.status === 401) { aiMsgDiv.textContent = "API Key 无效。"; } else { aiMsgDiv.textContent = `请求失败 (${res.status})。`; } aiChatBox.scrollTop = aiChatBox.scrollHeight; }, onerror: function() { aiMsgDiv.textContent = "网络请求失败。"; } }); } } btnSendAi.onclick = sendAiMessage; aiInput.onkeydown = (e) => { if (e.key === 'Enter') { e.preventDefault(); sendAiMessage(); } }; // ==================== 4. 截图 ==================== const btnScreenshot = shadow.getElementById('btn-screenshot'), btnScreenshotSaveAs = shadow.getElementById('btn-screenshot-save-as'); function loadAndShot(isSaveAs) { if (!document.getElementById('html2canvas-script')) { const s = document.createElement('script'); s.id = 'html2canvas-script'; s.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js'; s.onload = () => takeScreenshot(isSaveAs); document.body.appendChild(s); } else takeScreenshot(isSaveAs); } btnScreenshot.onclick = () => loadAndShot(false); btnScreenshotSaveAs.onclick = () => loadAndShot(true); async function takeScreenshot(isSaveAs) { const old1 = btnScreenshot.textContent, old2 = btnScreenshotSaveAs.textContent; btnScreenshot.disabled = btnScreenshotSaveAs.disabled = true; btnScreenshot.textContent = btnScreenshotSaveAs.textContent = '截图中...'; ballWrapper.classList.add('hidden'); panel.classList.add('hidden'); const restore = () => { ballWrapper.classList.remove('hidden'); panel.classList.remove('hidden'); btnScreenshot.textContent = old1; btnScreenshotSaveAs.textContent = old2; btnScreenshot.disabled = btnScreenshotSaveAs.disabled = false; }; const to = setTimeout(restore, 8000); setTimeout(async () => { try { if (!window.html2canvas) throw new Error("组件未加载"); const c = await window.html2canvas(document.body, { useCORS: true, ignoreElements: (el) => el.id === 'cyber-assistant-host' }); const fn = `screenshot_${Date.now()}.png`; if (isSaveAs && window.showSaveFilePicker) { try { const h = await window.showSaveFilePicker({ suggestedName: fn, types: [{ description: 'PNG', accept: {'image/png': ['.png']} }] }); const w = await h.createWritable(); const b = await new Promise(r => c.toBlob(r, 'image/png')); await w.write(b); await w.close(); } catch (e) { if (e.name !== 'AbortError') { const a = document.createElement('a'); a.download = fn; a.href = c.toDataURL(); a.click(); } } } else { const a = document.createElement('a'); a.download = fn; a.href = c.toDataURL(); a.click(); } } catch (e) {} finally { clearTimeout(to); restore(); } }, 300); } })();