// ==UserScript== // @name PTA Auto Solver // @namespace https://pintia.cn/ // @version 1.8.0 // @description 轻量、清晰的 PTA AI 辅助答题脚本 // @author NeuronCState // @match https://pintia.cn/* // @homepageURL https://neuroncstate.github.io/PTA-Auto-Solver/ // @supportURL https://github.com/NeuronCState/PTA-Auto-Solver/issues // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @connect api.deepseek.com // @connect api.xiaomimimo.com // @connect opencode.ai // @connect 127.0.0.1 // @connect localhost // @run-at document-idle // @license All Rights Reserved // ==/UserScript== (function () { 'use strict'; var STORAGE_KEY = 'pta-auto-solver-v1'; var PROVIDERS = { deepseek: { name: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1', auth: 'bearer', supportsImages: true, hint: '全能快速模型', desc: '全能快速模型:官方 API 直连,代码生成与逻辑推导准确迅速,支持视觉图片输入。' }, mimo: { name: 'MiMo', baseUrl: 'https://api.xiaomimimo.com/v1', auth: 'api-key', supportsImages: true, hint: '全能性价比模型', desc: '全能性价比模型:小米官方高性价比推理模型,长上下文与多模态表现优异,默认开启图片。' }, opencode_zen: { name: 'Opencode Zen', baseUrl: 'https://opencode.ai/zen/v1', auth: 'bearer', supportsImages: true, hint: '免费模型', desc: '免费模型:Opencode 官方网关,内置免费模型(*-free、big-pickle)置顶展示。' }, opencode: { name: 'OpenCode Free', baseUrl: 'http://127.0.0.1:8788/v1', auth: 'bearer', supportsImages: false, optionalKey: true, hint: '本地免费模型', desc: '本地免费模型:本地代理(127.0.0.1:8788),自动发现本地 Free 模型,无需配置外网 Key。' } }; var LANG_NAMES = { c: 'C', cpp: 'C++', python: 'Python', java: 'Java', pascal: 'Pascal' }; var QUESTION_TYPES = { '1': 'judge', '2': 'choice', '3': 'multi_choice', '4': 'fill', '5': 'program_fill', '6': 'function', '7': 'programming' }; var state = { running: false, provider: 'deepseek', configs: { deepseek: { apiKey: '', model: '', models: [], modelMeta: {}, modelError: '', images: false, maxTokens: 2048, thinking: 2 }, mimo: { apiKey: '', model: '', models: [], modelMeta: {}, modelError: '', images: true, maxTokens: 2048, thinking: 2 }, opencode_zen: { apiKey: '', model: '', models: [], modelMeta: {}, modelError: '', images: false, maxTokens: 2048, thinking: 2 }, opencode: { apiKey: '', model: '', models: [], modelMeta: {}, modelError: '', images: false, maxTokens: 2048, thinking: 2 } }, skipAnswered: true, continuous: true, language: 'auto', theme: 'light' }; var localRuntime = { available: false, checking: false, baseUrl: PROVIDERS.opencode.baseUrl }; var localMonitorTimer = null; function readJson(key, fallback) { try { var value = localStorage.getItem(key); return value ? JSON.parse(value) : fallback; } catch (e) { return fallback; } } function saveState() { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch (e) {} } function loadState() { var saved = readJson(STORAGE_KEY, null); if (saved) { // 1.7.0 迁移:openrouter -> opencode_zen if (saved.provider === 'openrouter') saved.provider = 'opencode_zen'; if (saved.configs && saved.configs.openrouter && !saved.configs.opencode_zen) { saved.configs.opencode_zen = saved.configs.openrouter; delete saved.configs.openrouter; } state.provider = ['deepseek', 'mimo', 'opencode_zen', 'opencode'].indexOf(saved.provider) >= 0 ? saved.provider : 'deepseek'; state.configs = Object.assign(state.configs, saved.configs || {}); state.configs.deepseek = Object.assign({ apiKey: '', model: '', models: [], modelMeta: {}, modelError: '', images: false, maxTokens: 2048, thinking: 2 }, state.configs.deepseek || {}); state.configs.mimo = Object.assign({ apiKey: '', model: '', models: [], modelMeta: {}, modelError: '', images: true, maxTokens: 2048, thinking: 2 }, state.configs.mimo || {}); state.configs.opencode_zen = Object.assign({ apiKey: '', model: '', models: [], modelMeta: {}, modelError: '', images: false, maxTokens: 2048, thinking: 2 }, state.configs.opencode_zen || {}); state.configs.opencode = Object.assign({ apiKey: '', model: '', models: [], modelMeta: {}, modelError: '', images: false, maxTokens: 2048, thinking: 2 }, state.configs.opencode || {}); state.skipAnswered = saved.skipAnswered !== false; state.continuous = saved.continuous !== false; state.language = ['auto', 'c', 'cpp', 'python', 'java', 'pascal'].indexOf(saved.language) >= 0 ? saved.language : 'auto'; state.theme = saved.theme === 'dark' ? 'dark' : 'light'; return; } // 一次性兼容旧版本的 AI 配置。 var legacy = readJson('_ptaSettings', null) || readJson('_ptaDsSettings', null); if (legacy) { state.configs.deepseek.apiKey = legacy.key || legacy.apiKey || ''; state.configs.deepseek.model = legacy.model || ''; if (legacy.provider && /mimo/i.test(legacy.provider)) state.provider = 'mimo'; } saveState(); } var THINKING_LABELS = { 0: '关闭 (Off)', 1: '低 (Low · 快速)', 2: '中 (Medium · 推荐)', 3: '高 (High · 深度)' }; function formatTokens(val) { if (!val || val <= 0) return '0'; if (val >= 1024) { var k = val / 1024; return (k % 1 === 0 ? k : k.toFixed(1)) + 'K'; } return String(val); } function resolveModelContextLength(provider, modelId, apiContextLength) { if (apiContextLength && apiContextLength > 2048) return apiContextLength; if (!modelId) return 131072; var id = String(modelId).toLowerCase(); // 1. DeepSeek 官方规范 if (provider === 'deepseek' || id.indexOf('deepseek') >= 0) { if (/v4|flash/i.test(id)) return 1048576; // DeepSeek V4 / Flash: 1M if (/chat|reasoner|r1|v3/i.test(id)) return 65536; // DeepSeek V3 / R1: 64K } // 2. 小米 MiMo 官方规范 if (provider === 'mimo' || id.indexOf('mimo') >= 0) { if (/v2\.5|v2-pro/i.test(id)) return 1048576; // MiMo v2.5 / v2-pro: 1M if (/omni/i.test(id)) return 262144; // MiMo v2-omni: 256K if (/flash/i.test(id)) return 131072; // MiMo v2-flash: 128K } // 3. 通用模型名称规格推断(Opencode Zen / OpenCode Free / 开源模型) if (/1m|1000k/i.test(id)) return 1048576; if (/256k/i.test(id)) return 262144; if (/200k|claude/i.test(id)) return 200000; if (/gemini/i.test(id)) return 1048576; if (/128k|gpt-4o|o1|o3|qwen|llama/i.test(id)) return 131072; if (/64k/i.test(id)) return 65536; if (/32k/i.test(id)) return 32768; return 131072; } var ALL_TOKEN_TIERS = [ { val: 2048, label: '2K' }, { val: 8192, label: '8K' }, { val: 16384, label: '16K' }, { val: 32768, label: '32K' }, { val: 65536, label: '64K' }, { val: 131072, label: '128K' }, { val: 200000, label: '200K' }, { val: 262144, label: '256K' }, { val: 500000, label: '500K' }, { val: 1048576, label: '1M' } ]; function getAvailableTiers(maxContext) { var cap = Number(maxContext) || 131072; var filtered = ALL_TOKEN_TIERS.filter(function (t) { return t.val <= cap; }); if (!filtered.length) filtered = [ALL_TOKEN_TIERS[0]]; return filtered; } function findClosestTierIndex(tiers, val) { if (!tiers || !tiers.length) return 0; var target = Number(val) || 2048; var bestIdx = 0, bestDiff = Infinity; for (var i = 0; i < tiers.length; i++) { var diff = Math.abs(tiers[i].val - target); if (diff < bestDiff) { bestDiff = diff; bestIdx = i; } } return bestIdx; } function renderMaxTokensScale(tiers, activeIdx) { var scale = document.getElementById('pta-max-tokens-scale'); if (!scale || !tiers || !tiers.length) return; var total = tiers.length; var html = ''; tiers.forEach(function (tier, i) { var leftPct = total > 1 ? (i / (total - 1)) * 100 : 0; var isActive = i === activeIdx; var desc = ''; if (i === 0) desc = '建议最小'; else if (tier.val === 131072) desc = (i === total - 1) ? '建议 maxtoken / 上限' : '建议 maxtoken'; else if (i === total - 1) desc = '模型上限'; html += '
' + '' + tier.label + '' + (desc ? '' + desc + '' : '') + '
'; }); scale.innerHTML = html; scale.querySelectorAll('.max-token-step').forEach(function (item) { item.addEventListener('click', function () { var idx = Number(this.dataset.index); var input = document.getElementById('pta-max-tokens'); if (input) { input.value = idx; input.dispatchEvent(new Event('input', { bubbles: true })); } }); }); } function sleep(ms) { return new Promise(function (resolve) { setTimeout(resolve, ms); }); } function queryAll(selector, root) { var result = []; root = root || document; try { result = Array.prototype.slice.call(root.querySelectorAll(selector)); } catch (e) {} var nodes = []; try { nodes = Array.prototype.slice.call(root.querySelectorAll('*')); } catch (e2) {} nodes.forEach(function (node) { if (node.shadowRoot) result = result.concat(queryAll(selector, node.shadowRoot)); }); return result; } function textOf(el) { if (!el) return ''; return String(el.innerText || el.textContent || '').replace(/\u200b/g, '').replace(/\n{3,}/g, '\n\n').trim(); } function cleanText(value) { return String(value || '') .replace(/复制内容|格式|全屏|收起▾?|展开/g, ' ') .replace(/\[?\s*C\+\+\s*\]?/g, '') .replace(/[ \t]+\n/g, '\n') .replace(/\n{3,}/g, '\n\n') .trim(); } function nearestProblem(el) { var current = el; while (current && current !== document.body) { var cls = current.className && String(current.className); var text = textOf(current); if (current.classList && ( current.classList.contains('pc-x') || current.classList.contains('space-y-4') || /question|problem|exam-item/i.test(cls || '') ) && text.length > 10) return current; current = current.parentElement; } return el && (el.closest && el.closest('section, form')) || el && el.parentElement || document.body; } function getEditorDoc(editor) { var content = editor && editor.querySelector('.cm-content'); if (!content) return ''; try { var tile = content.cmTile; if (tile && tile.view && tile.view.state && tile.view.state.doc) { return tile.view.state.doc.sliceString(0, tile.view.state.doc.length); } } catch (e) {} return textOf(content); } function editorEmpty(editor) { return getEditorDoc(editor).trim().length < 2; } function editorForInput(input) { var current = input; while (current && current !== document.body) { if (current.classList && current.classList.contains('cm-editor')) return current; current = current.parentElement; } return null; } function editableEditor(preferEmpty) { var editors = queryAll('.cm-editor'); for (var i = 0; i < editors.length; i++) { var parentText = String((editors[i].parentElement && editors[i].parentElement.className) || ''); if (/readonly|readOnly/i.test(parentText)) continue; if (!preferEmpty || editorEmpty(editors[i])) return editors[i]; } return null; } function writeEditor(editor, code) { if (!editor || !code) return false; var content = editor.querySelector('.cm-content') || editor; var views = []; try { if (content.cmTile && content.cmTile.view) views.push(content.cmTile.view); queryAll('*', editor).forEach(function (el) { var view = el._view || el._cmView || el.__view; if (view && view.dispatch && view.state) views.push(view); }); } catch (e) {} for (var i = 0; i < views.length; i++) { try { views[i].dispatch({ changes: { from: 0, to: views[i].state.doc.length, insert: code }, selection: { anchor: code.length } }); return true; } catch (e2) {} } try { if (typeof GM_setClipboard === 'function') GM_setClipboard(code); if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(code); } catch (e3) {} return false; } function writeInput(input, value) { if (!input) return false; try { var setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set; setter.call(input, value); } catch (e) { input.value = value; } input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); input.dispatchEvent(new Event('blur', { bubbles: true })); return true; } function detectType() { var match = location.pathname.match(/\/exam\/problems\/type\/(\d+)/); if (match) return QUESTION_TYPES[match[1]] || 'unknown'; var body = textOf(document.body); if (/程序设计|编程题/.test(body) && queryAll('.cm-editor').length) return 'programming'; return 'unknown'; } function detectLanguage(code) { var value = ((code || '') + ' ' + document.title + ' ' + location.href).toLowerCase(); if (/public\s+class|system\.out|import\s+java\./.test(value)) return 'java'; if (/def\s+\w+\s*\(|print\s*\(|import\s+\w+|__name__/.test(value)) return 'python'; if (/cout|cin|iostream|std::|vector\s*<|c\+\+|\bcpp\b/.test(value)) return 'cpp'; if (/pascal|delphi/.test(value)) return 'pascal'; return 'c'; } function resolveLanguage(code, preferred) { var selected = preferred || state.language; return selected && selected !== 'auto' ? selected : detectLanguage(code || ''); } function languageLabel(code) { return LANG_NAMES[resolveLanguage('', code)] || 'C'; } function extractOptions(problem) { var labels = queryAll('label', problem).filter(function (label) { return !!label.querySelector('input[type="radio"], input[type="checkbox"]'); }); var options = labels.map(function (label) { var value = cleanText(textOf(label)); var match = value.match(/^\s*([A-F]|T|F|正确|错误)[.)、::\s]/i); return { label: match ? match[1].toUpperCase() : '', text: value, input: label.querySelector('input[type="radio"], input[type="checkbox"]') }; }).filter(function (option) { return option.text; }); return options.length ? options : null; } function problemImages(problem) { return queryAll('img', problem).map(function (img) { return img.currentSrc || img.src || img.getAttribute('data-src') || ''; }).filter(function (src, index, list) { return src && list.indexOf(src) === index; }).slice(0, 6); } function questionText(problem) { var clone = problem.cloneNode(true); queryAll('label, input, button, .cm-editor', clone).forEach(function (el) { el.remove(); }); return cleanText(textOf(clone)).slice(0, 9000); } function problemCodeBlocks(problem) { return queryAll('.cm-editor', problem).map(function (editor) { return cleanText(getEditorDoc(editor) || textOf(editor)); }).filter(Boolean); } function sectionCode(problem, startPattern, endPattern) { var nodes = Array.prototype.slice.call(problem.querySelectorAll('h1,h2,h3,h4,.cm-editor')); var active = false, result = []; nodes.forEach(function (node) { var isHeading = /^H[1-4]$/.test(node.tagName); if (isHeading) { var heading = cleanText(textOf(node)); if (active && endPattern && endPattern.test(heading)) active = false; if (startPattern.test(heading)) active = true; return; } if (active && node.classList && node.classList.contains('cm-editor')) { var code = cleanText(getEditorDoc(node) || textOf(node)); if (code) result.push(code); } }); return result; } function beforeFirstSection(text) { var match = String(text || '').search(/函数接口定义[::]|裁判测试程序样例[::]|输入格式[::]|输出格式[::]|样例输入|输入样例|Sample\s+Input/i); return cleanText(match >= 0 ? text.slice(0, match) : text).slice(0, 6000); } function findSection(text, headers, ends) { var start = -1; headers.some(function (header) { var match = text.match(header); if (match && (start < 0 || match.index < start)) { start = match.index + match[0].length; return true; } return false; }); if (start < 0) return ''; var end = text.length; ends.forEach(function (endPattern) { var match = new RegExp(endPattern.source, endPattern.flags.replace('g', '')).exec(text.slice(start)); if (match && start + match.index < end) end = start + match.index; }); return cleanText(text.slice(start, end)).slice(0, 1600); } function extractProgramming(problem) { var raw = questionText(problem); var ends = [/输入格式[::]/, /输出格式[::]/, /样例输入[::]/, /样例输出[::]/, /Sample\s+Input[::]/i, /Sample\s+Output[::]/i, /提示[::]/]; var allCode = problemCodeBlocks(problem); var sampleInputCodes = sectionCode(problem, /样例输入|输入样例|Sample\s+Input/i, /样例输出|输出样例|Sample\s+Output/i); var sampleOutputCodes = sectionCode(problem, /样例输出|输出样例|Sample\s+Output/i, /提示|样例输入|输入样例|Sample\s+Input/i); var sampleCodes = sampleInputCodes.concat(sampleOutputCodes); var frameworkCodes = allCode.filter(function (code) { return sampleCodes.indexOf(code) < 0; }); var q = { desc: findSection(raw, [/^题目[::]/m, /^问题[::]/m], ends) || beforeFirstSection(raw), input: findSection(raw, [/输入格式[::]/, /输入描述[::]/, /Input\s+Format[::]/i], ends), output: findSection(raw, [/输出格式[::]/, /输出描述[::]/, /Output\s+Format[::]/i], ends), sampleInput: sampleInputCodes.join('\n\n') || findSection(raw, [/样例输入\s*\d*[::]?\s*\n/, /输入样例\s*\d*[::]?\s*\n/, /Sample\s+Input\s*\d*[::]?\s*\n/i], [/样例输出[::]/, /输出样例[::]/, /Sample\s+Output[::]/i].concat(ends)), sampleOutput: sampleOutputCodes.join('\n\n') || findSection(raw, [/样例输出\s*\d*[::]?\s*\n/, /输出样例\s*\d*[::]?\s*\n/, /Sample\s+Output\s*\d*[::]?\s*\n/i], ends), frameworkCode: frameworkCodes[0] || '', referenceCode: frameworkCodes.slice(1, 3).join('\n\n'), images: queryAll('img', problem).map(function (img) { return img.currentSrc || img.src; }).filter(Boolean) }; return q; } function extractFunction(problem) { var raw = questionText(problem); var codes = sectionCode(problem, /函数接口定义|函数定义|Function\s+Definition/i, /输入样例|样例输入|Sample\s+Input/i); if (!codes.length) codes = problemCodeBlocks(problem); return { desc: beforeFirstSection(raw), interfaceCode: codes[0] || '', supportCode: codes.slice(1).join('\n\n'), sampleInput: sectionCode(problem, /样例输入|输入样例|Sample\s+Input/i, /样例输出|输出样例|Sample\s+Output/i).join('\n\n'), sampleOutput: sectionCode(problem, /样例输出|输出样例|Sample\s+Output/i, /提示|样例输入|输入样例|Sample\s+Input/i).join('\n\n'), images: queryAll('img', problem).map(function (img) { return img.currentSrc || img.src; }).filter(Boolean) }; } function collectGroups(filter) { var inputs = queryAll('input[type="radio"], input[type="checkbox"]').filter(function (input) { return !(input.closest && input.closest('#pta-root')) && (!filter || filter(input)); }); var groups = []; inputs.forEach(function (input) { var problem = nearestProblem(input); var existing = groups.find(function (group) { return group.problem === problem; }); if (existing) existing.inputs.push(input); else groups.push({ problem: problem, inputs: [input], done: false }); }); return groups.filter(function (group) { return group.inputs.length > 0; }); } function selected(group) { return group.inputs.some(function (input) { return input.checked; }); } function parseAnswer(value) { var lines = String(value || '').split(/\r?\n/).map(function (line) { return line.trim(); }).filter(Boolean); return lines.length > 1 ? lines[lines.length - 1] : (lines[0] || ''); } function normalizeAnswer(value, preserveMultiline) { var answer = String(value || '').trim(); var fenced = answer.match(/^```[^\n]*\n?([\s\S]*?)\n?```$/); if (fenced) answer = fenced[1].trim(); if (!preserveMultiline) answer = parseAnswer(answer); return answer.replace(/^```[a-z]*\s*/i, '').replace(/\s*```$/, '').trim(); } function writeChoice(problem, rawAnswer, multiple) { var answer = normalizeAnswer(rawAnswer); var options = extractOptions(problem); if (!options) return false; var lower = answer.toLowerCase(); var isTrue = /^(true|正确|对|yes|t|1)$/.test(lower); var isFalse = /^(false|错误|错|no|f|0)$/.test(lower); var wanted = []; if (isTrue || isFalse) { wanted = [isTrue ? 'T' : 'F']; } else { var chars = answer.toUpperCase().match(/[A-F]/g) || []; wanted = multiple ? chars : chars.slice(-1); } var changed = false; options.forEach(function (option) { var label = option.label || (option.text.match(/^\s*([A-F])/i) || [])[1]; if (label && wanted.indexOf(label.toUpperCase()) >= 0 && option.input) { option.input.click(); changed = true; } }); if (!changed && options.length === 2 && (isTrue || isFalse)) { var fallback = options[isTrue ? 0 : 1]; if (fallback && fallback.input) { fallback.input.click(); changed = true; } } return changed; } function groupFillInputs() { var inputs = queryAll('input').filter(function (input) { return !/hidden|radio|checkbox|button|submit|file|password|image/i.test(input.type || '') && !editorForInput(input); }); var groups = []; inputs.forEach(function (input) { var problem = nearestProblem(input); var existing = groups.find(function (group) { return group.problem === problem; }); if (existing) existing.inputs.push(input); else groups.push({ problem: problem, inputs: [input] }); }); return groups; } function groupProgramFills() { var inputs = queryAll('.cm-widgetBuffer + span input'); var groups = []; inputs.forEach(function (input) { var editor = editorForInput(input); var existing = groups.find(function (group) { return group.editor === editor; }); if (existing) existing.inputs.push(input); else groups.push({ editor: editor, inputs: [input], problem: nearestProblem(input) }); }); return groups; } function promptFor(type, data, thinking) { var language = languageLabel(data.language || state.language || 'auto'); var rule = { judge: '只输出一行 true 或 false。', choice: '只输出一个选项字母,例如 B。', multi_choice: '只输出正确选项字母拼接,例如 ACD。', fill: '只输出答案;多个空用英文竖线 | 分隔,不要解释。', program_fill: '严格按空的顺序输出答案,用英文竖线 | 分隔,不要解释。', function: '只输出可直接粘贴的函数代码,不要 markdown 代码块、解释或 main。', programming: '只输出完整、可运行的程序代码,不要 markdown 代码块、解释或注释。' }[type] || '只输出最终答案,不要解释。'; var parts = ['【编程语言】' + language, '【题干】\n' + (data.description || '')]; if (data.options && data.options.length) parts.push('【选项】\n' + data.options.map(function (option) { return (option.label || '') + '. ' + option.text; }).join('\n')); if (data.interfaceCode) parts.push('【函数接口】\n' + data.interfaceCode); if (data.frameworkCode) parts.push('【代码框架】\n' + data.frameworkCode); if (data.supportCode) parts.push('【类型与裁判参考代码】\n' + data.supportCode); if (data.referenceCode) parts.push('【参考代码】\n' + data.referenceCode); if (data.code) parts.push('【当前代码】\n' + data.code); if (data.input) parts.push('【输入格式】\n' + data.input); if (data.output) parts.push('【输出格式】\n' + data.output); if (data.sampleInput) parts.push('【样例输入】\n' + data.sampleInput); if (data.sampleOutput) parts.push('【样例输出】\n' + data.sampleOutput); if (data.blankCount) parts.push('【空数量】' + data.blankCount); parts.push('【输出要求】' + rule); var thinkingRules = { 0: '无需长篇思考,直接输出最终答案,严禁输出任何分析或思考过程。', 1: '快速思考,简要推理后直接输出答案。', 2: '适度思考,在内部严谨推导后输出最终答案,不要输出分析过程。', 3: '深度严谨思考,仔细验证所有边界条件和特殊情况后输出最终答案,不要输出分析过程。' }; var tRule = thinkingRules[thinking !== undefined ? thinking : 2] || thinkingRules[2]; parts.push('【思考约束】' + tRule); parts.push('【答题约束】只依据以上题干、接口和代码上下文作答;不要重复题目,不要输出分析过程。'); return parts.join('\n\n'); } function baseUrl(provider) { var config = state.configs[provider]; if (provider === 'mimo' && config && /^tp-/i.test(config.apiKey || '')) return 'https://token-plan-cn.xiaomimimo.com/v1'; if (provider === 'opencode') return localRuntime.baseUrl; return PROVIDERS[provider].baseUrl; } function endpoint(provider) { return baseUrl(provider) + '/chat/completions'; } function modelsEndpoint(provider) { return baseUrl(provider) + '/models'; } function authHeaders(provider, apiKey, method) { var headers = method === 'GET' ? {} : { 'Content-Type': 'application/json' }; if (apiKey) { if (PROVIDERS[provider].auth === 'api-key') headers['api-key'] = apiKey; else headers.Authorization = 'Bearer ' + apiKey; } return headers; } function providerReady(provider) { var config = state.configs[provider]; return !!(config && (PROVIDERS[provider].optionalKey ? localRuntime.available : config.apiKey)); } function probeLocalOpenCode(host) { return new Promise(function (resolve) { GM_xmlhttpRequest({ method: 'GET', url: 'http://' + host + ':8788/health', timeout: 1800, onload: function (response) { resolve(response.status >= 200 && response.status < 300 ? 'http://' + host + ':8788/v1' : ''); }, onerror: function () { resolve(''); }, ontimeout: function () { resolve(''); }, onabort: function () { resolve(''); } }); }); } function detectOpenCode(silent) { if (localRuntime.checking) return Promise.resolve(localRuntime.available); var wasAvailable = localRuntime.available; localRuntime.checking = true; syncUI(); return probeLocalOpenCode('127.0.0.1').then(function (url) { return url ? url : probeLocalOpenCode('localhost'); }).then(function (url) { localRuntime.checking = false; localRuntime.available = !!url; if (url) localRuntime.baseUrl = url; if (!url) { localRuntime.baseUrl = PROVIDERS.opencode.baseUrl; if (state.provider === 'opencode') { state.configs.opencode.models = []; state.configs.opencode.model = ''; } } syncUI(); if (url && !wasAvailable) { if (!silent) { toast('已检测到 OpenCode Free 本地代理'); log('OpenCode Free 已连接:' + url, 'success'); } if (state.provider === 'opencode') loadModels('opencode', true); } return localRuntime.available; }); } function apiError(prefix, status, data) { var serverMessage = (data && data.error && (data.error.message || data.error.code)) || (data && data.message) || '请求失败'; if (status === 401 || status === 403) return prefix + ':API Key 无效或无权限(HTTP ' + status + ')'; if (status === 402) return prefix + ':账户余额不足(HTTP 402)'; if (status === 429) return prefix + ':请求过于频繁(HTTP 429)'; return prefix + ' HTTP ' + status + ':' + serverMessage; } function parseJsonResponse(response, label) { var raw = String(response && response.responseText || '').replace(/^\uFEFF/, '').trim(); try { return JSON.parse(raw || '{}'); } catch (e) { var status = response && response.status ? '(HTTP ' + response.status + ')' : ''; var preview = raw.replace(/\s+/g, ' ').slice(0, 180); throw new Error(PROVIDERS[state.provider].name + ' ' + label + '不是有效 JSON' + status + (preview ? ':' + preview : ':空响应')); } } function requestModels(provider) { var config = state.configs[provider]; return new Promise(function (resolve, reject) { if (!providerReady(provider)) { reject(new Error(PROVIDERS[provider].optionalKey ? '未检测到 OpenCode Free 本地代理(127.0.0.1:8788)' : '请先填写 ' + PROVIDERS[provider].name + ' API Key')); return; } GM_xmlhttpRequest({ method: 'GET', url: modelsEndpoint(provider), headers: authHeaders(provider, config.apiKey, 'GET'), timeout: 30000, onload: function (response) { var data; try { data = parseJsonResponse(response, '模型列表响应'); } catch (error) { reject(error); return; } if (response.status < 200 || response.status >= 300) { reject(new Error(apiError('获取模型失败', response.status, data))); return; } var items = (Array.isArray(data.data) ? data.data : []).filter(function (item) { if (!item || !item.id || /asr|tts|voiceclone|voicedesign|embedding/i.test(item.id)) return false; var outputModalities = item.architecture && item.architecture.output_modalities; return !Array.isArray(outputModalities) || outputModalities.indexOf('text') >= 0; }); if (provider === 'opencode_zen') items.sort(function (a, b) { return Number(isFreeModel(b)) - Number(isFreeModel(a)); }); var seen = {}; var models = items.map(function (item) { return item.id; }).filter(function (id) { if (seen[id]) return false; seen[id] = true; return true; }); if (!models.length) { reject(new Error('接口没有返回可用于答题的模型')); return; } var meta = {}; items.forEach(function (item) { var rawCtx = Number( item.context_length || item.context_window || item.max_model_len || item.max_context_length || (item.top_provider && item.top_provider.context_length) || (item.per_request_limits && item.per_request_limits.max_tokens) || 0 ) || null; var ctxLen = resolveModelContextLength(provider, item.id, rawCtx); meta[item.id] = { free: provider === 'opencode_zen' && isFreeModel(item), images: provider === 'opencode_zen' && supportsImageInput(item), contextLength: ctxLen }; }); resolve({ ids: models, meta: meta }); }, onerror: function () { reject(new Error('获取模型失败:网络请求失败,请检查网络、脚本连接权限或 API Key')); }, ontimeout: function () { reject(new Error('获取模型失败:请求超时(30 秒)')); }, onabort: function () { reject(new Error('获取模型失败:请求被中止')); } }); }); } function isFreeModel(model) { if (!model) return false; var id = String(model.id || ''); if (/-free$/i.test(id) || id === 'big-pickle' || /:(free)$/i.test(id) || id === 'openrouter/free') return true; var pricing = model.pricing || {}; var fields = ['prompt', 'completion', 'request', 'image']; return fields.some(function (field) { return pricing[field] !== undefined; }) && fields.every(function (field) { return pricing[field] === undefined || Number(pricing[field]) === 0; }); } function supportsImageInput(model) { var modalities = model && model.architecture && model.architecture.input_modalities; return Array.isArray(modalities) && modalities.indexOf('image') >= 0; } function modelSupportsVision(provider, modelId, modelMeta) { if (!modelId) return false; var id = String(modelId).toLowerCase(); if (provider === 'deepseek') { return /v4|flash|vision/i.test(id) && !/chat|reasoner/i.test(id); } if (provider === 'mimo') { return /v2|omni|pro|flash/i.test(id); } if (provider === 'opencode_zen') { if (modelMeta && modelMeta.images !== undefined) return !!modelMeta.images; return /vision|vl|gemini|gpt-4|claude|omni/i.test(id); } return false; } function requestAI(messages, maxTokens, thinking) { var provider = PROVIDERS[state.provider]; var config = state.configs[state.provider]; return new Promise(function (resolve, reject) { if (!providerReady(state.provider)) { reject(new Error(provider.optionalKey ? '未检测到 OpenCode Free 本地代理(127.0.0.1:8788)' : '请先配置 ' + provider.name + ' API Key')); return; } if (!config.model) { reject(new Error('请先获取并选择一个模型')); return; } var tVal = thinking !== undefined ? thinking : (config.thinking !== undefined ? config.thinking : 2); var body = { model: config.model, messages: messages, temperature: 0, max_tokens: maxTokens || config.maxTokens || 2048, stream: false }; if (tVal !== undefined && tVal !== null) { var effortMap = { 0: 'none', 1: 'low', 2: 'medium', 3: 'high' }; var effort = effortMap[tVal] || 'medium'; if (tVal === 0) { body.thinking = { type: 'disabled' }; body.reasoning = { effort: 'none' }; body.reasoning_effort = 'low'; } else { body.thinking = { type: 'enabled' }; body.reasoning = { effort: effort }; body.reasoning_effort = effort; } } var headers = authHeaders(state.provider, config.apiKey, 'POST'); GM_xmlhttpRequest({ method: 'POST', url: endpoint(state.provider), headers: headers, data: JSON.stringify(body), timeout: 120000, onload: function (response) { var data; try { data = parseJsonResponse(response, 'AI 响应'); } catch (error) { reject(error); return; } if (response.status < 200 || response.status >= 300) { reject(new Error(apiError('AI 请求失败', response.status, data))); return; } var content = data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content; if (Array.isArray(content)) content = content.map(function (item) { return item.text || ''; }).join(''); if (!content) { reject(new Error(data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.reasoning_content ? 'AI 返回为空:本次只返回了推理内容,建议在设置中调大 Max Token 或降低思考强度' : 'AI 返回为空:请检查模型是否支持当前请求')); return; } resolve(String(content).trim()); }, onerror: function () { reject(new Error('AI 请求失败:网络请求失败,请检查网络、脚本连接权限或 API Key')); }, ontimeout: function () { reject(new Error('AI 请求失败:请求超时(120 秒)')); }, onabort: function () { reject(new Error('AI 请求失败:请求被中止')); } }); }); } function solveOne(type, data) { var provider = PROVIDERS[state.provider], config = state.configs[state.provider]; var thinking = config.thinking !== undefined ? Number(config.thinking) : 2; var userMax = config.maxTokens || 2048; var isCode = /programming|function/.test(type); var maxTokens = isCode ? userMax : Math.min(userMax, Math.max(2048, Math.floor(userMax / 2))); var system = '你是 PTA 编程题答题助手。先在内部完成严谨分析,最终严格遵守用户的输出格式。'; if (thinking === 0) { system = '你是 PTA 答题助手。直接分析并给出最精简最终答案,严禁输出任何思考或推理过程,严格遵守输出格式。'; } else if (thinking === 3) { system = '你是 PTA 编程题答题助手。先在内部进行全面深度的推导与多重边界验证,最终严格遵守用户的输出格式。'; } var userContent = promptFor(type, data, thinking); var canSendImages = provider.supportsImages && config.images && data.images && data.images.length && modelSupportsVision(state.provider, config.model, config.modelMeta && config.modelMeta[config.model]); if (canSendImages) { userContent = [{ type: 'text', text: userContent }].concat(data.images.map(function (src) { return { type: 'image_url', image_url: { url: src } }; })); } return requestAI([{ role: 'system', content: system }, { role: 'user', content: userContent }], maxTokens, thinking); } function log(message, level) { var box = document.getElementById('pta-log'); if (box) { var item = document.createElement('div'); item.className = 'pta-log-item ' + (level || 'info'); item.innerHTML = '' + escapeHtml(message) + ''; box.appendChild(item); box.scrollTop = box.scrollHeight; var count = document.getElementById('pta-log-count'); if (count) count.textContent = box.children.length; } console.log('[PTA Auto Solver] ' + message); } function escapeHtml(value) { return String(value).replace(/[&<>"']/g, function (char) { return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[char]; }); } function isContinuous() { var input = document.getElementById('pta-continuous'); return input ? input.checked : state.continuous; } function isSkipAnswered() { var input = document.getElementById('pta-skip'); return input ? input.checked : state.skipAnswered; } function setProgress(label, current, total) { var title = document.getElementById('pta-progress-title'); var count = document.getElementById('pta-progress-count'); var fill = document.getElementById('pta-progress-fill'); if (title) title.textContent = label; if (count) count.textContent = total ? current + ' / ' + total : '准备中'; if (fill) fill.style.width = total ? Math.min(100, current / total * 100) + '%' : '8%'; } function nextButton() { return queryAll('button').find(function (button) { return /下一题|下一道|next/i.test(textOf(button)) && !button.disabled; }); } function goNext() { var button = nextButton(); if (button) { button.click(); return true; } return false; } function lastPage() { return !nextButton() || /完成考试|提交试卷|考试结束|查看成绩/.test(textOf(document.body)); } function questionProgress() { var typeMatch = location.pathname.match(/\/exam\/problems\/type\/(\d+)/); var currentMatch = location.href.match(/[?&]problemSetProblemId=([^&#]+)/); var currentId = currentMatch ? decodeURIComponent(currentMatch[1]) : ''; var ids = queryAll('a').map(function (link) { var href = link.href || link.getAttribute('href') || ''; var match = href.match(/\/exam\/problems\/type\/(\d+)[?&]problemSetProblemId=([^&#]+)/); return match && (!typeMatch || match[1] === typeMatch[1]) ? decodeURIComponent(match[2]) : ''; }).filter(Boolean).filter(function (id, index, list) { return list.indexOf(id) === index; }); var current = currentId ? ids.indexOf(currentId) + 1 : 0; if (current > 0 && ids.length > 0) return { current: current, total: ids.length }; var summary = textOf(document.body).match(/(?:函数题|编程题|多文件编程题)\s*(\d+)\s*\/\s*(\d+)/); return summary ? { current: Number(summary[1]), total: Number(summary[2]) } : null; } function setQuestionProgress(label, fallbackCurrent, fallbackTotal) { var progress = questionProgress(); setProgress(label, progress ? progress.current : (fallbackCurrent || 0), progress ? progress.total : (fallbackTotal || 0)); } function submitButton() { return queryAll('button').find(function (button) { return !button.disabled && /提交本题作答|提交代码/.test(textOf(button)); }); } function lastSubmissionButton() { return queryAll('button').find(function (button) { return !button.disabled && /查看上次提交/.test(textOf(button)); }); } function readSubmissionScore() { var body = textOf(document.body); if (!/提交结果/.test(body)) return null; var match = body.match(/(?:分数|得分)\s*[::]?\s*(\d+(?:\.\d+)?)\s*\/\s*(\d+(?:\.\d+)?)/); if (!match) return null; return { score: Number(match[1]), total: Number(match[2]) }; } function closeSubmissionResult() { if (!/提交结果/.test(textOf(document.body))) return false; var close = queryAll('button').find(function (button) { return /^(确认|关闭|返回)$/.test(textOf(button)); }); if (close) { close.click(); return true; } return false; } async function waitForSubmitButton(timeout) { var deadline = Date.now() + (timeout || 6000); while (Date.now() < deadline && state.running) { var button = submitButton(); if (button) return button; await sleep(250); } return null; } async function waitForSubmissionScore(timeout) { var started = Date.now(); var deadline = started + (timeout || 45000); var openedResult = false; while (Date.now() < deadline && state.running) { var score = readSubmissionScore(); if (score) return score; if (!openedResult && Date.now() - started >= 1800) { var view = lastSubmissionButton(); if (view) { view.click(); openedResult = true; await sleep(500); } } await sleep(800); } return null; } async function solveChoicePage(type) { var multiple = type === 'multi_choice'; var groups = collectGroups(function (input) { var label = input.closest('label'); return !label || !/^(T|F|正确|错误)\b/i.test(textOf(label)); }); if (!groups.length) return 0; var done = 0; for (var i = 0; i < groups.length && state.running; i++) { var group = groups[i]; if (isSkipAnswered() && selected(group)) continue; group.problem.scrollIntoView({ behavior: 'smooth', block: 'center' }); setQuestionProgress(multiple ? '多选题' : '单选题', i + 1, groups.length); await sleep(250); var options = extractOptions(group.problem); var answer = await solveOne(type, { description: questionText(group.problem), options: options, images: problemImages(group.problem), language: state.language }); if (writeChoice(group.problem, answer, multiple)) { done++; log('第 ' + (i + 1) + ' 题已写入', 'success'); } else log('第 ' + (i + 1) + ' 题未找到对应选项', 'warning'); } return done; } async function solveJudgePage() { var groups = collectGroups(function (input) { var label = input.closest('label'); return !label || /^(T|F|正确|错误)\b/i.test(textOf(label)); }); if (!groups.length) return 0; var done = 0; for (var i = 0; i < groups.length && state.running; i++) { var group = groups[i]; if (isSkipAnswered() && selected(group)) continue; group.problem.scrollIntoView({ behavior: 'smooth', block: 'center' }); setQuestionProgress('判断题', i + 1, groups.length); await sleep(250); var answer = await solveOne('judge', { description: questionText(group.problem), options: extractOptions(group.problem), images: problemImages(group.problem), language: state.language }); if (writeChoice(group.problem, answer, false)) { done++; log('第 ' + (i + 1) + ' 题已写入', 'success'); } else log('第 ' + (i + 1) + ' 题未找到判断选项', 'warning'); } return done; } async function solveFillPage() { var groups = groupFillInputs().filter(function (group) { return group.inputs.some(function (input) { return !input.value.trim(); }); }); if (!groups.length) return 0; var done = 0; for (var i = 0; i < groups.length && state.running; i++) { var group = groups[i], blanks = group.inputs.filter(function (input) { return !input.value.trim(); }); if (!blanks.length) continue; group.problem.scrollIntoView({ behavior: 'smooth', block: 'center' }); setQuestionProgress('填空题', i + 1, groups.length); var answer = await solveOne('fill', { description: questionText(group.problem), blankCount: blanks.length, images: problemImages(group.problem), language: state.language }); var values = normalizeAnswer(answer).split('|').map(function (value) { return value.trim(); }); blanks.forEach(function (input, index) { if (writeInput(input, values[index] || values[0] || '')) done++; }); log('填空组 ' + (i + 1) + ' 已写入', 'success'); } return done; } async function solveProgramFillPage() { var groups = groupProgramFills().filter(function (group) { return group.inputs.some(function (input) { return !input.value.trim(); }); }); if (!groups.length) return 0; var done = 0; for (var i = 0; i < groups.length && state.running; i++) { var group = groups[i], code = getEditorDoc(group.editor), blanks = group.inputs.filter(function (input) { return !input.value.trim(); }); var marked = code.replace(/______+/g, function () { return '【空】'; }); var answer = await solveOne('program_fill', { description: questionText(group.problem), code: marked, blankCount: blanks.length, images: problemImages(group.problem), language: resolveLanguage(code, state.language) }); var values = normalizeAnswer(answer).split('|').map(function (value) { return value.trim(); }); blanks.forEach(function (input, index) { if (writeInput(input, values[index] || values[0] || '')) done++; }); setQuestionProgress('程序填空', i + 1, groups.length); log('程序填空组 ' + (i + 1) + ' 已写入', 'success'); } return done; } async function submitCode(type) { var problem = queryAll('.rendered-markdown')[0] || document.querySelector('section'); if (!problem) return { processed: 0, fullScore: false }; setQuestionProgress('读取题目', 1, 1); closeSubmissionResult(); var question = type === 'programming' ? extractProgramming(problem) : extractFunction(problem); var attempts = 0; var editor = null; while (attempts < 2 && state.running) { setQuestionProgress(attempts ? '第 2 次生成答案' : '生成答案', 1, 1); if (!editor || !document.documentElement.contains(editor)) editor = editableEditor(attempts === 0 && isSkipAnswered()); if (!editor) { if (attempts === 0 && isSkipAnswered()) { setQuestionProgress('已跳过', 1, 1); log('当前题已有答案,已跳过', 'info'); return { processed: 0, fullScore: false }; } log('未找到可写代码编辑器', 'warning'); return { processed: 0, fullScore: false }; } var code = getEditorDoc(editor); var answer = await solveOne(type, { description: (question.desc || questionText(problem)) + (attempts ? '\n\n【重试要求】上一次提交未获得满分,请重新检查边界条件、输入输出和代码逻辑,不要机械重复上一次答案。' : ''), code: code, interfaceCode: question.interfaceCode, supportCode: question.supportCode, frameworkCode: question.frameworkCode, referenceCode: question.referenceCode, input: question.input, output: question.output, sampleInput: question.sampleInput, sampleOutput: question.sampleOutput, images: question.images || problemImages(problem), language: resolveLanguage([question.interfaceCode, question.frameworkCode, question.supportCode, question.referenceCode, code].filter(Boolean).join('\n'), state.language) }); if (!writeEditor(editor, normalizeAnswer(answer, true))) { log('代码已复制,请在编辑器中手动粘贴', 'warning'); return { processed: 0, fullScore: false }; } setQuestionProgress(attempts ? '第 2 次准备提交' : '准备提交', 1, 1); log(attempts ? '第 2 次代码已写入编辑器' : '代码已写入编辑器', 'success'); var submit = await waitForSubmitButton(6000); if (!submit) { log('未找到可点击的“提交本题作答”按钮', 'warning'); return { processed: 1, fullScore: false }; } await sleep(400); submit.click(); log(attempts ? '已提交第 2 次答案,等待评分' : '已点击提交,等待程序评分', 'info'); setQuestionProgress('等待评分', 1, 1); setStatus('正在评分', '等待 PTA 程序评测…', 'live'); var result = await waitForSubmissionScore(45000); if (!result) { log('等待评分超时,未自动重复提交', 'warning'); return { processed: 1, fullScore: false }; } var fullScore = result.total > 0 && result.score >= result.total; log('本题得分 ' + result.score + ' / ' + result.total + (fullScore ? ',满分' : ',未满分'), fullScore ? 'success' : 'warning'); closeSubmissionResult(); if (fullScore) { setQuestionProgress('满分通过', 1, 1); setStatus('满分通过', result.score + ' / ' + result.total, 'ok'); return { processed: 1, fullScore: true }; } attempts++; if (attempts < 2 && state.running) { setQuestionProgress('准备重答', 1, 1); log('本题未满分,重新作答(最多重试 1 次)', 'info'); await sleep(700); } } return { processed: 1, fullScore: false }; } async function autoSolve() { var type = detectType(); setQuestionProgress('准备处理', 1, 1); log('识别为' + (type === 'unknown' ? '未知题型' : ' ' + type) + ',语言 ' + languageLabel(state.language) + ',使用 ' + PROVIDERS[state.provider].name, 'info'); if (type === 'unknown') { log('当前页面未识别到题型,请进入具体题目页面', 'warning'); return; } var total = 0; do { if (!state.running) break; if (type === 'judge') total += await solveJudgePage(); else if (type === 'choice' || type === 'multi_choice') total += await solveChoicePage(type); else if (type === 'fill') total += await solveFillPage(); else if (type === 'program_fill') total += await solveProgramFillPage(); else if (type === 'function' || type === 'programming') { var codeResult = await submitCode(type); total += codeResult.processed; if (!codeResult.fullScore || !isContinuous() || lastPage() || !goNext()) break; log('本题满分,进入下一题', 'success'); await sleep(1200); setQuestionProgress('读取下一题', 1, 1); continue; } if (!isContinuous() || lastPage() || !goNext()) break; await sleep(900); } while (state.running); log('本次完成,共处理 ' + total + ' 题/空', 'success'); } var CSS = ` #pta-root{--pta-bg:#fff;--pta-panel:#f8f9fc;--pta-text:#17181c;--pta-muted:#737782;--pta-line:#e7e9ee;--pta-accent:#635bff;--pta-accent-soft:#eeedff;--pta-success:#219653;--pta-warn:#b7791f;position:fixed;right:22px;bottom:22px;z-index:2147483000;font:13px/1.4 -apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;color:var(--pta-text)} #pta-root *{box-sizing:border-box}#pta-shell{position:relative;z-index:2;width:390px;height:554px;max-height:calc(100vh - 44px);border:1px solid rgba(20,24,40,.12);border-radius:18px;background:var(--pta-bg);box-shadow:0 18px 55px rgba(22,24,40,.18),0 3px 12px rgba(22,24,40,.08);overflow:hidden;transition:width .72s cubic-bezier(.16,1,.3,1),height .72s cubic-bezier(.16,1,.3,1),border-radius .72s cubic-bezier(.16,1,.3,1)} #pta-shell.collapsed{width:58px;height:58px;border-radius:19px;cursor:pointer} .pta-aura{position:absolute;right:0;bottom:0;width:58px;height:58px;border-radius:19px;pointer-events:none;z-index:1;opacity:0;transition:opacity .45s ease;display:flex;align-items:center;justify-content:center} #pta-shell.collapsed.running + .pta-aura,#pta-root.running #pta-shell.collapsed + .pta-aura{opacity:1} .pta-aura-ambient{position:absolute;width:84px;height:84px;border-radius:50%;background:radial-gradient(circle at center,rgba(245,180,0,.3) 0%,rgba(99,91,255,.18) 45%,transparent 70%);filter:blur(16px);animation:pta-ambient-breathe 3.6s ease-in-out infinite alternate} .pta-aura-disc{position:absolute;width:92px;height:92px;border-radius:50%;background:conic-gradient(from 0deg,rgba(245,175,0,.82) 0deg,rgba(235,95,30,.7) 60deg,rgba(99,91,255,.82) 135deg,rgba(14,165,233,.75) 220deg,rgba(16,185,129,.68) 285deg,rgba(245,175,0,.82) 360deg);filter:blur(15px);opacity:.78;animation:pta-orb-spin 6.5s linear infinite,pta-orb-morph 9s ease-in-out infinite alternate;will-change:transform,filter} html.pta-theme-dark #pta-shell.collapsed{background:#18181a;backdrop-filter:none;-webkit-backdrop-filter:none} html.pta-theme-dark .pta-aura-ambient{background:radial-gradient(circle at center,rgba(255,200,50,.32) 0%,rgba(138,120,255,.2) 45%,transparent 70%);filter:blur(16px)} html.pta-theme-dark .pta-aura-disc{background:conic-gradient(from 0deg,rgba(255,204,0,.88) 0deg,rgba(255,136,0,.75) 55deg,rgba(155,89,255,.82) 130deg,rgba(56,189,248,.78) 215deg,rgba(52,211,153,.72) 280deg,rgba(255,204,0,.88) 360deg);filter:blur(14px);opacity:.85} #pta-shell.collapsed.running .pta-brand-title{background:linear-gradient(90deg,rgba(23,24,28,.45) 0%,rgba(23,24,28,.45) 20%,#635bff 45%,#17181c 50%,#635bff 55%,rgba(23,24,28,.45) 80%,rgba(23,24,28,.45) 100%);background-size:260% 100%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:pta-pas-shimmer 2.2s linear infinite} html.pta-theme-dark #pta-shell.collapsed.running .pta-brand-title{background:linear-gradient(90deg,rgba(245,245,245,.45) 0%,rgba(245,245,245,.45) 20%,#a5b4fc 45%,#ffffff 50%,#a5b4fc 55%,rgba(245,245,245,.45) 80%,rgba(245,245,245,.45) 100%);background-size:260% 100%;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:pta-pas-shimmer 2.2s linear infinite} #pta-shell.collapsed.running .pta-pas-p{animation:pta-letter-wave 1.6s ease-in-out infinite 0s} #pta-shell.collapsed.running .pta-pas-a{animation:pta-letter-wave 1.6s ease-in-out infinite .22s} #pta-shell.collapsed.running .pta-pas-s{animation:pta-letter-wave 1.6s ease-in-out infinite .44s} @keyframes pta-orb-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}} @keyframes pta-orb-morph{0%{filter:blur(13px) hue-rotate(0deg);transform:scale(.96)}50%{filter:blur(16px) hue-rotate(75deg);transform:scale(1.04)}100%{filter:blur(13px) hue-rotate(150deg);transform:scale(.98)}} @keyframes pta-ambient-breathe{0%{opacity:.3;transform:scale(.92)}100%{opacity:.65;transform:scale(1.08)}} @keyframes pta-pas-shimmer{0%{background-position:130% 0}100%{background-position:-130% 0}} @keyframes pta-letter-wave{0%,100%{transform:translateY(0) scale(1)}50%{transform:translateY(-1.5px) scale(1.07)}}#pta-shell.collapsed .pta-top{height:58px;padding:0;border-bottom:0;background:transparent}#pta-shell.collapsed .pta-expanded{opacity:0;pointer-events:none;visibility:hidden;transition:opacity .28s ease,visibility 0s linear .28s}#pta-shell.collapsed .pta-brand{padding:0;justify-content:center;align-items:center;gap:0;width:100%;height:100%}#pta-shell.collapsed .pta-brand-copy{align-items:center;justify-content:center;width:100%;height:100%;text-align:center}#pta-shell.collapsed .pta-brand-meta{display:none;opacity:0;max-height:0}#pta-shell.collapsed .pta-top-actions{display:none} .pta-top{height:62px;display:flex;align-items:center;padding:0 14px;border-bottom:1px solid var(--pta-line);background:linear-gradient(135deg,#fff 0%,#f7f7ff 100%);transition:padding .72s cubic-bezier(.16,1,.3,1),height .72s cubic-bezier(.16,1,.3,1)}.pta-brand{display:flex;align-items:center;gap:0;flex:1;min-width:0;height:100%;transition:all .72s cubic-bezier(.16,1,.3,1)}.pta-brand-copy{min-width:0;display:flex;flex-direction:column;justify-content:center;transition:all .72s cubic-bezier(.16,1,.3,1)}.pta-brand-title{font-weight:700;font-size:13px;letter-spacing:-.2px;color:var(--pta-text);display:flex;align-items:center;white-space:nowrap;line-height:1.2;transition:font-size .72s cubic-bezier(.16,1,.3,1),font-weight .72s ease,letter-spacing .72s ease,transform .25s ease}#pta-shell.collapsed .pta-brand-title{font-size:14px;font-weight:800;letter-spacing:1px;justify-content:center}#pta-shell.collapsed:hover .pta-brand-title{transform:scale(1.08)}.pta-pas-letter{display:inline-block;font-weight:inherit;font-size:inherit;transition:all .72s cubic-bezier(.16,1,.3,1)}.pta-pas-rest{display:inline-block;max-width:55px;opacity:1;overflow:hidden;white-space:nowrap;vertical-align:bottom;transition:max-width .72s cubic-bezier(.16,1,.3,1),opacity .38s ease}#pta-shell.collapsed .pta-pas-rest{max-width:0;opacity:0;margin:0;padding:0}.pta-pas-space{display:inline-block;width:3px;max-width:3px;overflow:hidden;transition:all .72s cubic-bezier(.16,1,.3,1)}#pta-shell.collapsed .pta-pas-space{width:0;max-width:0;opacity:0}.pta-brand-meta{font-size:11px;color:var(--pta-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:1;max-height:18px;transition:opacity .28s ease,max-height .72s cubic-bezier(.16,1,.3,1)}.pta-top-actions{display:flex;gap:5px}.pta-icon-btn,.pta-link-btn{border:0;background:transparent;color:var(--pta-muted);cursor:pointer;border-radius:8px;padding:7px;transition:transform .2s ease,background .2s ease,color .2s ease}.pta-icon-btn:hover,.pta-link-btn:hover{background:var(--pta-accent-soft);color:var(--pta-accent);transform:translateY(-1px)} .pta-expanded{width:390px;padding:13px 14px 14px;opacity:1;transition:opacity .38s ease .18s}.pta-status{display:flex;align-items:center;gap:8px;padding:10px 11px;border-radius:12px;background:var(--pta-panel);border:1px solid var(--pta-line);margin-bottom:12px}.pta-status-dot{width:8px;height:8px;border-radius:50%;background:var(--pta-muted)}.pta-status-dot.live{background:var(--pta-accent);box-shadow:0 0 0 5px var(--pta-accent-soft);animation:pta-breathe 1.8s ease-in-out infinite}.pta-status-dot.ok{background:var(--pta-success)}.pta-status-text{flex:1}.pta-status-label{font-weight:600}.pta-status-sub{font-size:11px;color:var(--pta-muted);margin-top:1px}.pta-progress{margin:12px 2px 15px}.pta-progress-head{display:flex;justify-content:space-between;color:var(--pta-muted);font-size:11px;margin-bottom:7px}.pta-progress-head strong{color:var(--pta-text);font-weight:600}.pta-progress-track{height:5px;border-radius:99px;background:#eceef3;overflow:hidden}.pta-progress-fill{height:100%;width:0;border-radius:inherit;background:linear-gradient(90deg,#8178ff,#5149dc);transition:width .38s ease}.pta-options{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:13px}.pta-check{display:flex;align-items:center;gap:7px;color:var(--pta-muted);font-size:12px;cursor:pointer}.pta-check input{accent-color:var(--pta-accent)}.pta-primary{width:100%;height:42px;border:0;border-radius:11px;background:var(--pta-accent);color:#fff;font:600 13px inherit;cursor:pointer;box-shadow:0 7px 16px rgba(99,91,255,.23);transition:transform .2s ease,box-shadow .2s ease,background .2s ease}.pta-primary:hover{transform:translateY(-1px);box-shadow:0 10px 19px rgba(99,91,255,.28)}.pta-primary:active{transform:scale(.98)}.pta-primary.stop{background:#d84c5b;box-shadow:0 7px 16px rgba(216,76,91,.2)}.pta-log-head{display:flex;align-items:center;margin:16px 1px 7px;color:var(--pta-muted);font-size:11px}.pta-log-head strong{color:var(--pta-text);font-weight:600;margin-right:5px}.pta-log-clear{margin-left:auto;padding:0}.pta-log{height:108px;border-top:1px solid var(--pta-line);padding-top:5px;overflow:auto;scrollbar-width:thin}.pta-log-item{display:flex;align-items:flex-start;gap:7px;padding:4px 2px;color:var(--pta-muted);font-size:11px;animation:pta-rise .25s ease both}.pta-log-item.success{color:var(--pta-success)}.pta-log-item.warning{color:var(--pta-warn)}.pta-log-dot{width:5px;height:5px;border-radius:50%;background:currentColor;margin-top:6px;flex:0 0 auto}.pta-empty{color:#a4a8b0;padding:10px 2px}.pta-drawer{position:absolute;inset:0;background:var(--pta-bg);transform:translateX(104%);transition:transform .55s cubic-bezier(.16,1,.3,1);display:flex;flex-direction:column}.pta-drawer.open{transform:translateX(0)}.pta-drawer-head{height:62px;display:flex;align-items:center;gap:8px;padding:0 14px;border-bottom:1px solid var(--pta-line)}.pta-drawer-title{font-weight:700;flex:1}.pta-drawer-body{padding:14px;overflow:auto}.pta-tabs{display:grid;grid-template-columns:1fr 1fr;background:var(--pta-panel);border-radius:10px;padding:3px;margin-bottom:15px}.pta-tab{border:0;background:transparent;color:var(--pta-muted);border-radius:8px;padding:8px;cursor:pointer;font:600 12px inherit}.pta-tab.active{background:#fff;color:var(--pta-text);box-shadow:0 1px 4px rgba(22,24,40,.1)}.pta-pane{display:none}.pta-pane.active{display:block;animation:pta-rise .25s ease both}.pta-provider{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:14px}.pta-provider-card{padding:11px;border:1px solid var(--pta-line);border-radius:11px;text-align:left;background:#fff;cursor:pointer;transition:border .2s ease,background .2s ease,transform .2s ease}.pta-provider-card:hover{transform:translateY(-1px)}.pta-provider-card.active{border-color:var(--pta-accent);background:var(--pta-accent-soft)}.pta-provider-name{font-weight:700}.pta-provider-hint{display:block;color:var(--pta-muted);font-size:10px;margin-top:3px}.pta-field{margin:12px 0}.pta-field label{display:block;font-size:11px;font-weight:600;color:var(--pta-muted);margin-bottom:6px}.pta-field input,.pta-field select{width:100%;height:36px;border:1px solid var(--pta-line);border-radius:9px;padding:0 10px;outline:none;background:#fff;color:var(--pta-text);font:12px inherit}.pta-field input:focus,.pta-field select:focus{border-color:var(--pta-accent);box-shadow:0 0 0 3px var(--pta-accent-soft)}.pta-fixed{display:flex;align-items:center;justify-content:space-between;padding:10px 11px;border-radius:10px;background:var(--pta-panel);color:var(--pta-muted);font-size:11px}.pta-fixed strong{color:var(--pta-text);font-weight:600}.pta-save-hint{font-size:11px;color:var(--pta-muted);user-select:none;transition:all .2s ease}.pta-save-hint.saved{color:var(--pta-success);font-weight:600}.pta-provider-desc{font-size:11px;line-height:1.5;color:var(--pta-muted);padding:2px 2px 10px;margin-top:-6px;transition:color .2s ease}.pta-help{font-size:11px;line-height:1.7;color:var(--pta-muted);margin:12px 1px}.pta-switch-row{display:flex;align-items:center;justify-content:space-between;padding:12px;border:1px solid var(--pta-line);border-radius:11px}.pta-switch{position:relative;width:38px;height:22px}.pta-switch input{opacity:0;width:0;height:0}.pta-slider{position:absolute;inset:0;border-radius:99px;background:#d9dce4;cursor:pointer;transition:.2s}.pta-slider:before{content:'';position:absolute;width:16px;height:16px;left:3px;top:3px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.2);transition:.2s}.pta-switch input:checked+.pta-slider{background:var(--pta-accent)}.pta-switch input:checked+.pta-slider:before{transform:translateX(16px)}.pta-toast{position:fixed;right:22px;bottom:92px;z-index:2147483001;padding:10px 13px;border-radius:10px;background:#1b1d23;color:#fff;box-shadow:0 8px 25px rgba(0,0,0,.18);animation:pta-toast-in .25s ease both}.pta-toast.out{animation:pta-toast-out .25s ease both}@keyframes pta-breathe{50%{opacity:.55;transform:scale(.85)}}@keyframes pta-rise{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}@keyframes pta-toast-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}@keyframes pta-toast-out{to{opacity:0;transform:translateY(8px)}} @media(max-width:600px){#pta-root{right:12px;bottom:12px}#pta-shell{width:min(390px,calc(100vw - 24px));height:min(554px,calc(100vh - 24px))}#pta-shell.collapsed{width:58px;height:58px}.pta-expanded{width:min(390px,calc(100vw - 24px))}} #pta-root{--pta-bg:rgba(255,255,255,.84);--pta-panel:rgba(246,246,246,.72);--pta-text:#101010;--pta-muted:#777;--pta-line:rgba(0,0,0,.12);--pta-accent:#111;--pta-accent-soft:rgba(0,0,0,.07);--pta-success:#111;--pta-warn:#444} #pta-shell,.pta-drawer{background:var(--pta-bg);backdrop-filter:blur(24px) saturate(105%);-webkit-backdrop-filter:blur(24px) saturate(105%)} #pta-shell{border-color:rgba(0,0,0,.14);box-shadow:0 22px 65px rgba(0,0,0,.14),0 3px 12px rgba(0,0,0,.06)} .pta-top{background:rgba(255,255,255,.58);border-bottom-color:rgba(0,0,0,.1)} .pta-primary{background:#111;box-shadow:0 8px 18px rgba(0,0,0,.16)}.pta-progress-fill{background:#111}.pta-primary.stop{background:#444;box-shadow:0 8px 18px rgba(0,0,0,.12)} .pta-status{border-color:rgba(0,0,0,.1)}.pta-status-dot.live{background:#111;box-shadow:0 0 0 5px rgba(0,0,0,.08)} .pta-provider-card{background:rgba(255,255,255,.4);border-color:rgba(0,0,0,.12);transition:border-color .2s ease,background .2s ease,transform .2s cubic-bezier(.22,1,.36,1)}.pta-provider-card.active{border-color:#111;background:rgba(0,0,0,.06)}.pta-home-language{margin:0 1px 13px;padding:10px 11px;border:1px solid rgba(0,0,0,.1);border-radius:11px;background:rgba(245,245,245,.52)}.pta-home-language-head{display:flex;align-items:center;justify-content:space-between;color:#555;font-size:11px;font-weight:600}.pta-home-language-head span:last-child{color:#888;font-weight:500}.pta-home-language .pta-model-select{margin-top:7px} html.pta-theme-dark{color-scheme:dark;background:#101114}html.pta-theme-dark body{background:#101114!important;color:#ededed!important}html.pta-theme-dark body>*:not(#pta-root){filter:invert(1) hue-rotate(180deg)}html.pta-theme-dark body>*:not(#pta-root) img,html.pta-theme-dark body>*:not(#pta-root) video{filter:invert(1) hue-rotate(180deg)}html.pta-theme-dark #pta-root{--pta-bg:rgba(24,24,26,.9);--pta-panel:rgba(42,42,45,.76);--pta-text:#f5f5f5;--pta-muted:#a5a5aa;--pta-line:rgba(255,255,255,.15);--pta-accent:#f5f5f5;--pta-accent-soft:rgba(255,255,255,.1);--pta-success:#f5f5f5;--pta-warn:#d4d4d4}html.pta-theme-dark #pta-shell,html.pta-theme-dark .pta-drawer{background:var(--pta-bg);color:var(--pta-text)}html.pta-theme-dark .pta-top{background:rgba(28,28,30,.72);border-bottom-color:var(--pta-line)}html.pta-theme-dark .pta-status,html.pta-theme-dark .pta-fixed{background:rgba(42,42,45,.64);border-color:var(--pta-line)}html.pta-theme-dark .pta-select-trigger{background:rgba(38,38,41,.78);color:var(--pta-text);border-color:var(--pta-line)}html.pta-theme-dark .pta-select-trigger:hover,html.pta-theme-dark .pta-model-select.open .pta-select-trigger{background:#303034;border-color:#f5f5f5}html.pta-theme-dark .pta-select-menu{background:rgba(32,32,35,.96);border-color:var(--pta-line)}html.pta-theme-dark .pta-select-option{color:#d4d4d8}html.pta-theme-dark .pta-select-option:hover,html.pta-theme-dark .pta-select-option.active{background:#f5f5f5;color:#111} html.pta-theme-dark .pta-field input,html.pta-theme-dark .pta-field select{background:rgba(255,255,255,.065);border-color:rgba(255,255,255,.16);color:#f5f5f5;box-shadow:inset 0 1px 0 rgba(255,255,255,.04)}html.pta-theme-dark .pta-field input::placeholder{color:#8f8f96}html.pta-theme-dark .pta-field input:focus,html.pta-theme-dark .pta-field select:focus{background:rgba(255,255,255,.1);border-color:#f5f5f5;box-shadow:0 0 0 3px rgba(255,255,255,.1),inset 0 1px 0 rgba(255,255,255,.06)}html.pta-theme-dark .pta-select-trigger{background:rgba(255,255,255,.065);border-color:rgba(255,255,255,.16);box-shadow:inset 0 1px 0 rgba(255,255,255,.04)}html.pta-theme-dark .pta-select-trigger:hover,html.pta-theme-dark .pta-model-select.open .pta-select-trigger{background:rgba(255,255,255,.12);border-color:#f5f5f5;box-shadow:0 0 0 3px rgba(255,255,255,.1),inset 0 1px 0 rgba(255,255,255,.06)}html.pta-theme-dark .pta-select-menu{background:rgba(26,26,29,.97);border-color:rgba(255,255,255,.16);box-shadow:0 18px 38px rgba(0,0,0,.38)}html.pta-theme-dark .pta-select-option{color:#d9d9de}html.pta-theme-dark .pta-select-option:hover,html.pta-theme-dark .pta-select-option.active{background:#f5f5f5;color:#111}html.pta-theme-dark .pta-select-option[aria-disabled="true"]{color:#77777f}html.pta-theme-dark .pta-provider-card{background:rgba(255,255,255,.055);border-color:rgba(255,255,255,.14);color:#f5f5f5}html.pta-theme-dark .pta-provider-card.active{background:rgba(255,255,255,.13);border-color:#f5f5f5}html.pta-theme-dark .pta-tabs{background:rgba(255,255,255,.07)}html.pta-theme-dark .pta-tab{color:#aaaab2}html.pta-theme-dark .pta-tab.active{background:#f5f5f5;color:#111;box-shadow:0 1px 6px rgba(0,0,0,.28)}html.pta-theme-dark .pta-save-hint{color:#8c909c}html.pta-theme-dark .pta-save-hint.saved{color:#3dd68c}html.pta-theme-dark .pta-provider-desc{color:#8c909c}html.pta-theme-dark .pta-secondary{background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.16);color:#f5f5f5}html.pta-theme-dark .pta-secondary:hover{background:#f5f5f5;color:#111}html.pta-theme-dark .pta-home-language{background:rgba(255,255,255,.055);border-color:rgba(255,255,255,.14)}html.pta-theme-dark .pta-home-language-head{color:#d0d0d5}html.pta-theme-dark .pta-home-language-head span:last-child{color:#9999a2} .pta-check,.pta-image-option{position:relative;display:flex;align-items:center;justify-content:center;gap:7px;min-height:32px;padding:0 12px;border:1px solid var(--pta-line);border-radius:999px;background:rgba(255,255,255,.48);color:var(--pta-muted);font-size:11px;line-height:1;cursor:pointer;user-select:none;white-space:nowrap;transition:background .2s ease,border-color .2s ease,color .2s ease,box-shadow .2s ease,transform .2s ease}.pta-check:hover,.pta-image-option:hover{transform:translateY(-1px);border-color:var(--pta-text);color:var(--pta-text)}.pta-check input,.pta-image-option input{position:absolute;width:1px;height:1px;margin:0;opacity:0;pointer-events:none}.pta-check:has(input:checked),.pta-image-option:has(input:checked){background:var(--pta-text);border-color:var(--pta-text);color:var(--pta-bg);box-shadow:0 5px 14px rgba(0,0,0,.12)}.pta-check:has(input:focus-visible),.pta-image-option:has(input:focus-visible){outline:2px solid var(--pta-text);outline-offset:2px}.pta-check:has(input:disabled),.pta-image-option:has(input:disabled){cursor:not-allowed;opacity:.48;transform:none}.pta-image-option{justify-content:flex-start}.pta-image-option:has(input:checked){background:var(--pta-text);color:var(--pta-bg)}html.pta-theme-dark .pta-range-badge{background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.16);color:#f5f5f5} html.pta-theme-dark .pta-range{background:rgba(255,255,255,.16)} html.pta-theme-dark .pta-range::-webkit-slider-thumb{background:#f5f5f5;border-color:#1c1d21;box-shadow:0 1px 4px rgba(0,0,0,.6)} html.pta-theme-dark .pta-range::-moz-range-thumb{background:#f5f5f5;border-color:#1c1d21;box-shadow:0 1px 4px rgba(0,0,0,.6)} html.pta-theme-dark .pta-scale-item strong{color:#f5f5f5} html.pta-theme-dark .pta-max-tokens-scale .pta-scale-item.active strong,html.pta-theme-dark .pta-thinking-scale .pta-scale-item.active strong{color:#fff} html.pta-theme-dark .pta-max-tokens-scale .pta-scale-item.active span,html.pta-theme-dark .pta-thinking-scale .pta-scale-item.active span{color:#fff} html.pta-theme-dark .pta-check,html.pta-theme-dark .pta-image-option{background:rgba(255,255,255,.065);border-color:rgba(255,255,255,.16);color:#aaaab2}html.pta-theme-dark .pta-check:hover,html.pta-theme-dark .pta-image-option:hover{border-color:#f5f5f5;color:#f5f5f5}html.pta-theme-dark .pta-check:has(input:checked),html.pta-theme-dark .pta-image-option:has(input:checked){background:#f5f5f5;border-color:#f5f5f5;color:#111;box-shadow:0 5px 16px rgba(0,0,0,.28)}html.pta-theme-dark .pta-check:has(input:focus-visible),html.pta-theme-dark .pta-image-option:has(input:focus-visible){outline-color:#f5f5f5} .pta-secondary{height:32px;border:1px solid var(--pta-line);border-radius:8px;background:rgba(255,255,255,.52);color:var(--pta-text);padding:0 11px;cursor:pointer;font:600 11px inherit;transition:background .2s ease,color .2s ease,transform .2s ease}.pta-secondary:hover{background:#111;color:#fff;transform:translateY(-1px)}.pta-drawer-actions{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:10px}.pta-image-option{display:flex;align-items:center;gap:6px;color:var(--pta-muted);font-size:11px}.pta-image-option input{accent-color:#111}.pta-fixed,.pta-status{background:rgba(245,245,245,.6)}.pta-toast{background:#111}.pta-icon-btn:hover,.pta-link-btn:hover{background:rgba(0,0,0,.08);color:#111}.pta-error{color:#333!important;font-weight:600} .pta-model-select{position:relative}.pta-model-native{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}.pta-select-trigger{width:100%;height:38px;display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid var(--pta-line);border-radius:10px;padding:0 11px;background:rgba(255,255,255,.56);color:var(--pta-text);font:12px inherit;text-align:left;cursor:pointer;transition:border-color .2s ease,background .2s ease,box-shadow .2s ease}.pta-select-trigger:hover,.pta-model-select.open .pta-select-trigger{border-color:#111;background:#fff}.pta-model-select.open .pta-select-trigger{box-shadow:0 0 0 3px rgba(0,0,0,.06)}.pta-select-trigger:disabled{cursor:not-allowed;opacity:.55}.pta-select-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pta-select-chevron{width:7px;height:7px;flex:0 0 auto;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(45deg) translateY(-2px);transition:transform .24s cubic-bezier(.22,1,.36,1)}.pta-model-select.open .pta-select-chevron{transform:rotate(225deg) translate(-1px,-2px)}.pta-select-menu{position:absolute;left:0;right:0;top:calc(100% + 7px);z-index:20;padding:4px;border:1px solid rgba(0,0,0,.12);border-radius:11px;background:rgba(255,255,255,.92);box-shadow:0 14px 32px rgba(0,0,0,.12);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);opacity:0;visibility:hidden;transform:translateY(-5px) scale(.985);transform-origin:top center;transition:opacity .2s ease,transform .24s cubic-bezier(.22,1,.36,1),visibility 0s linear .24s;max-height:176px;overflow:auto}.pta-model-select.open .pta-select-menu{opacity:1;visibility:visible;transform:none;transition-delay:0s}.pta-select-option{display:flex;align-items:center;min-height:32px;padding:0 9px;border-radius:7px;color:#555;font-size:12px;cursor:pointer;opacity:0;transform:translateY(-3px);transition:background .16s ease,color .16s ease,opacity .2s ease,transform .24s cubic-bezier(.22,1,.36,1)}.pta-model-select.open .pta-select-option{opacity:1;transform:none;transition-delay:calc(var(--option-index,0) * 22ms)}.pta-select-option:hover,.pta-select-option.active{background:#111;color:#fff}.pta-select-option[aria-disabled="true"]{color:#999;cursor:default}.pta-select-option[aria-disabled="true"]:hover{background:transparent;color:#999} .pta-model-select.closing .pta-select-menu{opacity:0;visibility:visible;transform:translateY(-4px) scale(.985);transition:opacity .15s ease,transform .17s cubic-bezier(.4,0,.2,1),visibility 0s linear .17s}.pta-model-select.closing .pta-select-option{opacity:0;transform:translateY(-2px);transition-delay:calc(var(--option-index,0) * 8ms)} .pta-range-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px} .pta-range-head label{display:block;font-size:11px;font-weight:600;color:var(--pta-muted);margin:0} .pta-range-badge{font-size:11px;font-weight:600;color:var(--pta-text);background:rgba(0,0,0,.05);padding:2px 7px;border-radius:6px;border:1px solid var(--pta-line);white-space:nowrap} .pta-range-wrap{position:relative;width:100%;margin-top:2px} .pta-range{display:block;width:100%;height:6px;margin:8px 0 6px;-webkit-appearance:none;appearance:none;background:var(--pta-line);border-radius:99px;outline:none;cursor:pointer;transition:background .2s ease} .pta-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;border-radius:50%;background:var(--pta-text);border:2px solid var(--pta-bg);box-shadow:0 1px 4px rgba(0,0,0,.22);cursor:pointer;transition:transform .15s ease} .pta-range::-webkit-slider-thumb:hover{transform:scale(1.18)} .pta-range::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:var(--pta-text);border:2px solid var(--pta-bg);box-shadow:0 1px 4px rgba(0,0,0,.22);cursor:pointer;transition:transform .15s ease} .pta-range-scale{position:relative;display:flex;align-items:flex-start;justify-content:space-between;margin-top:4px;font-size:10px;color:var(--pta-muted);line-height:1.25} .pta-scale-item{display:flex;flex-direction:column} .pta-scale-item.min{text-align:left} .pta-scale-item.max{text-align:right} .pta-scale-item.mid{position:absolute;transform:translateX(-50%);text-align:center;white-space:nowrap} .pta-scale-item strong{font-weight:600;color:var(--pta-text)} .pta-max-tokens-scale,.pta-thinking-scale{position:relative;width:100%;height:28px;margin-top:5px;display:block} .pta-max-tokens-scale .pta-scale-item,.pta-thinking-scale .pta-scale-item{position:absolute;top:0;transform:translateX(-50%);text-align:center;white-space:nowrap;cursor:pointer;color:var(--pta-muted);transition:color .18s ease,transform .18s ease} .pta-max-tokens-scale .pta-scale-item span,.pta-thinking-scale .pta-scale-item span{display:block;font-size:10px;font-weight:700;line-height:1.2} .pta-max-tokens-scale .pta-scale-item strong,.pta-thinking-scale .pta-scale-item strong{display:block;font-size:9px;font-weight:600;line-height:1.2;color:var(--pta-muted);transition:color .18s ease} .pta-max-tokens-scale .pta-scale-item:hover,.pta-thinking-scale .pta-scale-item:hover{transform:translateX(-50%) translateY(-1px);color:var(--pta-text)} .pta-max-tokens-scale .pta-scale-item:hover strong,.pta-thinking-scale .pta-scale-item:hover strong{color:var(--pta-text)} .pta-max-tokens-scale .pta-scale-item.active,.pta-thinking-scale .pta-scale-item.active{color:var(--pta-text)} .pta-max-tokens-scale .pta-scale-item.active strong,.pta-thinking-scale .pta-scale-item.active strong{color:var(--pta-text);font-weight:700} .pta-provider{grid-template-columns:repeat(3,minmax(0,1fr))} @media(prefers-reduced-motion:reduce){#pta-shell,.pta-drawer,.pta-select-menu,.pta-select-option,.pta-select-chevron,.pta-primary,.pta-secondary{transition-duration:.01ms!important;animation-duration:.01ms!important}} `; function toast(message) { var old = document.querySelector('.pta-toast'); if (old) old.remove(); var item = document.createElement('div'); item.className = 'pta-toast'; item.textContent = message; document.body.appendChild(item); setTimeout(function () { item.classList.add('out'); setTimeout(function () { item.remove(); }, 250); }, 2200); } function render() { if (document.getElementById('pta-root')) return; var style = document.createElement('style'); style.id = 'pta-style'; style.textContent = CSS; document.head.appendChild(style); var root = document.createElement('div'); root.id = 'pta-root'; root.innerHTML = ` `; document.body.appendChild(root); bindUI(root); syncUI(); } function closeModelDropdown() { document.querySelectorAll('.pta-model-select.open').forEach(function (wrapper) { wrapper.classList.remove('open'); wrapper.classList.add('closing'); clearTimeout(wrapper._ptaCloseTimer); wrapper._ptaCloseTimer = setTimeout(function () { wrapper.classList.remove('closing'); }, 190); }); document.querySelectorAll('.pta-select-trigger[aria-expanded="true"]').forEach(function (trigger) { trigger.setAttribute('aria-expanded', 'false'); }); } function syncDropdown(nativeId, wrapperId, triggerId, valueId, menuId) { var native = document.getElementById(nativeId); var wrapper = document.getElementById(wrapperId); var trigger = document.getElementById(triggerId); var value = document.getElementById(valueId); var menu = document.getElementById(menuId); if (!native || !wrapper || !trigger || !value || !menu) return; trigger.disabled = native.disabled; trigger.setAttribute('aria-expanded', wrapper.classList.contains('open') ? 'true' : 'false'); value.textContent = native.options[native.selectedIndex] ? native.options[native.selectedIndex].textContent : '填写 API Key 后获取'; menu.innerHTML = ''; Array.prototype.slice.call(native.options).forEach(function (option, index) { var item = document.createElement('div'); item.className = 'pta-select-option' + (option.value && option.value === native.value ? ' active' : ''); item.style.setProperty('--option-index', index); item.setAttribute('role', 'option'); item.setAttribute('aria-selected', option.value === native.value ? 'true' : 'false'); item.textContent = option.textContent; if (!option.value) item.setAttribute('aria-disabled', 'true'); item.addEventListener('click', function (event) { event.stopPropagation(); if (!option.value || native.disabled) return; native.value = option.value; native.dispatchEvent(new Event('change', { bubbles: true })); closeModelDropdown(); }); menu.appendChild(item); }); if (native.disabled) closeModelDropdown(); } function syncModelDropdown() { syncDropdown('pta-model', 'pta-model-select', 'pta-model-trigger', 'pta-model-value', 'pta-model-menu'); } function syncLanguageDropdown() { syncDropdown('pta-language', 'pta-language-select', 'pta-language-trigger', 'pta-language-value', 'pta-language-menu'); } function applyTheme() { var dark = state.theme === 'dark'; document.documentElement.classList.toggle('pta-theme-dark', dark); var button = document.getElementById('pta-theme-toggle'); if (button) { button.textContent = dark ? '☀' : '☾'; button.title = dark ? '切换浅色' : '切换深色'; button.setAttribute('aria-label', dark ? '切换浅色' : '切换深色'); } } function syncUI() { applyTheme(); var provider = PROVIDERS[state.provider], config = state.configs[state.provider]; var meta = document.getElementById('pta-meta'); if (meta) meta.textContent = (config.apiKey || provider.optionalKey && localRuntime.available) ? provider.name + ' · ' + (config.model || '等待模型') : '未配置 AI'; var key = document.getElementById('pta-api-key'); if (key) key.value = config.apiKey || ''; var keyLabel = document.getElementById('pta-api-key-label'); if (keyLabel) keyLabel.textContent = provider.optionalKey ? '本地 Token(可选)' : 'API Key'; if (key) key.placeholder = provider.optionalKey ? '未设置 AUTH_TOKEN 时可留空' : '填写后自动读取模型列表'; var model = document.getElementById('pta-model'); if (model) { model.innerHTML = ''; if (!config.models || !config.models.length) { var placeholder = document.createElement('option'); placeholder.value = ''; placeholder.textContent = provider.optionalKey && !localRuntime.available ? '等待检测到本地代理' : (config.apiKey || provider.optionalKey ? '正在等待模型列表…' : '填写 API Key 后获取'); model.appendChild(placeholder); model.disabled = true; } else { config.models.forEach(function (id) { var option = document.createElement('option'); option.value = id; option.textContent = id + (config.modelMeta && config.modelMeta[id] && config.modelMeta[id].free ? ' · Free' : ''); model.appendChild(option); }); model.value = config.model || config.models[0]; model.disabled = false; } } syncModelDropdown(); var language = document.getElementById('pta-language'); if (language) language.value = state.language || 'auto'; syncLanguageDropdown(); var modelState = document.getElementById('pta-model-state'); if (modelState) { var emptyHint = provider.optionalKey && !localRuntime.available ? '未检测到本地 OpenCode Free 服务(127.0.0.1:8788)。启动后会自动出现。' : (config.models && config.models.length ? '已读取 ' + config.models.length + ' 个可用模型。' : (provider.optionalKey ? '已连接,正在读取 Free 模型列表。' : '填写 API Key 后自动读取模型列表。')); modelState.textContent = config.modelsLoading ? '正在向供应商读取模型列表…' : (config.modelError || emptyHint); modelState.classList.toggle('pta-error', !!config.modelError || provider.optionalKey && !localRuntime.available); } var endpointEl = document.getElementById('pta-endpoint'); if (endpointEl) endpointEl.textContent = baseUrl(state.provider).replace('https://', ''); var providerDescEl = document.getElementById('pta-provider-desc'); if (providerDescEl) providerDescEl.textContent = provider.desc || provider.hint || ''; var languageMode = document.getElementById('pta-language-mode'); if (languageMode) languageMode.textContent = state.language && state.language !== 'auto' ? '手动 · ' + (LANG_NAMES[state.language] || state.language) : '自动检测'; var maxTokensInput = document.getElementById('pta-max-tokens'); var maxTokensVal = document.getElementById('pta-max-tokens-val'); var thinkingInput = document.getElementById('pta-thinking'); var thinkingVal = document.getElementById('pta-thinking-val'); var currentMeta = config.modelMeta && config.modelMeta[config.model]; var ctxLen = currentMeta && currentMeta.contextLength; var maxRange = resolveModelContextLength(state.provider, config.model, ctxLen); var availableTiers = getAvailableTiers(maxRange); if (maxTokensInput) { maxTokensInput.min = '0'; maxTokensInput.max = String(availableTiers.length - 1); maxTokensInput.step = '1'; var curTokens = config.maxTokens || 2048; var bestIdx = findClosestTierIndex(availableTiers, curTokens); maxTokensInput.value = bestIdx; config.maxTokens = availableTiers[bestIdx].val; if (maxTokensVal) maxTokensVal.textContent = config.maxTokens + ' (' + availableTiers[bestIdx].label + ')'; renderMaxTokensScale(availableTiers, bestIdx); } if (thinkingInput) { var curThinking = config.thinking !== undefined ? Number(config.thinking) : 2; if (isNaN(curThinking) || curThinking < 0 || curThinking > 3) curThinking = 2; thinkingInput.value = curThinking; config.thinking = curThinking; if (thinkingVal) thinkingVal.textContent = THINKING_LABELS[curThinking] || '中 (Medium · 推荐)'; document.querySelectorAll('.pta-thinking-scale .pta-scale-item').forEach(function (item) { var step = Number(item.dataset.step); item.classList.toggle('active', step === curThinking); }); } document.querySelectorAll('.pta-provider-card').forEach(function (card) { var isOpenCode = card.dataset.provider === 'opencode'; card.classList.toggle('active', card.dataset.provider === state.provider); if (isOpenCode) { card.hidden = !localRuntime.available; card.disabled = !localRuntime.available; } }); var images = document.getElementById('pta-images'); if (images) { images.checked = !!config.images; images.disabled = !provider.supportsImages || !modelSupportsVision(state.provider, config.model, config.modelMeta && config.modelMeta[config.model]); } var skip = document.getElementById('pta-skip'); if (skip) skip.checked = state.skipAnswered; var continuous = document.getElementById('pta-continuous'); if (continuous) continuous.checked = state.continuous; } function bindUI(root) { var shell = document.getElementById('pta-shell'), drawer = document.getElementById('pta-drawer'); var apiKeyTimer = null; var saveHintTimer = null; function flashSaveStatus(text) { var el = document.getElementById('pta-save-hint'); if (!el) return; el.textContent = text || '✓ 已保存'; el.classList.add('saved'); clearTimeout(saveHintTimer); saveHintTimer = setTimeout(function () { el.textContent = '实时保存'; el.classList.remove('saved'); }, 1400); } shell.addEventListener('click', function () { if (shell.classList.contains('collapsed')) shell.classList.remove('collapsed'); }); document.getElementById('pta-collapse').addEventListener('click', function (e) { e.stopPropagation(); shell.classList.add('collapsed'); drawer.classList.remove('open'); }); document.getElementById('pta-theme-toggle').addEventListener('click', function (e) { e.stopPropagation(); state.theme = state.theme === 'dark' ? 'light' : 'dark'; saveState(); applyTheme(); }); document.getElementById('pta-settings').addEventListener('click', function (e) { e.stopPropagation(); drawer.classList.add('open'); }); document.getElementById('pta-drawer-close').addEventListener('click', function () { saveSettings(false); drawer.classList.remove('open'); }); function bindDropdownTrigger(triggerId, wrapperId) { document.getElementById(triggerId).addEventListener('click', function (e) { e.stopPropagation(); var wrapper = document.getElementById(wrapperId); if (!wrapper || this.disabled) return; var wasOpen = wrapper.classList.contains('open'); closeModelDropdown(); if (wasOpen) return; clearTimeout(wrapper._ptaCloseTimer); wrapper.classList.remove('closing'); wrapper.classList.add('open'); this.setAttribute('aria-expanded', 'true'); }); } bindDropdownTrigger('pta-model-trigger', 'pta-model-select'); bindDropdownTrigger('pta-language-trigger', 'pta-language-select'); document.addEventListener('click', function (e) { var wrappers = document.querySelectorAll('.pta-model-select'); var inside = Array.prototype.some.call(wrappers, function (wrapper) { return wrapper.contains(e.target); }); if (!inside) closeModelDropdown(); }); document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeModelDropdown(); }); document.querySelectorAll('.pta-provider-card').forEach(function (card) { card.addEventListener('click', function () { if (card.dataset.provider === 'opencode' && !localRuntime.available) return; state.provider = card.dataset.provider; saveState(); syncUI(); flashSaveStatus('已切换并保存'); if (providerReady(state.provider) && !state.configs[state.provider].models.length) loadModels(state.provider, true); }); }); document.getElementById('pta-api-key').addEventListener('input', function () { var value = this.value, config = state.configs[state.provider]; if (value.trim() !== config.apiKey) { config.models = []; config.model = ''; config.modelMeta = {}; config.modelError = ''; config.apiKey = value.trim(); saveState(); syncUI(); this.value = value; flashSaveStatus('已保存'); } clearTimeout(apiKeyTimer); var providerAtInput = state.provider; apiKeyTimer = setTimeout(function () { if (providerAtInput === state.provider && (value.trim().length >= 10 || providerAtInput === 'opencode' && localRuntime.available)) { saveSettings(false); loadModels(providerAtInput, false); } }, 800); }); document.getElementById('pta-api-key').addEventListener('blur', function () { saveSettings(false); if (providerReady(state.provider) && !state.configs[state.provider].models.length) { loadModels(state.provider, false); } }); document.getElementById('pta-language').addEventListener('change', function () { state.language = this.value || 'auto'; saveState(); syncUI(); log('编程语言已设为 ' + languageLabel(state.language), 'info'); }); document.getElementById('pta-model').addEventListener('change', function () { state.configs[state.provider].model = this.value; state.configs[state.provider].modelError = ''; saveState(); syncUI(); flashSaveStatus('已保存'); }); document.getElementById('pta-images').addEventListener('change', function () { state.configs[state.provider].images = this.checked; saveState(); flashSaveStatus('已保存'); }); var maxTokensEl = document.getElementById('pta-max-tokens'); if (maxTokensEl) { maxTokensEl.addEventListener('input', function () { var idx = Number(this.value); var config = state.configs[state.provider]; var currentMeta = config.modelMeta && config.modelMeta[config.model]; var ctxLen = currentMeta && currentMeta.contextLength; var maxRange = resolveModelContextLength(state.provider, config.model, ctxLen); var availableTiers = getAvailableTiers(maxRange); var tier = availableTiers[idx] || availableTiers[0]; config.maxTokens = tier.val; var maxValEl = document.getElementById('pta-max-tokens-val'); if (maxValEl) maxValEl.textContent = tier.val + ' (' + tier.label + ')'; renderMaxTokensScale(availableTiers, idx); saveState(); flashSaveStatus('已保存'); }); } var thinkingEl = document.getElementById('pta-thinking'); if (thinkingEl) { thinkingEl.addEventListener('input', function () { var val = Number(this.value); if (isNaN(val)) val = 2; state.configs[state.provider].thinking = val; var thinkingValEl = document.getElementById('pta-thinking-val'); if (thinkingValEl) thinkingValEl.textContent = THINKING_LABELS[val] || (val + ' 级'); document.querySelectorAll('.pta-thinking-scale .pta-scale-item').forEach(function (item) { var step = Number(item.dataset.step); item.classList.toggle('active', step === val); }); saveState(); flashSaveStatus('已保存'); }); } document.querySelectorAll('.pta-thinking-scale .pta-scale-item').forEach(function (item) { item.addEventListener('click', function () { var step = Number(this.dataset.step); if (isNaN(step)) return; var input = document.getElementById('pta-thinking'); if (input) { input.value = step; input.dispatchEvent(new Event('input', { bubbles: true })); } }); }); document.getElementById('pta-refresh-models').addEventListener('click', function () { saveSettings(false); loadModels(state.provider, false); }); document.getElementById('pta-log-clear').addEventListener('click', function (e) { e.stopPropagation(); document.getElementById('pta-log').innerHTML = '
记录已清空
'; document.getElementById('pta-log-count').textContent = '0'; }); document.getElementById('pta-test').addEventListener('click', async function (e) { e.stopPropagation(); saveSettings(false); if (!providerReady(state.provider)) { setStatus('未配置 Key', PROVIDERS[state.provider].optionalKey ? '请先启动本地 OpenCode Free 代理' : '请先填写 API Key', ''); log('测试失败:当前供应商尚未就绪', 'warning'); return; } toast('正在测试连接…'); try { await requestAI([{ role: 'user', content: '只回复 OK,不要输出思考过程' }], 1024, 0); toast(PROVIDERS[state.provider].name + ' 连接成功'); setStatus('连接正常', '测试通过', 'ok'); log(PROVIDERS[state.provider].name + ' 连接测试通过', 'success'); } catch (error) { toast('错误已记录到面板'); setStatus(/API Key 无效/.test(error.message) ? 'Key 错误' : '连接失败', error.message, ''); log('连接失败:' + error.message, 'warning'); } }); document.getElementById('pta-start').addEventListener('click', function (e) { e.stopPropagation(); if (state.running) stopSolve(); else startSolve(); }); } function saveSettings(flash) { var config = state.configs[state.provider]; var nextKey = (document.getElementById('pta-api-key').value || '').trim(); if (nextKey !== config.apiKey) { config.models = []; config.model = ''; config.modelMeta = {}; config.modelError = ''; } config.apiKey = nextKey; config.model = (document.getElementById('pta-model').value || '').trim(); config.images = !!document.getElementById('pta-images').checked; var maxTokensInput = document.getElementById('pta-max-tokens'); if (maxTokensInput) { var idx = Number(maxTokensInput.value); var currentMeta = config.modelMeta && config.modelMeta[config.model]; var ctxLen = currentMeta && currentMeta.contextLength; var maxRange = resolveModelContextLength(state.provider, config.model, ctxLen); var availableTiers = getAvailableTiers(maxRange); config.maxTokens = (availableTiers[idx] || availableTiers[0]).val; } var thinkingInput = document.getElementById('pta-thinking'); if (thinkingInput) { var tVal = Number(thinkingInput.value); config.thinking = isNaN(tVal) ? 2 : tVal; } state.language = document.getElementById('pta-language').value || 'auto'; state.skipAnswered = document.getElementById('pta-skip').checked; state.continuous = document.getElementById('pta-continuous').checked; saveState(); syncUI(); if (flash !== false && typeof flashSaveStatus === 'function') flashSaveStatus('已保存'); } function loadModels(provider, silent) { var config = state.configs[provider]; if (!providerReady(provider)) { if (!silent) toast(PROVIDERS[provider].optionalKey ? '请先启动本地 OpenCode Free 代理' : '请先填写 API Key'); return; } config.modelsLoading = true; config.modelError = ''; syncUI(); requestModels(provider).then(function (result) { var models = result.ids || []; config.models = models; config.modelMeta = result.meta || {}; if (!config.model || models.indexOf(config.model) < 0) config.model = models[0]; config.modelsLoading = false; config.modelError = ''; saveState(); if (provider === state.provider) syncUI(); toast(PROVIDERS[provider].name + ' 已读取 ' + models.length + ' 个模型'); }).catch(function (error) { config.modelsLoading = false; config.modelError = error.message; if (provider === state.provider) { syncUI(); setStatus(/API Key 无效/.test(error.message) ? 'Key 错误' : '模型读取失败', error.message, ''); log('模型读取失败:' + error.message, 'warning'); } if (!silent) toast('错误已记录到面板'); }); } function setStatus(label, sub, mode) { var dot = document.getElementById('pta-status-dot'), title = document.getElementById('pta-status-label'), text = document.getElementById('pta-status-sub'); if (dot) dot.className = 'pta-status-dot ' + (mode || ''); if (title) title.textContent = label; if (text) text.textContent = sub; } function startSolve() { saveSettings(); if (!providerReady(state.provider)) { toast(PROVIDERS[state.provider].optionalKey ? '请先启动本地 OpenCode Free 代理' : '请先在设置中配置 API Key'); document.getElementById('pta-drawer').classList.add('open'); return; } if (!state.configs[state.provider].model || !state.configs[state.provider].models.length) { toast('请先获取并选择模型'); document.getElementById('pta-drawer').classList.add('open'); loadModels(state.provider, false); return; } state.running = true; var shell = document.getElementById('pta-shell'); if (shell) shell.classList.add('running'); var root = document.getElementById('pta-root'); if (root) root.classList.add('running'); document.getElementById('pta-start').textContent = '停止解题'; document.getElementById('pta-start').classList.add('stop'); setStatus('正在处理', PROVIDERS[state.provider].name + ' 工作中…', 'live'); log('任务开始', 'info'); autoSolve().catch(function (error) { log(error.message || String(error), 'warning'); toast('任务中断'); }).finally(function () { stopSolve(true); }); } function stopSolve(completed) { state.running = false; var shell = document.getElementById('pta-shell'); if (shell) shell.classList.remove('running'); var root = document.getElementById('pta-root'); if (root) root.classList.remove('running'); var button = document.getElementById('pta-start'); if (button) { button.textContent = '开始解题'; button.classList.remove('stop'); } setQuestionProgress(completed ? '已完成' : '已停止', 1, 1); setStatus(completed ? '已完成' : '已停止', completed ? '评分与答题流程已结束' : '任务已暂停', completed ? 'ok' : ''); } function boot() { loadState(); render(); log('面板已就绪,进入具体题目后开始。', 'info'); detectOpenCode(false); localMonitorTimer = setInterval(function () { detectOpenCode(true); }, 15000); if (providerReady(state.provider) && !state.configs[state.provider].models.length) setTimeout(function () { loadModels(state.provider, true); }, 250); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot); else boot(); })();