From 09f7c07de345872fab4be831d142fb59780e81f1 Mon Sep 17 00:00:00 2001 From: phamnazage-jpg Date: Wed, 3 Jun 2026 11:24:54 +0800 Subject: [PATCH] feat(portal): make provider/batch-import form fields self-explanatory + auto-fill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: provider manifest form had all-empty fields with cryptic placeholders, users had to know what model IDs to type. Fix on /portal/admin/providers.html (Provider Manifest 草稿): - DISPLAY NAME: datalist of common vendors (OpenAI / DeepSeek / 硅基流动 / Moonshot / 智谱 / Anthropic / 零一万物 / MiniMax / Qwen / Baichuan / 混元) - PLATFORM: datalist of common platforms (openai / openai-compatible / deepseek / anthropic / gemini / zhipu / moonshot / minimax / qwen / ...) - SMOKE TEST MODEL: datalist of common smoke models + auto-fills with first model from MODELS field if user leaves it empty - BASE URL PLACEHOLDER: datalist of common base URLs (12 presets) - MODELS: chip-row of 11 common models (gpt-5.4, gpt-5.4-mini, deepseek-chat, MiniMax-M2.7-highspeed, kimi-k2.6, glm-4.6, claude-sonnet-4-5, gemini-2.5-pro, qwen3-coder-plus, gpt-4o, o4-mini) + clear button. Click chip → append to MODELS field (dedup). - KEYS textarea: 6 rows + example placeholder (sk-example-1/2/3) Fix on /portal/admin-batch-import.html (发起导入): - HOST ID: datalist of common host_ids + hint about loading pack first - ENTRIES textarea: 6 rows + multi-line hint explaining base_url|api_key|model1,model2 format, optional model, batch import JS change: syncDraftHelperState() in providers.html now auto-fills smoke_test_model with first model if user hasn't filled it yet. Also fixed: 2 duplicate copies of syncDraftHelperState (from earlier batch script restoration) — both now have the new logic. Verification: - bash scripts/test/test_tksea_portal_assets.sh → PASS - bash scripts/test/verify_frontend_smoke.sh → PASS - browser_console click test: gpt-5.4 + deepseek-chat + kimi-k2.6 chips → models='gpt-5.4,deepseek-chat,kimi-k2.6' + smoke='gpt-5.4' auto-fill ✓ - screenshot: /tmp/portal-screenshots/admin-providers-v5.png --- deploy/tksea-portal/admin-batch-import.html | 22 +++- deploy/tksea-portal/admin/providers.html | 131 +++++++++++++++++++- 2 files changed, 142 insertions(+), 11 deletions(-) diff --git a/deploy/tksea-portal/admin-batch-import.html b/deploy/tksea-portal/admin-batch-import.html index 33e3fa07..03f542a6 100644 --- a/deploy/tksea-portal/admin-batch-import.html +++ b/deploy/tksea-portal/admin-batch-import.html @@ -1,5 +1,5 @@ - + @@ -87,7 +87,14 @@ @@ -151,9 +158,14 @@
diff --git a/deploy/tksea-portal/admin/providers.html b/deploy/tksea-portal/admin/providers.html index dcfe707a..829456c5 100644 --- a/deploy/tksea-portal/admin/providers.html +++ b/deploy/tksea-portal/admin/providers.html @@ -197,8 +197,10 @@
@@ -225,7 +227,21 @@ 根据 display name / base url / models 自动生成,并尽量避免与现有 provider_id 冲突。
@@ -234,19 +250,74 @@
@@ -557,6 +628,13 @@ if (forceProviderID || state.draftProviderIDAuto || !draftProviderIDInput.value.trim()) { draftProviderIDInput.value = suggested; } + // Auto-fill smoke_test_model with first model if user hasn't filled it yet + if (!draftSmokeModelInput.value.trim()) { + const models = parseDraftModels(); + if (models.length > 0) { + draftSmokeModelInput.value = models[0]; + } + } } function rememberLastPublishedTemplate() { @@ -1441,6 +1519,13 @@ if (forceProviderID || state.draftProviderIDAuto || !draftProviderIDInput.value.trim()) { draftProviderIDInput.value = suggested; } + // Auto-fill smoke_test_model with first model if user hasn't filled it yet + if (!draftSmokeModelInput.value.trim()) { + const models = parseDraftModels(); + if (models.length > 0) { + draftSmokeModelInput.value = models[0]; + } + } } function rememberLastPublishedTemplate() { @@ -2112,6 +2197,40 @@ syncDraftHelperState(); refreshAdminSession().catch(() => {}); renderServerDrafts(); + + // Wire up the "常用模型" preset chips — click to append to Models field. + // Placed after the main script so the input refs are available. + (function wireModelPresets() { + const wrap = document.getElementById("model-presets"); + if (!wrap) return; + wrap.addEventListener("click", (ev) => { + const btn = ev.target.closest("button[data-model]"); + if (!btn) return; + ev.preventDefault(); + const model = btn.getAttribute("data-model"); + const current = (draftModelsInput.value || "") + .split(",") + .map((s) => s.trim()) + .filter(Boolean); + if (current.includes(model)) { + setStatus(document.getElementById("draft-status"), `模型 ${model} 已在列表中`, "note"); + return; + } + current.push(model); + draftModelsInput.value = current.join(","); + syncDraftHelperState(false); + setStatus(document.getElementById("draft-status"), `已加入 ${model}(共 ${current.length} 个)`, "success"); + }); + const clearBtn = wrap.querySelector('[data-action="clear-models"]'); + if (clearBtn) { + clearBtn.addEventListener("click", (ev) => { + ev.preventDefault(); + draftModelsInput.value = ""; + syncDraftHelperState(false); + setStatus(document.getElementById("draft-status"), "已清空 Models 字段", "note"); + }); + } + })();