Files

161 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DeepSeek Web 适配器</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f1f5f9;color:#334155;min-height:100vh}
.header{background:linear-gradient(135deg,#3b82f6,#2563eb);padding:16px 24px;display:flex;align-items:center;justify-content:space-between}
.header h1{font-size:20px;font-weight:600;color:#fff}
.status-bar{display:flex;gap:16px;align-items:center}
.status-dot{width:10px;height:10px;border-radius:50%;display:inline-block}
.status-dot.online{background:#22c55e;box-shadow:0 0 8px #22c55e80}
.status-dot.offline{background:#ef4444;box-shadow:0 0 8px #ef444480}
.status-text{font-size:13px;color:#e0e7ff}
.container{max-width:1200px;margin:0 auto;padding:24px}
.tabs{display:flex;gap:4px;margin-bottom:24px;background:#e2e8f0;border-radius:10px;padding:4px}
.tab{padding:10px 20px;border-radius:8px;cursor:pointer;font-size:14px;font-weight:500;color:#64748b;border:none;background:none}
.tab:hover{color:#334155;background:#cbd5e1}
.tab.active{background:#3b82f6;color:#fff}
.panel{display:none}.panel.active{display:block}
.card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:24px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.06)}
.card h2{font-size:16px;font-weight:600;margin-bottom:16px;color:#1e293b}
.form-group{margin-bottom:16px}
.form-group label{display:block;font-size:13px;font-weight:500;color:#64748b;margin-bottom:6px}
.form-group input,.form-group select,.form-group textarea{width:100%;padding:10px 14px;background:#f8fafc;border:1px solid #cbd5e1;border-radius:8px;color:#334155;font-size:14px;outline:none}
.form-group input:focus{border-color:#3b82f6}
.form-group input:disabled{opacity:.5}
.row{display:flex;gap:16px}.row .form-group{flex:1}
.btn{padding:10px 20px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;border:none;display:inline-flex;align-items:center;gap:6px}
.btn-primary{background:#3b82f6;color:#fff}.btn-primary:hover{background:#2563eb}
.btn-danger{background:#ef4444;color:#fff}.btn-danger:hover{background:#dc2626}
.btn-success{background:#22c55e;color:#fff}.btn-success:hover{background:#16a34a}
.btn-secondary{background:#e2e8f0;color:#475569}.btn-secondary:hover{background:#cbd5e1}
.btn:disabled{opacity:.5;cursor:not-allowed}
.btn-group{display:flex;gap:8px;margin-top:16px}
.log-container{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:16px;max-height:500px;overflow-y:auto;font-family:monospace;font-size:12px;line-height:1.6;color:#475569}
.info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}
.info-item{background:#f8fafc;border-radius:8px;padding:14px;border:1px solid #e2e8f0}
.info-item .label{font-size:12px;color:#94a3b8;margin-bottom:4px}
.info-item .value{font-size:16px;font-weight:600;color:#1e293b}
.info-item .value.good{color:#16a34a}.info-item .value.bad{color:#dc2626}.info-item .value.warn{color:#d97706}
.toast{position:fixed;top:20px;right:20px;padding:12px 20px;border-radius:8px;font-size:14px;z-index:9999;box-shadow:0 4px 12px rgba(0,0,0,.15)}
.toast.success{background:#22c55e;color:#fff}.toast.error{background:#ef4444;color:#fff}.toast.info{background:#3b82f6;color:#fff}
.register-result{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:16px;margin-top:16px}
.register-result .api-key{background:#f1f5f9;padding:8px 12px;border-radius:6px;font-family:monospace;color:#16a34a;margin:8px 0;word-break:break-all;border:1px solid #e2e8f0}
</style>
</head>
<body>
<div class="header">
<h1>DeepSeek Web 适配器</h1>
<div class="status-bar">
<span class="status-dot" id="platformDot"></span>
<span class="status-text" id="platformStatus">平台: --</span>
<span class="status-dot" id="deepseekDot"></span>
<span class="status-text" id="deepseekStatus">DeepSeek: --</span>
</div>
</div>
<div class="container">
<div class="tabs">
<button class="tab active" onclick="switchTab('status',this)">📊 状态</button>
<button class="tab" onclick="switchTab('config',this)">⚙️ 配置</button>
<button class="tab" onclick="switchTab('register',this)">🔑 注册</button>
<button class="tab" onclick="switchTab('browser',this)">🌐 浏览器</button>
<button class="tab" onclick="switchTab('logs',this)">📋 日志</button>
</div>
<div class="panel active" id="panel-status">
<div class="card"><h2>📊 系统状态</h2>
<div class="info-grid">
<div class="info-item"><div class="label">平台连接</div><div class="value" id="st-platform">--</div></div>
<div class="info-item"><div class="label">DeepSeek 浏览器</div><div class="value" id="st-deepseek">--</div></div>
<div class="info-item"><div class="label">当前任务</div><div class="value" id="st-task">--</div></div>
<div class="info-item"><div class="label">Worker ID</div><div class="value" id="st-worker-id" style="font-size:12px;word-break:break-all">--</div></div>
<div class="info-item"><div class="label">Worker 名称</div><div class="value" id="st-worker-name">--</div></div>
<div class="info-item"><div class="label">处理状态</div><div class="value" id="st-processing">--</div></div>
</div></div></div>
<div class="panel" id="panel-config">
<div class="card"><h2>⚙️ 平台配置</h2>
<div class="form-group"><label>列阵平台地址</label><input type="text" id="cfg-base-url"></div>
<div class="row">
<div class="form-group"><label>API Key</label><input type="password" id="cfg-api-key"></div>
<div class="form-group"><label>Worker ID</label><input type="text" id="cfg-worker-id" disabled></div>
</div>
<div class="row">
<div class="form-group"><label>Worker 名称</label><input type="text" id="cfg-worker-name"></div>
<div class="form-group"><label>回复范围</label><select id="cfg-reply-scope"><option value="chat">公开回复</option><option value="private">私密回复</option></select></div>
</div></div>
<div class="card"><h2>🌐 DeepSeek 配置</h2>
<div class="row">
<div class="form-group"><label>DeepSeek 网址</label><input type="text" id="cfg-deepseek-url"></div>
<div class="form-group"><label>无头模式</label><select id="cfg-headless"><option value="false">否</option><option value="true">是</option></select></div>
</div>
<div class="form-group"><label>用户数据目录</label><input type="text" id="cfg-user-data-dir"></div>
</div>
<div class="card"><h2>⚡ 执行配置</h2>
<div class="row">
<div class="form-group"><label>最大交互轮数</label><input type="number" id="cfg-max-rounds"></div>
<div class="form-group"><label>任务超时(秒)</label><input type="number" id="cfg-task-timeout"></div>
</div>
<div class="row">
<div class="form-group"><label>控制面板端口</label><input type="number" id="cfg-admin-port"></div>
<div class="form-group"><label>日志级别</label><select id="cfg-log-level"><option value="DEBUG">DEBUG</option><option value="INFO">INFO</option><option value="WARNING">WARNING</option><option value="ERROR">ERROR</option></select></div>
</div></div>
<div class="btn-group">
<button class="btn btn-primary" onclick="saveConfig()">💾 保存配置</button>
<button class="btn btn-secondary" onclick="loadConfig()">🔄 重新加载</button>
</div></div>
<div class="panel" id="panel-register">
<div class="card"><h2>🔑 注册新 Worker</h2>
<p style="color:#94a3b8;margin-bottom:16px;font-size:14px">在列阵平台注册一个新的 AI Worker。注册成功后将获得 API Key,请妥善保存。</p>
<div class="form-group"><label>Worker 名称</label><input type="text" id="reg-name" value="DeepSeek-Web"></div>
<div class="form-group"><label>Worker 类型</label><select id="reg-type"><option value="ai" selected>AI</option><option value="tool">工具</option></select></div>
<div class="form-group"><label>描述(可选)</label><textarea id="reg-description" rows="3"></textarea></div>
<div class="form-group"><label>邀请码(可选)</label><input type="text" id="reg-invite-code"></div>
<div class="btn-group"><button class="btn btn-success" onclick="registerWorker()">🚀 注册</button></div>
<div id="registerResult" style="display:none"></div>
</div></div>
<div class="panel" id="panel-browser">
<div class="card"><h2>🌐 DeepSeek 浏览器控制</h2>
<p style="color:#94a3b8;margin-bottom:16px;font-size:14px">启动 Chrome 浏览器连接 DeepSeek 网页版。首次启动需要手动登录。</p>
<div class="btn-group" style="margin-top:0">
<button class="btn btn-success" id="btn-start" onclick="startBrowser()">🚀 启动浏览器</button>
<button class="btn btn-danger" id="btn-stop" onclick="stopBrowser()" disabled>⏹️ 关闭浏览器</button>
<button class="btn btn-secondary" onclick="reinitPrompt()">🔄 重新发送系统提示词</button>
</div></div>
<div class="card"><h2>📋 DeepSeek 日志</h2>
<div class="btn-group" style="margin-top:0;margin-bottom:12px">
<button class="btn btn-secondary" onclick="refreshLogs()">🔄 刷新</button>
<button class="btn btn-secondary" onclick="clearLogs()">🗑️ 清空</button>
</div>
<div class="log-container" id="deepseekLogs"><div style="color:#64748b">暂无日志</div></div>
</div></div>
<div class="panel" id="panel-logs">
<div class="card"><h2>📋 系统日志</h2>
<div class="btn-group" style="margin-top:0;margin-bottom:12px">
<button class="btn btn-secondary" onclick="refreshSystemLogs()">🔄 刷新</button>
</div>
<div class="log-container" id="systemLogs"><div style="color:#64748b">暂无日志</div></div>
</div></div>
</div>
<script>
function switchTab(n,el){document.querySelectorAll('.tab').forEach(function(t){t.classList.remove('active')});document.querySelectorAll('.panel').forEach(function(p){p.classList.remove('active')});el.classList.add('active');document.getElementById('panel-'+n).classList.add('active')}
function showToast(m,t){t=t||'info';var e=document.createElement('div');e.className='toast '+t;e.textContent=m;document.body.appendChild(e);setTimeout(function(){e.remove()},3000)}
function api(p,m,d){m=m||'GET';d=d||null;var o={method:m,headers:{'Content-Type':'application/json'}};if(d)o.body=JSON.stringify(d);return fetch('/api'+p,o).then(function(r){return r.json()})}
function loadConfig(){api('/config').then(function(c){if(c.error){showToast(c.error,'error');return}document.getElementById('cfg-base-url').value=c.array_base_url||'';document.getElementById('cfg-api-key').value=c.api_key||'';document.getElementById('cfg-worker-id').value=c.worker_id||'';document.getElementById('cfg-worker-name').value=c.worker_name||'';document.getElementById('cfg-reply-scope').value=c.reply_scope||'chat';document.getElementById('cfg-deepseek-url').value=c.deepseek_url||'';document.getElementById('cfg-headless').value=String(c.headless||false);document.getElementById('cfg-user-data-dir').value=c.user_data_dir||'';document.getElementById('cfg-max-rounds').value=c.max_rounds||30;document.getElementById('cfg-task-timeout').value=c.task_timeout||600;document.getElementById('cfg-admin-port').value=c.admin_port||9091;document.getElementById('cfg-log-level').value=c.log_level||'INFO'})}
function saveConfig(){var d={array_base_url:document.getElementById('cfg-base-url').value,api_key:document.getElementById('cfg-api-key').value,worker_name:document.getElementById('cfg-worker-name').value,reply_scope:document.getElementById('cfg-reply-scope').value,deepseek_url:document.getElementById('cfg-deepseek-url').value,headless:document.getElementById('cfg-headless').value==='true',user_data_dir:document.getElementById('cfg-user-data-dir').value,max_rounds:parseInt(document.getElementById('cfg-max-rounds').value)||30,task_timeout:parseInt(document.getElementById('cfg-task-timeout').value)||600,admin_port:parseInt(document.getElementById('cfg-admin-port').value)||9091,log_level:document.getElementById('cfg-log-level').value};api('/config','POST',d).then(function(r){if(r.success)showToast('配置已保存','success');else showToast(r.error||'保存失败','error')})}
function registerWorker(){var d={name:document.getElementById('reg-name').value,type:document.getElementById('reg-type').value,description:document.getElementById('reg-description').value,invite_code:document.getElementById('reg-invite-code').value};api('/register','POST',d).then(function(r){var el=document.getElementById('registerResult');el.style.display='block';if(r.error){el.innerHTML='<div class="register-result"><p style="color:#ef4444">注册失败: '+r.error+'</p></div>'}else{var kid=r.worker_id||'';var kkey=r.api_key||'';el.innerHTML='<div class="register-result"><p style="color:#22c55e">注册成功!</p><p>Worker ID:</p><div class="api-key">'+kid+'</div><p>API Key(仅显示一次,请立即保存!):</p><div class="api-key">'+kkey+'</div><div class="btn-group"><button class="btn btn-primary" onclick="fillApiKey(\''+kkey.replace(/'/g,"\\'")+'\')">填入配置</button></div></div>'}})}
function fillApiKey(k){document.getElementById('cfg-api-key').value=k;switchTab('config');document.querySelectorAll('.tab').forEach(function(t){t.classList.remove('active')});document.querySelectorAll('.tab')[1].classList.add('active');showToast('API Key 已填入','info')}
function startBrowser(){document.getElementById('btn-start').disabled=true;api('/browser/start','POST').then(function(r){if(r.success)showToast('浏览器启动中...','success');else{showToast(r.error||'启动失败','error');document.getElementById('btn-start').disabled=false}setTimeout(refreshStatus,3000)})}
function stopBrowser(){document.getElementById('btn-stop').disabled=true;api('/browser/stop','POST').then(function(r){if(r.success)showToast('浏览器已关闭','success');else showToast(r.error||'关闭失败','error');setTimeout(refreshStatus,2000)})}
function reinitPrompt(){api('/browser/reinit','POST').then(function(r){if(r.success)showToast('系统提示词已重新发送','success');else showToast(r.error||'操作失败','error')})}
function refreshStatus(){api('/status').then(function(s){var pD=document.getElementById('platformDot'),pT=document.getElementById('platformStatus'),dD=document.getElementById('deepseekDot'),dT=document.getElementById('deepseekStatus');if(s.connected){pD.className='status-dot online';pT.textContent='平台: 已连接'}else{pD.className='status-dot offline';pT.textContent='平台: 未连接'}if(s.deepseek_ready){dD.className='status-dot online';dT.textContent='DeepSeek: 就绪'}else{dD.className='status-dot offline';dT.textContent='DeepSeek: 未启动'}document.getElementById('st-platform').textContent=s.connected?'已连接':'未连接';document.getElementById('st-platform').className='value '+(s.connected?'good':'bad');document.getElementById('st-deepseek').textContent=s.deepseek_ready?'就绪':'未启动';document.getElementById('st-deepseek').className='value '+(s.deepseek_ready?'good':'bad');document.getElementById('st-task').textContent=s.current_task||'空闲';document.getElementById('st-task').className='value '+(s.current_task?'warn':'good');document.getElementById('st-worker-id').textContent=s.worker_id||'--';document.getElementById('st-worker-name').textContent=s.worker_name||'--';document.getElementById('st-processing').textContent=s.processing?'处理中':'空闲';document.getElementById('st-processing').className='value '+(s.processing?'warn':'good');document.getElementById('btn-start').disabled=s.deepseek_ready;document.getElementById('btn-stop').disabled=!s.deepseek_ready})}
function refreshLogs(){api('/logs/deepseek').then(function(r){var el=document.getElementById('deepseekLogs');if(r.logs){el.innerHTML=r.logs.split('\n').map(function(l){return '<div>'+l.replace(/</g,'&lt;')+'</div>'}).join('');el.scrollTop=el.scrollHeight}else{el.innerHTML='<div style="color:#64748b">暂无日志</div>'}})}
function clearLogs(){api('/logs/deepseek','DELETE').then(function(){refreshLogs()})}
function refreshSystemLogs(){api('/logs/system').then(function(r){var el=document.getElementById('systemLogs');if(r.logs){el.innerHTML=r.logs.split('\n').map(function(l){return '<div>'+l.replace(/</g,'&lt;')+'</div>'}).join('');el.scrollTop=el.scrollHeight}else{el.innerHTML='<div style="color:#64748b">暂无日志</div>'}})}
loadConfig();refreshStatus();setInterval(refreshStatus,5000);
</script>
</body>
</html>