// ==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日
`;
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);
}
})();