From 1af4fd84119fce135869e573ad8ef2f82f3241c4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 6 Mar 2026 06:52:09 +0000
Subject: [PATCH] feat: add auto-detect API provider and models from any key
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add 🔍 自动检测 button in setup form and settings panel
- autoDetectAPI() probes GET /models on all known providers
(yunwu → openai → gemini → deepseek → moonshot → zhipu)
- Custom endpoint is tried first if one is set
- Auto-fills provider select + model dropdown with live API response
- 8s timeout per probe (PROBE_TIMEOUT_MS constant)
- Shows inline loading/success/error feedback
- Add .det-btn CSS class for consistent button styling
Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
---
docs/index.html | 102 ++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 99 insertions(+), 3 deletions(-)
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
- 打开链接 https://qinfendebingshuo.github.io/guanghulab/
- 下拉菜单选择你是谁(冰朔 / 肥猫 / 桔子 / 开发者名字)
- - 填入你的 API Key(没有的话点「演示模式」)
+ - 填入你的 API Key 并点「🔍 自动检测」,系统将自动识别提供商和可用模型
- 点「开始对话」,铸渊会针对你的身份打招呼
- 直接用聊天的方式问铸渊任何问题
@@ -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');