diff --git a/docs/index.html b/docs/index.html index bea264f3..8aacc9dd 100644 --- a/docs/index.html +++ b/docs/index.html @@ -139,6 +139,9 @@ footer{padding:12px 18px 16px;background:var(--s1);border-top:1px solid var(--bo .sfg small{display:block;color:var(--dim);font-size:11px;margin-top:4px} .sbtn2{width:100%;padding:10px;background:var(--accent);color:#fff;border:none;border-radius:var(--rs);font-size:14px;cursor:pointer;transition:opacity .2s;font-family:inherit} .sbtn2:hover{opacity:.85} +.det-btn{flex-shrink:0;padding:0 12px;border-radius:var(--rs);border:1px solid var(--accent);background:transparent;color:var(--accent);font-size:12px;cursor:pointer;white-space:nowrap;transition:all .2s;font-family:inherit} +.det-btn:hover{background:rgba(79,142,247,.1)} +.det-btn:disabled{opacity:.5;cursor:default} .sec-ttl{font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:.1em;margin:18px 0 10px;border-top:1px solid var(--border);padding-top:12px} .ir2{display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid rgba(30,50,81,.5);font-size:13px} .iv{color:var(--ok);font-weight:500} @@ -247,8 +250,12 @@ footer{padding:12px 18px 16px;background:var(--s1);border-top:1px solid var(--bo
- +
+ + +
+
@@ -375,8 +382,12 @@ footer{padding:12px 18px 16px;background:var(--s1);border-top:1px solid var(--bo
- +
+ + +
🔒 密钥仅本次会话有效,关闭标签页自动清除 +
@@ -447,7 +458,7 @@ footer{padding:12px 18px 16px;background:var(--s1);border-top:1px solid var(--bo
  1. 打开链接 https://qinfendebingshuo.github.io/guanghulab/
  2. 下拉菜单选择你是谁(冰朔 / 肥猫 / 桔子 / 开发者名字)
  3. -
  4. 填入你的 API Key(没有的话点「演示模式」)
  5. +
  6. 填入你的 API Key 并点「🔍 自动检测」,系统将自动识别提供商和可用模型
  7. 点「开始对话」,铸渊会针对你的身份打招呼
  8. 直接用聊天的方式问铸渊任何问题
@@ -573,6 +584,7 @@ const MODES = { const MODE_ORD = ['chat','build','review','brain']; const DEFAULT_MDL = 'gpt-4o'; +const PROBE_TIMEOUT_MS = 8000; // ms per provider probe in autoDetectAPI const FB_COV = {implemented:3,total:17,percent:'17.6%'}; // KEY_MASK is no longer used in the input field; kept only for backward compatibility // with any saved value that might still be in localStorage. @@ -711,6 +723,90 @@ function fillModels(selId, pv, cur){ if(cur && mdls.includes(cur)) sel.value=cur; } +// ═══════════════════════════════════════════════════════ +// AUTO-DETECT PROVIDER & MODELS +// ═══════════════════════════════════════════════════════ +async function autoDetectAPI(ctx){ + const keyEl = document.getElementById(ctx==='s'?'sk':'ck'); + const statusEl = document.getElementById(ctx==='s'?'sdet-status':'cdet-status'); + const btnEl = document.getElementById(ctx==='s'?'sdet-btn':'cdet-btn'); + const key = keyEl.value.trim(); + + if(!key){ + statusEl.innerHTML='⚠️ 请先输入 API 密钥'; + return; + } + + // Build probe list: custom endpoint first (if provided), then all named providers + const candidates = []; + const customBase = (document.getElementById(ctx==='s'?'sep':'cep')?.value||'').trim(); + if(customBase) candidates.push({pv:'custom', base:customBase}); + for(const [pv, cfg] of Object.entries(PROVS)){ + if(pv!=='custom' && cfg.base) candidates.push({pv, base:cfg.base}); + } + + btnEl.disabled = true; + btnEl.textContent = '⏳ 检测中…'; + statusEl.innerHTML = '🔍 正在自动探测兼容提供商…'; + + let matched = false; + for(const {pv, base} of candidates){ + statusEl.innerHTML = `🔍 探测 ${pv === 'custom' ? base : pv}…`; + try{ + const ctrl = new AbortController(); + const tid = setTimeout(()=>ctrl.abort(), PROBE_TIMEOUT_MS); + const res = await fetch(base+'/models',{ + headers:{'Authorization':'Bearer '+key}, + signal: ctrl.signal, + }); + clearTimeout(tid); + if(res.ok){ + // Expected: { data: [ { id: "model-name", ... }, ... ] } (OpenAI-compatible /models) + const data = await res.json(); + const mdls = (data.data||[]).map(m=>m.id).filter(Boolean).sort(); + if(!mdls.length) continue; + + // Switch provider select + const provSel = document.getElementById(ctx==='s'?'sp':'cp'); + if(provSel) provSel.value = pv; + onProv(pv, ctx); + + // Override model dropdown with live list (always show as dropdown) + const mdlSel = document.getElementById(ctx==='s'?'sm':'cm'); + const mdlCust = document.getElementById(ctx==='s'?'sm-cust':'cm-cust'); + const mdlHint = document.getElementById(ctx==='s'?'sm-cust-hint':'cm-cust-hint'); + if(mdlSel){ + mdlSel.innerHTML = mdls.map(m=>``).join(''); + mdlSel.style.display = 'block'; + } + if(mdlCust) mdlCust.style.display='none'; + if(mdlHint) mdlHint.style.display='none'; + + // If custom endpoint was the match, show the endpoint field + if(pv==='custom' && ctx==='s'){ + document.getElementById('sep-g').style.display='block'; + document.getElementById('sep').value = customBase; + } else if(pv==='custom' && ctx==='c'){ + document.getElementById('cep-g').style.display='block'; + document.getElementById('cep').value = customBase; + } + + const label = PROVS[pv]?.base ? pv : customBase; + statusEl.innerHTML = `✅ 检测成功(${label})· 共发现 ${mdls.length} 个可用模型`; + matched = true; + break; + } + }catch(e){ console.debug('autoDetect probe failed:', pv, e.message); } + } + + if(!matched){ + statusEl.innerHTML = '❌ 未能自动匹配,请手动选择提供商和模型后保存'; + } + + btnEl.disabled = false; + btnEl.textContent = '🔍 自动检测'; +} + function doSetup(){ const k = document.getElementById('sk').value.trim(); const errEl = document.getElementById('sk-err');