返回 DeepSeek-Reasonix
provider_setup.html
根目录 / internal / serve / provider_setup.html
1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1" />
6 <title>Reasonix Provider Setup</title>
7 <style>
8 *,*::before,*::after{box-sizing:border-box}
9 :root{color-scheme:dark;--bg:#121316;--panel:#1c1e22;--border:#353840;--text:#f1f2f4;--muted:#a4a8b2;--accent:#e1663f;--accent-hover:#ed7650;--danger:#ff806f}
10 body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px;background:radial-gradient(circle at top,#22242a 0,var(--bg) 52%);color:var(--text);font:15px/1.5 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
11 .card{width:min(520px,100%);padding:28px;border:1px solid var(--border);border-radius:18px;background:var(--panel);box-shadow:0 24px 80px rgba(0,0,0,.34)}
12 .brand{display:flex;align-items:center;gap:12px;margin-bottom:24px}.brand img{width:132px;height:auto}.badge{margin-left:auto;padding:4px 9px;border-radius:999px;background:rgba(225,102,63,.14);color:#ff9a79;font-size:12px}
13 h1{margin:0 0 8px;font-size:24px;line-height:1.2}p{margin:0;color:var(--muted)}
14 .meta{display:grid;grid-template-columns:92px 1fr;gap:8px 14px;margin:24px 0;padding:16px;border-radius:12px;background:#15171a}.meta dt{color:var(--muted)}.meta dd{margin:0;min-width:0;overflow-wrap:anywhere;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
15 label{display:block;margin:0 0 8px;font-weight:600}.field{width:100%;height:44px;padding:0 12px;border:1px solid var(--border);border-radius:10px;background:#111215;color:var(--text);font:14px ui-monospace,SFMono-Regular,Menlo,monospace;outline:none}.field:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(225,102,63,.16)}
16 .hint{margin-top:8px;font-size:12px}.actions{display:flex;align-items:center;gap:12px;margin-top:22px}button{height:42px;padding:0 18px;border:0;border-radius:10px;background:var(--accent);color:white;font:600 14px system-ui;cursor:pointer}button:hover{background:var(--accent-hover)}button:disabled{cursor:wait;opacity:.62}.status{min-height:22px;color:var(--muted);font-size:13px}.status.error{color:var(--danger)}
17 </style>
18 </head>
19 <body>
20 <main class="card">
21 <div class="brand"><img src="/assets/logo-wordmark.svg" alt="Reasonix" /><span class="badge" id="remote-badge">Remote</span></div>
22 <h1 id="title">Configure the remote Provider</h1>
23 <p id="intro">This Reasonix Serve is ready, but its selected Provider has no saved API key on this host.</p>
24 <dl class="meta">
25 <dt id="provider-label">Provider</dt><dd id="provider">—</dd>
26 <dt id="model-label">Model</dt><dd id="model">—</dd>
27 <dt id="key-label">Credential</dt><dd id="key-env">—</dd>
28 </dl>
29 <form id="setup-form">
30 <label for="api-key" id="api-key-label">API Key</label>
31 <input class="field" id="api-key" type="password" autocomplete="off" spellcheck="false" required />
32 <p class="hint" id="hint">The key is stored only in this remote host's Reasonix credential file.</p>
33 <div class="actions"><button id="save" type="submit">Save and open Reasonix</button><span class="status" id="status" role="status"></span></div>
34 </form>
35 </main>
36 <script>
37 const langPref='__LANG__';
38 const zh=String(langPref).toLowerCase().startsWith('zh');
39 const copy=zh?{
40 badge:'远端',title:'配置远端 Provider',intro:'Reasonix Serve 已可访问,但这台主机尚未保存当前 Provider 的 API Key。',provider:'Provider',model:'模型',credential:'凭据变量',apiKey:'API Key',hint:'密钥只会保存到这台远端主机的 Reasonix 凭据文件。',pendingHint:'API Key 已安全保存。现在只需重试激活 Provider。',save:'保存并打开 Reasonix',retry:'重试激活',saving:'正在保存…',activating:'正在激活…',failed:'保存失败,请重试。'
41 }:{badge:'Remote',title:'Configure the remote Provider',intro:'This Reasonix Serve is ready, but its selected Provider has no saved API key on this host.',provider:'Provider',model:'Model',credential:'Credential',apiKey:'API Key',hint:"The key is stored only in this remote host's Reasonix credential file.",pendingHint:'The API key is safely stored. Retry Provider activation without entering it again.',save:'Save and open Reasonix',retry:'Retry activation',saving:'Saving…',activating:'Activating…',failed:'Setup failed. Try again.'};
42 const $=id=>document.getElementById(id);
43 $('remote-badge').textContent=copy.badge;$('title').textContent=copy.title;$('intro').textContent=copy.intro;$('provider-label').textContent=copy.provider;$('model-label').textContent=copy.model;$('key-label').textContent=copy.credential;$('api-key-label').textContent=copy.apiKey;$('hint').textContent=copy.hint;$('save').textContent=copy.save;
44 let activationPending=false;
45 async function load(){
46 const response=await fetch('/provider-setup',{cache:'no-store'});
47 if(!response.ok)throw new Error(copy.failed);
48 const state=await response.json();
49 if(!state.required){location.reload();return;}
50 activationPending=state.activationPending===true;
51 const input=$('api-key');
52 $('provider').textContent=state.provider||'—';$('model').textContent=state.model||'—';$('key-env').textContent=state.keyEnv||'—';
53 $('api-key-label').hidden=activationPending;input.hidden=activationPending;input.required=!activationPending;
54 $('hint').textContent=activationPending?copy.pendingHint:copy.hint;$('save').textContent=activationPending?copy.retry:copy.save;
55 $('status').textContent='';$('status').classList.remove('error');
56 if(state.error){$('status').textContent=state.error;$('status').classList.add('error');}
57 }
58 $('setup-form').addEventListener('submit',async event=>{
59 event.preventDefault();const input=$('api-key'),button=$('save'),status=$('status');
60 button.disabled=true;status.classList.remove('error');status.textContent=activationPending?copy.activating:copy.saving;
61 try{
62 const response=await fetch('/provider-setup',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({apiKey:activationPending?'':input.value})});
63 input.value='';
64 if(!response.ok)throw new Error((await response.text()).trim()||copy.failed);
65 location.reload();
66 }catch(error){input.value='';let refreshed=false;try{await load();refreshed=status.classList.contains('error');}catch{}if(!refreshed){status.textContent=error instanceof Error?error.message:copy.failed;status.classList.add('error');}button.disabled=false;if(!activationPending)input.focus();}
67 });
68 load().catch(error=>{$('status').textContent=error instanceof Error?error.message:copy.failed;$('status').classList.add('error');});
69 </script>
70 </body>
71 </html>
72
72 lines HTML