Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/chengyatsung-art_trade-document-grading-system.json

1 line
63 KiB
JSON

{"repo": "chengyatsung-art/trade-document-grading-system", "files": {"src/pages/Settings.tsx": {"52ddb8ccb6102b56c878a47285aeb5c059eb5c26": {"t": 1785747369.8127651, "n": 15734, "h": "d8731a0af6489171", "c": "import React, { useState, useEffect } from 'react';\nimport { Save, Key, Sliders, Database, Shield, Globe } from 'lucide-react';\n\nexport function Settings() {\n const [engineType, setEngineType] = useState<'builtin' | 'custom'>('builtin');\n const [builtinModel, setBuiltinModel] = useState('gemini-3.5-flash');\n const [customBaseUrl, setCustomBaseUrl] = useState('https://api.deepseek.com/v1');\n const [customApiKey, setCustomApiKey] = useState('');\n const [customModel, setCustomModel] = useState('deepseek-chat');\n const [customGeminiKey, setCustomGeminiKey] = useState('');\n const [customGeminiProxy, setCustomGeminiProxy] = useState('');\n const [coolingDelay, setCoolingDelay] = useState<number>(5);\n const [saved, setSaved] = useState(false);\n\n useEffect(() => {\n const savedEngine = localStorage.getItem('ai_engine_type') as 'builtin' | 'custom';\n if (savedEngine) setEngineType(savedEngine);\n \n const savedBuiltinModel = localStorage.getItem('ai_builtin_model');\n if (savedBuiltinModel) setBuiltinModel(savedBuiltinModel);\n \n const savedBaseUrl = localStorage.getItem('ai_custom_base_url');\n if (savedBaseUrl) setCustomBaseUrl(savedBaseUrl);\n \n const savedApiKey = localStorage.getItem('ai_custom_api_key');\n if (savedApiKey) setCustomApiKey(savedApiKey);\n \n const savedModel = localStorage.getItem('ai_custom_model');\n if (savedModel) setCustomModel(savedModel);\n\n const savedGeminiKey = localStorage.getItem('ai_custom_gemini_key');\n if (savedGeminiKey) setCustomGeminiKey(savedGeminiKey);\n\n const savedGeminiProxy = localStorage.getItem('ai_custom_gemini_proxy');\n if (savedGeminiProxy) setCustomGeminiProxy(savedGeminiProxy);\n\n const savedDelay = localStorage.getItem('ai_cooling_delay');\n if (savedDelay) setCoolingDelay(parseInt(savedDelay, 10) || 5);\n }, []);\n\n const handleSave = () => {\n localStorage.setItem('ai_engine_type', engineType);\n localStorage.setItem('ai_builtin_model', builtinModel);\n localStorage.setItem('ai_custom_base_url', customBaseUrl);\n localStorage.setItem('ai_custom_api_key', customApiKey);\n localStorage.setItem('ai_custom_model', customModel);\n localStorage.setItem('ai_custom_gemini_key', customGeminiKey);\n localStorage.setItem('ai_custom_gemini_proxy', customGeminiProxy);\n localStorage.setItem('ai_cooling_delay', coolingDelay.toString());\n \n setSaved(true);\n setTimeout(() => setSaved(false), 2000);\n };\n\n return (\n <div className=\"p-8 max-w-4xl mx-auto\">\n <h2 className=\"text-2xl font-bold text-slate-900 mb-8\">系统设置</h2>\n\n <div className=\"space-y-6\">\n {/* AI Engine Settings */}\n <div className=\"bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden\">\n <div className=\"px-6 py-4 border-b border-slate-200 bg-slate-50 flex items-center gap-2\">\n <Sliders className=\"text-blue-600\" size={20} />\n <h3 className=\"font-semibold text-slate-800\">AI 批改引擎配置</h3>\n </div>\n <div className=\"p-6 space-y-6\">\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-2\">\n 批改引擎服务商\n </label>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div \n className={`border rounded-lg p-4 cursor-pointer transition-colors ${engineType === 'builtin' ? 'border-blue-500 bg-blue-50' : 'border-slate-200 hover:border-blue-200'}`}\n onClick={() => setEngineType('builtin')}\n >\n <div className=\"flex items-center gap-2 mb-1\">\n <div className={`w-3 h-3 rounded-full ${engineType === 'builtin' ? 'bg-blue-500' : 'bg-slate-300'}`}></div>\n <p className=\"font-medium text-slate-800\">AI Studio 内置引擎</p>\n </div>\n <p className=\"text-sm text-slate-500 ml-5\">使用当前环境默认的 Gemini 大模型,无需配置 API Key。</p>\n </div>\n \n <div \n className={`border rounded-lg p-4 cursor-pointer transition-colors ${engineType === 'custom' ? 'border-blue-500 bg-blue-50' : 'border-slate-200 hover:border-blue-200'}`}\n onClick={() => setEngineType('custom')}\n >\n <div className=\"flex items-center gap-2 mb-1\">\n <div className={`w-3 h-3 rounded-full ${engineType === 'custom' ? 'bg-blue-500' : 'bg-slate-300'}`}></div>\n <p className=\"font-medium text-slate-800\">自定义国内模型 (OpenAI 兼容)</p>\n </div>\n <p className=\"text-sm text-slate-500 ml-5\">支持 DeepSeek、智谱 GLM、通义千问等国内大模型接口。</p>\n </div>\n </div>\n </div>\n\n {engineType === 'builtin' && (\n <div className=\"space-y-4 p-5 bg-slate-50 border border-slate-200 rounded-lg animate-in fade-in slide-in-from-top-2\">\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-2\">\n 默认大模型选择\n </label>\n <select \n value={builtinModel}\n onChange={(e) => setBuiltinModel(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n >\n <option value=\"gemini-3.5-flash\">Gemini 3.5 Flash (最新最快,速度极快且额度充足 - 推荐)</option>\n <option value=\"gemini-3.1-pro-preview\">Gemini 3.1 Pro (推理能力强,适合极其复杂的单证推理)</option>\n <option value=\"gemini-3.1-flash-lite\">Gemini 3.1 Flash Lite (极速轻量,节省流量和额度,适合高速批量批改)</option>\n </select>\n <p className=\"text-sm text-slate-500 mt-2\">\n Flash 模型是 Google 最新的 3.5 系列大模型,批改速度极快且额度高(限制 15次/分钟);Lite 模型则是超轻量高速模型,适合大规模批改作业(限制 30次/分钟);Pro 模型推理能力极强但频控最严格(限制 2次/分钟)。\n </p>\n </div>\n\n <div className=\"mt-4 border-t border-slate-200 pt-4\">\n <label className=\"block text-sm font-medium text-slate-700 mb-2 flex items-center gap-2\">\n <Key size={16} className=\"text-slate-400\" />\n 您专属的 Gemini API Key (可选配置,用于自主额度)\n </label>\n <input \n type=\"password\" \n value={customGeminiKey}\n onChange={(e) => setCustomGeminiKey(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"AIzaSy... (若留空,则默认使用系统的共享免费免费体验额度)\"\n />\n <p className=\"text-xs text-slate-500 mt-2 leading-relaxed\">\n 🌟 <span>提示:如果连续批改多份大文件触发了系统的共享频次限制 (429) 或提示额度超限,强烈建议前往 </span>\n <a href=\"https://aistudio.google.com/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 font-medium hover:underline inline-flex items-center\">\n Google AI Studio 官网\n </a>\n <span> 免费获取您个人的 API Key 并填入此处。配置后页面将直接通过您的独立账号发起安全连接,彻底告别公共额度排队和限流。</span>\n </p>\n </div>\n\n <div className=\"mt-4 border-t border-slate-200 pt-4\">\n <label className=\"block text-sm font-medium text-slate-700 mb-2 flex items-center gap-2\">\n <Globe size={16} className=\"text-slate-400\" />\n Gemini API 代理地址 (可选,配置后可绕过网络或特定区域限制)\n </label>\n <input \n type=\"text\" \n value={customGeminiProxy}\n onChange={(e) => setCustomGeminiProxy(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"例如:https://api.example.com/v1\"\n />\n <p className=\"text-xs text-slate-500 mt-2 leading-relaxed\">\n ⚙️ 若您所在的网络环境无法直接连接 Google 官方服务器,或者使用了第三方反向代理/转发接口,可在此处指定代理 Base URL。留空则直接连接 Google 官方接口。\n </p>\n </div>\n\n <div className=\"mt-4 bg-amber-50 border border-amber-200 rounded-lg p-4 text-xs text-amber-800 leading-relaxed shadow-sm\">\n <span className=\"font-semibold block mb-1 text-amber-950\">💡 常见问题与解答:</span>\n <p className=\"mb-2\"><strong>问:为什么首页的「今日 API 请求额度 (本地预估)」显示为 0 次,依然会报错“user has exceeded quota”?</strong></p>\n <p className=\"mb-2\">答:这是一个本地离线计数器,它仅通过您本地浏览器的历史批改记录来<strong>估算</strong>您发送的文档件数,它<strong>并不是</strong>去 Google 服务器查询 API 额度。如果其他的教师正在共用内置的系统额度,或者您虽然使用了个人免费 Key,但因为单次上传作业多、文件大而触发了以下限制,也会导致该报错。</p>\n <p className=\"mb-2\"><strong>问:我已经配置了两个专属的免费 Gemini API Key,为什么还是会经常报 “exceeded quota / 429 频限” 错误?</strong></p>\n <p className=\"mb-1\">答:Google 提供的免费 API Key 有非常严格的限制。虽然它的总额度足够大,但在批量使用中:</p>\n <ul className=\"list-disc pl-4 mb-2 space-y-0.5\">\n <li><strong>RPM(每分钟请求数)</strong> 限制为 15 次。</li>\n <li><strong>TPM(每分钟 Token 数量限额)</strong> 被大文件瞬间充盈(批改作业时,尤其是<strong>图片和 PDF 格式大文件</strong>,会包含海量的字节并消耗巨量 Token),在一分钟内发送多份会导致 TPM 额度瞬间超支,立即被 Google 服务器暂停数分钟。</li>\n </ul>\n <p className=\"mb-2 font-medium text-amber-950\">🚀 极速解决办法:</p>\n <ol className=\"list-decimal pl-4 space-y-1\">\n <li>在下方「常规设置」中,将<strong>「批量批改间隔冷却时间」从默认的 5 秒调大到 15 秒或 20 秒</strong>。这能有效拉开排队间隔,防止短时间内累计的 Token 超过每分钟限制。</li>\n <li>如果您要追求极高的并发或者极速不间断地批改大批量文件,推荐<strong>切换至上方的「自定义国内模型」</strong>,选用如 <strong>DeepSeek</strong>、智谱、Kimi 等服务,其每百万 Token 的单价极其低廉且完全不设低规格速率限制!</li>\n </ol>\n </div>\n </div>\n )}\n\n {engineType === 'custom' && (\n <div className=\"space-y-4 p-5 bg-slate-50 border border-slate-200 rounded-lg animate-in fade-in slide-in-from-top-2\">\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-1 flex items-center gap-2\">\n <Globe size={16} className=\"text-slate-400\" />\n 接口地址 (Base URL)\n </label>\n <input \n type=\"text\" \n value={customBaseUrl}\n onChange={(e) => setCustomBaseUrl(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"例如:https://api.deepseek.com/v1\"\n />\n <p className=\"text-xs text-slate-500 mt-1\">\n 常用地址:DeepSeek (https://api.deepseek.com/v1) | 智谱 (https://open.bigmodel.cn/api/paas/v4)\n </p>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-1 flex items-center gap-2\">\n <Key size={16} className=\"text-slate-400\" />\n API Key\n </label>\n <input \n type=\"password\" \n value={customApiKey}\n onChange={(e) => setCustomApiKey(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"sk-...\"\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-1\">\n 模型名称 (Model)\n </label>\n <input \n type=\"text\" \n value={customModel}\n onChange={(e) => setCustomModel(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"例如:deepseek-chat\"\n />\n <p className=\"text-xs text-slate-500 mt-1\">\n 常用模型:deepseek-chat, glm-4-flash, qwen-plus\n </p>\n </div>\n </div>\n )}\n\n {/* Shared Frequency/Throttle Settings */}\n <div className=\"border-t border-slate-100 pt-5 mt-5\">\n <label className=\"block text-sm font-medium text-slate-700 mb-2\">\n 批量批改间隔冷却时间 (秒)\n </label>\n <div className=\"flex items-center gap-3\">\n <input\n type=\"number\"\n min=\"0\"\n max=\"60\"\n value={coolingDelay}\n onChange={(e) => setCoolingDelay(Math.max(0, parseInt(e.target.value, 10) || 0))}\n className=\"w-32 border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n />\n <span className=\"text-sm text-slate-600\">秒</span>\n </div>\n <p className=\"text-xs text-slate-400 mt-2\">\n * 默认值为 5 秒。如果在连续批改多份作业时遭遇 429 报错,建议调大该值(如 10 秒或 15 秒)以降低请求频率。\n </p>\n </div>\n </div>\n </div>\n\n {/* Local Storage Settings */}\n <div className=\"bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden\">\n <div className=\"px-6 py-4 border-b border-slate-200 bg-slate-50 flex items-center gap-2\">\n <Database className=\"text-blue-600\" size={20} />\n <h3 className=\"font-semibold text-slate-800\">本地数据与隐私</h3>\n </div>\n <div className=\"p-6 space-y-4\">\n <div className=\"flex items-center justify-between p-4 border border-slate-200 rounded-lg\">\n <div className=\"flex items-start gap-3\">\n <Shield className=\"text-green-600 mt-0.5\" size={20} />\n <div>\n <p className=\"font-medium text-slate-800\">纯前端模式 (Serverless)</p>\n <p className=\"text-sm text-slate-500 mt-1\">\n 当前系统运行在纯前端模式,所有学生文件均在本地浏览器解析,不会上传到任何第三方服务器(除了将文本发送给 Google Gemini API 进行批改)。\n </p>\n </div>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between p-4 border border-slate-200 rounded-lg\">\n <div>\n <p className=\"font-medium text-slate-800\">保留历史记录</p>\n <p className=\"text-sm text-slate-500 mt-1\">在浏览器本地存储 (LocalStorage) 中保留最近的批改任务记录</p>\n </div>\n <label className=\"relative inline-flex items-center cursor-pointer\">\n <input type=\"checkbox\" className=\"sr-only peer\" defaultChecked />\n <div className=\"w-11 h-6 bg-slate-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600\"></div>\n </label>\n </div>\n </div>\n </div>\n\n <div className=\"flex justify-end\">\n <button \n onClick={handleSave}\n className=\"bg-blue-600 hover:bg-blue-700 text-white px-8 py-2.5 rounded-lg font-medium transition-colors flex items-center gap-2\"\n >\n <Save size={18} />\n {saved ? '已保存' : '保存设置'}\n </button>\n </div>\n </div>\n </div>\n );\n}\n"}, "3166386d2ba3e674950c50758658818eee773134": {"t": 1785772863.552391, "n": 15734, "h": "d8731a0af6489171", "c": "import React, { useState, useEffect } from 'react';\nimport { Save, Key, Sliders, Database, Shield, Globe } from 'lucide-react';\n\nexport function Settings() {\n const [engineType, setEngineType] = useState<'builtin' | 'custom'>('builtin');\n const [builtinModel, setBuiltinModel] = useState('gemini-3.5-flash');\n const [customBaseUrl, setCustomBaseUrl] = useState('https://api.deepseek.com/v1');\n const [customApiKey, setCustomApiKey] = useState('');\n const [customModel, setCustomModel] = useState('deepseek-chat');\n const [customGeminiKey, setCustomGeminiKey] = useState('');\n const [customGeminiProxy, setCustomGeminiProxy] = useState('');\n const [coolingDelay, setCoolingDelay] = useState<number>(5);\n const [saved, setSaved] = useState(false);\n\n useEffect(() => {\n const savedEngine = localStorage.getItem('ai_engine_type') as 'builtin' | 'custom';\n if (savedEngine) setEngineType(savedEngine);\n \n const savedBuiltinModel = localStorage.getItem('ai_builtin_model');\n if (savedBuiltinModel) setBuiltinModel(savedBuiltinModel);\n \n const savedBaseUrl = localStorage.getItem('ai_custom_base_url');\n if (savedBaseUrl) setCustomBaseUrl(savedBaseUrl);\n \n const savedApiKey = localStorage.getItem('ai_custom_api_key');\n if (savedApiKey) setCustomApiKey(savedApiKey);\n \n const savedModel = localStorage.getItem('ai_custom_model');\n if (savedModel) setCustomModel(savedModel);\n\n const savedGeminiKey = localStorage.getItem('ai_custom_gemini_key');\n if (savedGeminiKey) setCustomGeminiKey(savedGeminiKey);\n\n const savedGeminiProxy = localStorage.getItem('ai_custom_gemini_proxy');\n if (savedGeminiProxy) setCustomGeminiProxy(savedGeminiProxy);\n\n const savedDelay = localStorage.getItem('ai_cooling_delay');\n if (savedDelay) setCoolingDelay(parseInt(savedDelay, 10) || 5);\n }, []);\n\n const handleSave = () => {\n localStorage.setItem('ai_engine_type', engineType);\n localStorage.setItem('ai_builtin_model', builtinModel);\n localStorage.setItem('ai_custom_base_url', customBaseUrl);\n localStorage.setItem('ai_custom_api_key', customApiKey);\n localStorage.setItem('ai_custom_model', customModel);\n localStorage.setItem('ai_custom_gemini_key', customGeminiKey);\n localStorage.setItem('ai_custom_gemini_proxy', customGeminiProxy);\n localStorage.setItem('ai_cooling_delay', coolingDelay.toString());\n \n setSaved(true);\n setTimeout(() => setSaved(false), 2000);\n };\n\n return (\n <div className=\"p-8 max-w-4xl mx-auto\">\n <h2 className=\"text-2xl font-bold text-slate-900 mb-8\">系统设置</h2>\n\n <div className=\"space-y-6\">\n {/* AI Engine Settings */}\n <div className=\"bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden\">\n <div className=\"px-6 py-4 border-b border-slate-200 bg-slate-50 flex items-center gap-2\">\n <Sliders className=\"text-blue-600\" size={20} />\n <h3 className=\"font-semibold text-slate-800\">AI 批改引擎配置</h3>\n </div>\n <div className=\"p-6 space-y-6\">\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-2\">\n 批改引擎服务商\n </label>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div \n className={`border rounded-lg p-4 cursor-pointer transition-colors ${engineType === 'builtin' ? 'border-blue-500 bg-blue-50' : 'border-slate-200 hover:border-blue-200'}`}\n onClick={() => setEngineType('builtin')}\n >\n <div className=\"flex items-center gap-2 mb-1\">\n <div className={`w-3 h-3 rounded-full ${engineType === 'builtin' ? 'bg-blue-500' : 'bg-slate-300'}`}></div>\n <p className=\"font-medium text-slate-800\">AI Studio 内置引擎</p>\n </div>\n <p className=\"text-sm text-slate-500 ml-5\">使用当前环境默认的 Gemini 大模型,无需配置 API Key。</p>\n </div>\n \n <div \n className={`border rounded-lg p-4 cursor-pointer transition-colors ${engineType === 'custom' ? 'border-blue-500 bg-blue-50' : 'border-slate-200 hover:border-blue-200'}`}\n onClick={() => setEngineType('custom')}\n >\n <div className=\"flex items-center gap-2 mb-1\">\n <div className={`w-3 h-3 rounded-full ${engineType === 'custom' ? 'bg-blue-500' : 'bg-slate-300'}`}></div>\n <p className=\"font-medium text-slate-800\">自定义国内模型 (OpenAI 兼容)</p>\n </div>\n <p className=\"text-sm text-slate-500 ml-5\">支持 DeepSeek、智谱 GLM、通义千问等国内大模型接口。</p>\n </div>\n </div>\n </div>\n\n {engineType === 'builtin' && (\n <div className=\"space-y-4 p-5 bg-slate-50 border border-slate-200 rounded-lg animate-in fade-in slide-in-from-top-2\">\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-2\">\n 默认大模型选择\n </label>\n <select \n value={builtinModel}\n onChange={(e) => setBuiltinModel(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n >\n <option value=\"gemini-3.5-flash\">Gemini 3.5 Flash (最新最快,速度极快且额度充足 - 推荐)</option>\n <option value=\"gemini-3.1-pro-preview\">Gemini 3.1 Pro (推理能力强,适合极其复杂的单证推理)</option>\n <option value=\"gemini-3.1-flash-lite\">Gemini 3.1 Flash Lite (极速轻量,节省流量和额度,适合高速批量批改)</option>\n </select>\n <p className=\"text-sm text-slate-500 mt-2\">\n Flash 模型是 Google 最新的 3.5 系列大模型,批改速度极快且额度高(限制 15次/分钟);Lite 模型则是超轻量高速模型,适合大规模批改作业(限制 30次/分钟);Pro 模型推理能力极强但频控最严格(限制 2次/分钟)。\n </p>\n </div>\n\n <div className=\"mt-4 border-t border-slate-200 pt-4\">\n <label className=\"block text-sm font-medium text-slate-700 mb-2 flex items-center gap-2\">\n <Key size={16} className=\"text-slate-400\" />\n 您专属的 Gemini API Key (可选配置,用于自主额度)\n </label>\n <input \n type=\"password\" \n value={customGeminiKey}\n onChange={(e) => setCustomGeminiKey(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"AIzaSy... (若留空,则默认使用系统的共享免费免费体验额度)\"\n />\n <p className=\"text-xs text-slate-500 mt-2 leading-relaxed\">\n 🌟 <span>提示:如果连续批改多份大文件触发了系统的共享频次限制 (429) 或提示额度超限,强烈建议前往 </span>\n <a href=\"https://aistudio.google.com/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 font-medium hover:underline inline-flex items-center\">\n Google AI Studio 官网\n </a>\n <span> 免费获取您个人的 API Key 并填入此处。配置后页面将直接通过您的独立账号发起安全连接,彻底告别公共额度排队和限流。</span>\n </p>\n </div>\n\n <div className=\"mt-4 border-t border-slate-200 pt-4\">\n <label className=\"block text-sm font-medium text-slate-700 mb-2 flex items-center gap-2\">\n <Globe size={16} className=\"text-slate-400\" />\n Gemini API 代理地址 (可选,配置后可绕过网络或特定区域限制)\n </label>\n <input \n type=\"text\" \n value={customGeminiProxy}\n onChange={(e) => setCustomGeminiProxy(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"例如:https://api.example.com/v1\"\n />\n <p className=\"text-xs text-slate-500 mt-2 leading-relaxed\">\n ⚙️ 若您所在的网络环境无法直接连接 Google 官方服务器,或者使用了第三方反向代理/转发接口,可在此处指定代理 Base URL。留空则直接连接 Google 官方接口。\n </p>\n </div>\n\n <div className=\"mt-4 bg-amber-50 border border-amber-200 rounded-lg p-4 text-xs text-amber-800 leading-relaxed shadow-sm\">\n <span className=\"font-semibold block mb-1 text-amber-950\">💡 常见问题与解答:</span>\n <p className=\"mb-2\"><strong>问:为什么首页的「今日 API 请求额度 (本地预估)」显示为 0 次,依然会报错“user has exceeded quota”?</strong></p>\n <p className=\"mb-2\">答:这是一个本地离线计数器,它仅通过您本地浏览器的历史批改记录来<strong>估算</strong>您发送的文档件数,它<strong>并不是</strong>去 Google 服务器查询 API 额度。如果其他的教师正在共用内置的系统额度,或者您虽然使用了个人免费 Key,但因为单次上传作业多、文件大而触发了以下限制,也会导致该报错。</p>\n <p className=\"mb-2\"><strong>问:我已经配置了两个专属的免费 Gemini API Key,为什么还是会经常报 “exceeded quota / 429 频限” 错误?</strong></p>\n <p className=\"mb-1\">答:Google 提供的免费 API Key 有非常严格的限制。虽然它的总额度足够大,但在批量使用中:</p>\n <ul className=\"list-disc pl-4 mb-2 space-y-0.5\">\n <li><strong>RPM(每分钟请求数)</strong> 限制为 15 次。</li>\n <li><strong>TPM(每分钟 Token 数量限额)</strong> 被大文件瞬间充盈(批改作业时,尤其是<strong>图片和 PDF 格式大文件</strong>,会包含海量的字节并消耗巨量 Token),在一分钟内发送多份会导致 TPM 额度瞬间超支,立即被 Google 服务器暂停数分钟。</li>\n </ul>\n <p className=\"mb-2 font-medium text-amber-950\">🚀 极速解决办法:</p>\n <ol className=\"list-decimal pl-4 space-y-1\">\n <li>在下方「常规设置」中,将<strong>「批量批改间隔冷却时间」从默认的 5 秒调大到 15 秒或 20 秒</strong>。这能有效拉开排队间隔,防止短时间内累计的 Token 超过每分钟限制。</li>\n <li>如果您要追求极高的并发或者极速不间断地批改大批量文件,推荐<strong>切换至上方的「自定义国内模型」</strong>,选用如 <strong>DeepSeek</strong>、智谱、Kimi 等服务,其每百万 Token 的单价极其低廉且完全不设低规格速率限制!</li>\n </ol>\n </div>\n </div>\n )}\n\n {engineType === 'custom' && (\n <div className=\"space-y-4 p-5 bg-slate-50 border border-slate-200 rounded-lg animate-in fade-in slide-in-from-top-2\">\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-1 flex items-center gap-2\">\n <Globe size={16} className=\"text-slate-400\" />\n 接口地址 (Base URL)\n </label>\n <input \n type=\"text\" \n value={customBaseUrl}\n onChange={(e) => setCustomBaseUrl(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"例如:https://api.deepseek.com/v1\"\n />\n <p className=\"text-xs text-slate-500 mt-1\">\n 常用地址:DeepSeek (https://api.deepseek.com/v1) | 智谱 (https://open.bigmodel.cn/api/paas/v4)\n </p>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-1 flex items-center gap-2\">\n <Key size={16} className=\"text-slate-400\" />\n API Key\n </label>\n <input \n type=\"password\" \n value={customApiKey}\n onChange={(e) => setCustomApiKey(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"sk-...\"\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-1\">\n 模型名称 (Model)\n </label>\n <input \n type=\"text\" \n value={customModel}\n onChange={(e) => setCustomModel(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"例如:deepseek-chat\"\n />\n <p className=\"text-xs text-slate-500 mt-1\">\n 常用模型:deepseek-chat, glm-4-flash, qwen-plus\n </p>\n </div>\n </div>\n )}\n\n {/* Shared Frequency/Throttle Settings */}\n <div className=\"border-t border-slate-100 pt-5 mt-5\">\n <label className=\"block text-sm font-medium text-slate-700 mb-2\">\n 批量批改间隔冷却时间 (秒)\n </label>\n <div className=\"flex items-center gap-3\">\n <input\n type=\"number\"\n min=\"0\"\n max=\"60\"\n value={coolingDelay}\n onChange={(e) => setCoolingDelay(Math.max(0, parseInt(e.target.value, 10) || 0))}\n className=\"w-32 border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n />\n <span className=\"text-sm text-slate-600\">秒</span>\n </div>\n <p className=\"text-xs text-slate-400 mt-2\">\n * 默认值为 5 秒。如果在连续批改多份作业时遭遇 429 报错,建议调大该值(如 10 秒或 15 秒)以降低请求频率。\n </p>\n </div>\n </div>\n </div>\n\n {/* Local Storage Settings */}\n <div className=\"bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden\">\n <div className=\"px-6 py-4 border-b border-slate-200 bg-slate-50 flex items-center gap-2\">\n <Database className=\"text-blue-600\" size={20} />\n <h3 className=\"font-semibold text-slate-800\">本地数据与隐私</h3>\n </div>\n <div className=\"p-6 space-y-4\">\n <div className=\"flex items-center justify-between p-4 border border-slate-200 rounded-lg\">\n <div className=\"flex items-start gap-3\">\n <Shield className=\"text-green-600 mt-0.5\" size={20} />\n <div>\n <p className=\"font-medium text-slate-800\">纯前端模式 (Serverless)</p>\n <p className=\"text-sm text-slate-500 mt-1\">\n 当前系统运行在纯前端模式,所有学生文件均在本地浏览器解析,不会上传到任何第三方服务器(除了将文本发送给 Google Gemini API 进行批改)。\n </p>\n </div>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between p-4 border border-slate-200 rounded-lg\">\n <div>\n <p className=\"font-medium text-slate-800\">保留历史记录</p>\n <p className=\"text-sm text-slate-500 mt-1\">在浏览器本地存储 (LocalStorage) 中保留最近的批改任务记录</p>\n </div>\n <label className=\"relative inline-flex items-center cursor-pointer\">\n <input type=\"checkbox\" className=\"sr-only peer\" defaultChecked />\n <div className=\"w-11 h-6 bg-slate-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600\"></div>\n </label>\n </div>\n </div>\n </div>\n\n <div className=\"flex justify-end\">\n <button \n onClick={handleSave}\n className=\"bg-blue-600 hover:bg-blue-700 text-white px-8 py-2.5 rounded-lg font-medium transition-colors flex items-center gap-2\"\n >\n <Save size={18} />\n {saved ? '已保存' : '保存设置'}\n </button>\n </div>\n </div>\n </div>\n );\n}\n"}, "2fccec9f54bffe41452e4c7804cdcd4128f77426": {"t": 1785772869.0975354, "n": 15586, "h": "459c321f97d942da", "c": "import React, { useState, useEffect } from 'react';\nimport { Save, Key, Sliders, Database, Shield, Globe } from 'lucide-react';\n\nexport function Settings() {\n const [engineType, setEngineType] = useState<'builtin' | 'custom'>('builtin');\n const [builtinModel, setBuiltinModel] = useState('gemini-3.5-flash');\n const [customBaseUrl, setCustomBaseUrl] = useState('https://api.deepseek.com/v1');\n const [customApiKey, setCustomApiKey] = useState('');\n const [customModel, setCustomModel] = useState('deepseek-chat');\n const [customGeminiKey, setCustomGeminiKey] = useState('');\n const [customGeminiProxy, setCustomGeminiProxy] = useState('');\n const [coolingDelay, setCoolingDelay] = useState<number>(5);\n const [saved, setSaved] = useState(false);\n\n useEffect(() => {\n const savedEngine = localStorage.getItem('ai_engine_type') as 'builtin' | 'custom';\n if (savedEngine) setEngineType(savedEngine);\n \n const savedBuiltinModel = localStorage.getItem('ai_builtin_model');\n if (savedBuiltinModel) setBuiltinModel(savedBuiltinModel);\n \n const savedBaseUrl = localStorage.getItem('ai_custom_base_url');\n if (savedBaseUrl) setCustomBaseUrl(savedBaseUrl);\n \n const savedApiKey = localStorage.getItem('ai_custom_api_key');\n if (savedApiKey) setCustomApiKey(savedApiKey);\n \n const savedModel = localStorage.getItem('ai_custom_model');\n if (savedModel) setCustomModel(savedModel);\n\n const savedGeminiKey = localStorage.getItem('ai_custom_gemini_key');\n if (savedGeminiKey) setCustomGeminiKey(savedGeminiKey);\n\n const savedGeminiProxy = localStorage.getItem('ai_custom_gemini_proxy');\n if (savedGeminiProxy) setCustomGeminiProxy(savedGeminiProxy);\n\n const savedDelay = localStorage.getItem('ai_cooling_delay');\n if (savedDelay) setCoolingDelay(parseInt(savedDelay, 10) || 5);\n }, []);\n\n const handleSave = () => {\n localStorage.setItem('ai_engine_type', engineType);\n localStorage.setItem('ai_builtin_model', builtinModel);\n localStorage.setItem('ai_custom_base_url', customBaseUrl);\n localStorage.setItem('ai_custom_api_key', customApiKey);\n localStorage.setItem('ai_custom_model', customModel);\n localStorage.setItem('ai_custom_gemini_key', customGeminiKey);\n localStorage.setItem('ai_custom_gemini_proxy', customGeminiProxy);\n localStorage.setItem('ai_cooling_delay', coolingDelay.toString());\n \n setSaved(true);\n setTimeout(() => setSaved(false), 2000);\n };\n\n return (\n <div className=\"p-8 max-w-4xl mx-auto\">\n <h2 className=\"text-2xl font-bold text-slate-900 mb-8\">系统设置</h2>\n\n <div className=\"space-y-6\">\n {/* AI Engine Settings */}\n <div className=\"bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden\">\n <div className=\"px-6 py-4 border-b border-slate-200 bg-slate-50 flex items-center gap-2\">\n <Sliders className=\"text-blue-600\" size={20} />\n <h3 className=\"font-semibold text-slate-800\">AI 批改引擎配置</h3>\n </div>\n <div className=\"p-6 space-y-6\">\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-2\">\n 批改引擎服务商\n </label>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div \n className={`border rounded-lg p-4 cursor-pointer transition-colors ${engineType === 'builtin' ? 'border-blue-500 bg-blue-50' : 'border-slate-200 hover:border-blue-200'}`}\n onClick={() => setEngineType('builtin')}\n >\n <div className=\"flex items-center gap-2 mb-1\">\n <div className={`w-3 h-3 rounded-full ${engineType === 'builtin' ? 'bg-blue-500' : 'bg-slate-300'}`}></div>\n <p className=\"font-medium text-slate-800\">AI Studio 内置引擎</p>\n </div>\n <p className=\"text-sm text-slate-500 ml-5\">使用当前环境默认的 Gemini 大模型,无需配置 API Key。</p>\n </div>\n \n <div \n className={`border rounded-lg p-4 cursor-pointer transition-colors ${engineType === 'custom' ? 'border-blue-500 bg-blue-50' : 'border-slate-200 hover:border-blue-200'}`}\n onClick={() => setEngineType('custom')}\n >\n <div className=\"flex items-center gap-2 mb-1\">\n <div className={`w-3 h-3 rounded-full ${engineType === 'custom' ? 'bg-blue-500' : 'bg-slate-300'}`}></div>\n <p className=\"font-medium text-slate-800\">自定义国内模型 (OpenAI 兼容)</p>\n </div>\n <p className=\"text-sm text-slate-500 ml-5\">支持 DeepSeek、智谱 GLM、通义千问等国内大模型接口。</p>\n </div>\n </div>\n </div>\n\n {engineType === 'builtin' && (\n <div className=\"space-y-4 p-5 bg-slate-50 border border-slate-200 rounded-lg animate-in fade-in slide-in-from-top-2\">\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-2\">\n 默认大模型选择\n </label>\n <select \n value={builtinModel}\n onChange={(e) => setBuiltinModel(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n >\n <option value=\"gemini-3.5-flash\">Gemini 3.5 Flash (最新最快,速度极快且额度充足 - 推荐)</option>\n <option value=\"gemini-3.1-pro-preview\">Gemini 3.1 Pro (推理能力强,适合极其复杂的单证推理)</option>\n </select>\n <p className=\"text-sm text-slate-500 mt-2\">\n Flash 模型是 Google 最新的 3.5 系列轻量化大模型,批改速度极快且额度高(限制 15次/分钟),Pro 模型推理能力极强但限制较严格(限制 2次/分钟)。\n </p>\n </div>\n\n <div className=\"mt-4 border-t border-slate-200 pt-4\">\n <label className=\"block text-sm font-medium text-slate-700 mb-2 flex items-center gap-2\">\n <Key size={16} className=\"text-slate-400\" />\n 您专属的 Gemini API Key (可选配置,用于自主额度)\n </label>\n <input \n type=\"password\" \n value={customGeminiKey}\n onChange={(e) => setCustomGeminiKey(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"AIzaSy... (若留空,则默认使用系统的共享免费免费体验额度)\"\n />\n <p className=\"text-xs text-slate-500 mt-2 leading-relaxed\">\n 🌟 <span>提示:如果连续批改多份大文件触发了系统的共享频次限制 (429) 或提示额度超限,强烈建议前往 </span>\n <a href=\"https://aistudio.google.com/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 font-medium hover:underline inline-flex items-center\">\n Google AI Studio 官网\n </a>\n <span> 免费获取您个人的 API Key 并填入此处。配置后页面将直接通过您的独立账号发起安全连接,彻底告别公共额度排队和限流。</span>\n </p>\n </div>\n\n <div className=\"mt-4 border-t border-slate-200 pt-4\">\n <label className=\"block text-sm font-medium text-slate-700 mb-2 flex items-center gap-2\">\n <Globe size={16} className=\"text-slate-400\" />\n Gemini API 代理地址 (可选,配置后可绕过网络或特定区域限制)\n </label>\n <input \n type=\"text\" \n value={customGeminiProxy}\n onChange={(e) => setCustomGeminiProxy(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"例如:https://api.example.com/v1\"\n />\n <p className=\"text-xs text-slate-500 mt-2 leading-relaxed\">\n ⚙️ 若您所在的网络环境无法直接连接 Google 官方服务器,或者使用了第三方反向代理/转发接口,可在此处指定代理 Base URL。留空则直接连接 Google 官方接口。\n </p>\n </div>\n\n <div className=\"mt-4 bg-amber-50 border border-amber-200 rounded-lg p-4 text-xs text-amber-800 leading-relaxed shadow-sm\">\n <span className=\"font-semibold block mb-1 text-amber-950\">💡 常见问题与解答:</span>\n <p className=\"mb-2\"><strong>问:为什么首页的「今日 API 请求额度 (本地预估)」显示为 0 次,依然会报错“user has exceeded quota”?</strong></p>\n <p className=\"mb-2\">答:这是一个本地离线计数器,它仅通过您本地浏览器的历史批改记录来<strong>估算</strong>您发送的文档件数,它<strong>并不是</strong>去 Google 服务器查询 API 额度。如果其他的教师正在共用内置的系统额度,或者您虽然使用了个人免费 Key,但因为单次上传作业多、文件大而触发了以下限制,也会导致该报错。</p>\n <p className=\"mb-2\"><strong>问:我已经配置了两个专属的免费 Gemini API Key,为什么还是会经常报 “exceeded quota / 429 频限” 错误?</strong></p>\n <p className=\"mb-1\">答:Google 提供的免费 API Key 有非常严格的限制。虽然它的总额度足够大,但在批量使用中:</p>\n <ul className=\"list-disc pl-4 mb-2 space-y-0.5\">\n <li><strong>RPM(每分钟请求数)</strong> 限制为 15 次。</li>\n <li><strong>TPM(每分钟 Token 数量限额)</strong> 被大文件瞬间充盈(批改作业时,尤其是<strong>图片和 PDF 格式大文件</strong>,会包含海量的字节并消耗巨量 Token),在一分钟内发送多份会导致 TPM 额度瞬间超支,立即被 Google 服务器暂停数分钟。</li>\n </ul>\n <p className=\"mb-2 font-medium text-amber-950\">🚀 极速解决办法:</p>\n <ol className=\"list-decimal pl-4 space-y-1\">\n <li>在下方「常规设置」中,将<strong>「批量批改间隔冷却时间」从默认的 5 秒调大到 15 秒或 20 秒</strong>。这能有效拉开排队间隔,防止短时间内累计的 Token 超过每分钟限制。</li>\n <li>如果您要追求极高的并发或者极速不间断地批改大批量文件,推荐<strong>切换至上方的「自定义国内模型」</strong>,选用如 <strong>DeepSeek</strong>、智谱、Kimi 等服务,其每百万 Token 的单价极其低廉且完全不设低规格速率限制!</li>\n </ol>\n </div>\n </div>\n )}\n\n {engineType === 'custom' && (\n <div className=\"space-y-4 p-5 bg-slate-50 border border-slate-200 rounded-lg animate-in fade-in slide-in-from-top-2\">\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-1 flex items-center gap-2\">\n <Globe size={16} className=\"text-slate-400\" />\n 接口地址 (Base URL)\n </label>\n <input \n type=\"text\" \n value={customBaseUrl}\n onChange={(e) => setCustomBaseUrl(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"例如:https://api.deepseek.com/v1\"\n />\n <p className=\"text-xs text-slate-500 mt-1\">\n 常用地址:DeepSeek (https://api.deepseek.com/v1) | 智谱 (https://open.bigmodel.cn/api/paas/v4)\n </p>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-1 flex items-center gap-2\">\n <Key size={16} className=\"text-slate-400\" />\n API Key\n </label>\n <input \n type=\"password\" \n value={customApiKey}\n onChange={(e) => setCustomApiKey(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"sk-...\"\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-1\">\n 模型名称 (Model)\n </label>\n <input \n type=\"text\" \n value={customModel}\n onChange={(e) => setCustomModel(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"例如:deepseek-chat\"\n />\n <p className=\"text-xs text-slate-500 mt-1\">\n 常用模型:deepseek-chat, glm-4-flash, qwen-plus\n </p>\n </div>\n </div>\n )}\n\n {/* Shared Frequency/Throttle Settings */}\n <div className=\"border-t border-slate-100 pt-5 mt-5\">\n <label className=\"block text-sm font-medium text-slate-700 mb-2\">\n 批量批改间隔冷却时间 (秒)\n </label>\n <div className=\"flex items-center gap-3\">\n <input\n type=\"number\"\n min=\"0\"\n max=\"60\"\n value={coolingDelay}\n onChange={(e) => setCoolingDelay(Math.max(0, parseInt(e.target.value, 10) || 0))}\n className=\"w-32 border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n />\n <span className=\"text-sm text-slate-600\">秒</span>\n </div>\n <p className=\"text-xs text-slate-400 mt-2\">\n * 默认值为 5 秒。如果在连续批改多份作业时遭遇 429 报错,建议调大该值(如 10 秒或 15 秒)以降低请求频率。\n </p>\n </div>\n </div>\n </div>\n\n {/* Local Storage Settings */}\n <div className=\"bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden\">\n <div className=\"px-6 py-4 border-b border-slate-200 bg-slate-50 flex items-center gap-2\">\n <Database className=\"text-blue-600\" size={20} />\n <h3 className=\"font-semibold text-slate-800\">本地数据与隐私</h3>\n </div>\n <div className=\"p-6 space-y-4\">\n <div className=\"flex items-center justify-between p-4 border border-slate-200 rounded-lg\">\n <div className=\"flex items-start gap-3\">\n <Shield className=\"text-green-600 mt-0.5\" size={20} />\n <div>\n <p className=\"font-medium text-slate-800\">纯前端模式 (Serverless)</p>\n <p className=\"text-sm text-slate-500 mt-1\">\n 当前系统运行在纯前端模式,所有学生文件均在本地浏览器解析,不会上传到任何第三方服务器(除了将文本发送给 Google Gemini API 进行批改)。\n </p>\n </div>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between p-4 border border-slate-200 rounded-lg\">\n <div>\n <p className=\"font-medium text-slate-800\">保留历史记录</p>\n <p className=\"text-sm text-slate-500 mt-1\">在浏览器本地存储 (LocalStorage) 中保留最近的批改任务记录</p>\n </div>\n <label className=\"relative inline-flex items-center cursor-pointer\">\n <input type=\"checkbox\" className=\"sr-only peer\" defaultChecked />\n <div className=\"w-11 h-6 bg-slate-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600\"></div>\n </label>\n </div>\n </div>\n </div>\n\n <div className=\"flex justify-end\">\n <button \n onClick={handleSave}\n className=\"bg-blue-600 hover:bg-blue-700 text-white px-8 py-2.5 rounded-lg font-medium transition-colors flex items-center gap-2\"\n >\n <Save size={18} />\n {saved ? '已保存' : '保存设置'}\n </button>\n </div>\n </div>\n </div>\n );\n}\n"}, "95a38db93dbad7fdb3003c78c429fde809dc9d93": {"t": 1785772870.408774, "n": 8485, "h": "ba3bff5c4a50cd1e", "c": "import React, { useState, useEffect } from 'react';\nimport { Save, Key, Sliders, Database, Shield, Globe } from 'lucide-react';\n\nexport function Settings() {\n const [engineType, setEngineType] = useState<'builtin' | 'custom'>('builtin');\n const [customBaseUrl, setCustomBaseUrl] = useState('https://api.deepseek.com/v1');\n const [customApiKey, setCustomApiKey] = useState('');\n const [customModel, setCustomModel] = useState('deepseek-chat');\n const [saved, setSaved] = useState(false);\n\n useEffect(() => {\n const savedEngine = localStorage.getItem('ai_engine_type') as 'builtin' | 'custom';\n if (savedEngine) setEngineType(savedEngine);\n \n const savedBaseUrl = localStorage.getItem('ai_custom_base_url');\n if (savedBaseUrl) setCustomBaseUrl(savedBaseUrl);\n \n const savedApiKey = localStorage.getItem('ai_custom_api_key');\n if (savedApiKey) setCustomApiKey(savedApiKey);\n \n const savedModel = localStorage.getItem('ai_custom_model');\n if (savedModel) setCustomModel(savedModel);\n }, []);\n\n const handleSave = () => {\n localStorage.setItem('ai_engine_type', engineType);\n localStorage.setItem('ai_custom_base_url', customBaseUrl);\n localStorage.setItem('ai_custom_api_key', customApiKey);\n localStorage.setItem('ai_custom_model', customModel);\n \n setSaved(true);\n setTimeout(() => setSaved(false), 2000);\n };\n\n return (\n <div className=\"p-8 max-w-4xl mx-auto\">\n <h2 className=\"text-2xl font-bold text-slate-900 mb-8\">系统设置</h2>\n\n <div className=\"space-y-6\">\n {/* AI Engine Settings */}\n <div className=\"bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden\">\n <div className=\"px-6 py-4 border-b border-slate-200 bg-slate-50 flex items-center gap-2\">\n <Sliders className=\"text-blue-600\" size={20} />\n <h3 className=\"font-semibold text-slate-800\">AI 批改引擎配置</h3>\n </div>\n <div className=\"p-6 space-y-6\">\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-2\">\n 批改引擎服务商\n </label>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div \n className={`border rounded-lg p-4 cursor-pointer transition-colors ${engineType === 'builtin' ? 'border-blue-500 bg-blue-50' : 'border-slate-200 hover:border-blue-200'}`}\n onClick={() => setEngineType('builtin')}\n >\n <div className=\"flex items-center gap-2 mb-1\">\n <div className={`w-3 h-3 rounded-full ${engineType === 'builtin' ? 'bg-blue-500' : 'bg-slate-300'}`}></div>\n <p className=\"font-medium text-slate-800\">AI Studio 内置引擎</p>\n </div>\n <p className=\"text-sm text-slate-500 ml-5\">使用当前环境默认的 Gemini 大模型,无需配置 API Key。</p>\n </div>\n \n <div \n className={`border rounded-lg p-4 cursor-pointer transition-colors ${engineType === 'custom' ? 'border-blue-500 bg-blue-50' : 'border-slate-200 hover:border-blue-200'}`}\n onClick={() => setEngineType('custom')}\n >\n <div className=\"flex items-center gap-2 mb-1\">\n <div className={`w-3 h-3 rounded-full ${engineType === 'custom' ? 'bg-blue-500' : 'bg-slate-300'}`}></div>\n <p className=\"font-medium text-slate-800\">自定义国内模型 (OpenAI 兼容)</p>\n </div>\n <p className=\"text-sm text-slate-500 ml-5\">支持 DeepSeek、智谱 GLM、通义千问等国内大模型接口。</p>\n </div>\n </div>\n </div>\n\n {engineType === 'custom' && (\n <div className=\"space-y-4 p-5 bg-slate-50 border border-slate-200 rounded-lg animate-in fade-in slide-in-from-top-2\">\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-1 flex items-center gap-2\">\n <Globe size={16} className=\"text-slate-400\" />\n 接口地址 (Base URL)\n </label>\n <input \n type=\"text\" \n value={customBaseUrl}\n onChange={(e) => setCustomBaseUrl(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"例如:https://api.deepseek.com/v1\"\n />\n <p className=\"text-xs text-slate-500 mt-1\">\n 常用地址:DeepSeek (https://api.deepseek.com/v1) | 智谱 (https://open.bigmodel.cn/api/paas/v4)\n </p>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-1 flex items-center gap-2\">\n <Key size={16} className=\"text-slate-400\" />\n API Key\n </label>\n <input \n type=\"password\" \n value={customApiKey}\n onChange={(e) => setCustomApiKey(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"sk-...\"\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-slate-700 mb-1\">\n 模型名称 (Model)\n </label>\n <input \n type=\"text\" \n value={customModel}\n onChange={(e) => setCustomModel(e.target.value)}\n className=\"w-full border border-slate-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none\"\n placeholder=\"例如:deepseek-chat\"\n />\n <p className=\"text-xs text-slate-500 mt-1\">\n 常用模型:deepseek-chat, glm-4-flash, qwen-plus\n </p>\n </div>\n </div>\n )}\n </div>\n </div>\n\n {/* Local Storage Settings */}\n <div className=\"bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden\">\n <div className=\"px-6 py-4 border-b border-slate-200 bg-slate-50 flex items-center gap-2\">\n <Database className=\"text-blue-600\" size={20} />\n <h3 className=\"font-semibold text-slate-800\">本地数据与隐私</h3>\n </div>\n <div className=\"p-6 space-y-4\">\n <div className=\"flex items-center justify-between p-4 border border-slate-200 rounded-lg\">\n <div className=\"flex items-start gap-3\">\n <Shield className=\"text-green-600 mt-0.5\" size={20} />\n <div>\n <p className=\"font-medium text-slate-800\">纯前端模式 (Serverless)</p>\n <p className=\"text-sm text-slate-500 mt-1\">\n 当前系统运行在纯前端模式,所有学生文件均在本地浏览器解析,不会上传到任何第三方服务器(除了将文本发送给 Google Gemini API 进行批改)。\n </p>\n </div>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between p-4 border border-slate-200 rounded-lg\">\n <div>\n <p className=\"font-medium text-slate-800\">保留历史记录</p>\n <p className=\"text-sm text-slate-500 mt-1\">在浏览器本地存储 (LocalStorage) 中保留最近的批改任务记录</p>\n </div>\n <label className=\"relative inline-flex items-center cursor-pointer\">\n <input type=\"checkbox\" className=\"sr-only peer\" defaultChecked />\n <div className=\"w-11 h-6 bg-slate-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-slate-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600\"></div>\n </label>\n </div>\n </div>\n </div>\n\n <div className=\"flex justify-end\">\n <button \n onClick={handleSave}\n className=\"bg-blue-600 hover:bg-blue-700 text-white px-8 py-2.5 rounded-lg font-medium transition-colors flex items-center gap-2\"\n >\n <Save size={18} />\n {saved ? '已保存' : '保存设置'}\n </button>\n </div>\n </div>\n </div>\n );\n}\n"}}}}