mirror of
https://github.com/TheSmallHanCat/flow2api.git
synced 2026-05-22 20:31:42 +08:00
fix: secure test page defaults and fallback catalog
This commit is contained in:
@@ -17,6 +17,7 @@ h1 { text-align: center; margin-bottom: 8px; font-size: 24px; color: #7c8aff; }
|
||||
}
|
||||
.config-bar label { font-size: 13px; color: #888; white-space: nowrap; }
|
||||
.config-bar input { flex: 1; background: #0f1117; border: 1px solid #2a2b3a; color: #e1e1e6; padding: 8px 12px; border-radius: 6px; font-size: 14px; }
|
||||
.config-note { margin-bottom: 20px; font-size: 12px; color: #7f8498; }
|
||||
|
||||
.main-layout { display: flex; gap: 20px; }
|
||||
.sidebar { width: 280px; flex-shrink: 0; }
|
||||
@@ -127,10 +128,11 @@ h1 { text-align: center; margin-bottom: 8px; font-size: 24px; color: #7c8aff; }
|
||||
|
||||
<div class="config-bar">
|
||||
<label>API Key:</label>
|
||||
<input type="password" id="apiKey" value="han1234" placeholder="输入 API Key">
|
||||
<input type="password" id="apiKey" placeholder="输入 API Key">
|
||||
<label>地址:</label>
|
||||
<input type="text" id="baseUrl" placeholder="http://localhost:8000" />
|
||||
</div>
|
||||
<p class="config-note">未填写 API Key 或模型列表加载失败时,会回退到内置候选模型;实际生成前仍需填写有效 API Key。</p>
|
||||
|
||||
<div class="main-layout">
|
||||
<div class="sidebar" id="modelSidebar"></div>
|
||||
@@ -168,9 +170,6 @@ h1 { text-align: center; margin-bottom: 8px; font-size: 24px; color: #7c8aff; }
|
||||
<script>
|
||||
const STATE = { selectedModel: null, modelConfig: null, images: [], generating: false };
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<script>
|
||||
// Model categories for sidebar
|
||||
const MODEL_CATEGORIES = {
|
||||
@@ -185,28 +184,69 @@ const MODEL_CATEGORIES = {
|
||||
"视频放大 (Upsample)": { filter: m => m.includes("4k") || m.includes("1080p") },
|
||||
};
|
||||
|
||||
const FALLBACK_MODELS = {
|
||||
"gemini-3.1-flash-image": "Image generation (alias) - aspects: landscape, portrait, square, four-three, three-four; sizes: 2k, 4k",
|
||||
"gemini-3.0-pro-image": "Image generation (alias) - aspects: landscape, portrait, square, four-three, three-four; sizes: 2k, 4k",
|
||||
"gemini-2.5-flash-image": "Image generation (alias) - aspects: landscape, portrait",
|
||||
"imagen-4.0-generate-preview": "Image generation (alias) - aspects: landscape, portrait",
|
||||
"veo_3_1_t2v_fast": "Video generation (alias) - supports landscape/portrait via generationConfig",
|
||||
"veo_3_1_t2v_fast_ultra": "Video generation (alias) - supports landscape/portrait via generationConfig",
|
||||
"veo_3_1_t2v_fast_ultra_relaxed": "Video generation (alias) - supports landscape/portrait via generationConfig",
|
||||
"veo_3_1_t2v": "Video generation (alias) - supports landscape/portrait via generationConfig",
|
||||
"veo_3_1_i2v_s_fast_fl": "Video generation (alias) - supports landscape/portrait via generationConfig",
|
||||
"veo_3_1_i2v_s_fast_ultra_fl": "Video generation (alias) - supports landscape/portrait via generationConfig",
|
||||
"veo_3_1_i2v_s_fast_ultra_relaxed": "Video generation (alias) - supports landscape/portrait via generationConfig",
|
||||
"veo_3_1_i2v_s": "Video generation (alias) - supports landscape/portrait via generationConfig",
|
||||
"veo_3_1_r2v_fast": "Video generation (alias) - supports landscape/portrait via generationConfig",
|
||||
"veo_3_1_r2v_fast_ultra": "Video generation (alias) - supports landscape/portrait via generationConfig",
|
||||
"veo_3_1_r2v_fast_ultra_relaxed": "Video generation (alias) - supports landscape/portrait via generationConfig",
|
||||
"veo_2_1_fast_d_15_t2v": "Video generation (alias) - supports landscape/portrait via generationConfig",
|
||||
"veo_2_0_t2v": "Video generation (alias) - supports landscape/portrait via generationConfig",
|
||||
"veo_2_1_fast_d_15_i2v": "Video generation (alias) - supports landscape/portrait via generationConfig",
|
||||
"veo_2_0_i2v": "Video generation (alias) - supports landscape/portrait via generationConfig",
|
||||
"veo_3_1_upsampler_1080p": "Video upsample - 1080p",
|
||||
"veo_3_1_upsampler_4k": "Video upsample - 4k",
|
||||
};
|
||||
|
||||
let ALL_MODELS = {};
|
||||
|
||||
function applyFallbackModels(reason) {
|
||||
console.warn("使用内置候选模型列表:", reason);
|
||||
ALL_MODELS = { ...FALLBACK_MODELS };
|
||||
renderSidebar();
|
||||
}
|
||||
|
||||
async function loadModels() {
|
||||
const baseUrl = document.getElementById("baseUrl").value || "";
|
||||
const apiKey = document.getElementById("apiKey").value;
|
||||
const baseUrl = (document.getElementById("baseUrl").value || "").trim();
|
||||
const apiKey = (document.getElementById("apiKey").value || "").trim();
|
||||
if (!baseUrl || !apiKey) {
|
||||
applyFallbackModels("missing_base_url_or_api_key");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const resp = await fetch(`${baseUrl}/v1/models`, {
|
||||
headers: { "Authorization": `Bearer ${apiKey}` }
|
||||
});
|
||||
if (!resp.ok) {
|
||||
throw new Error(`HTTP ${resp.status}`);
|
||||
}
|
||||
const data = await resp.json();
|
||||
const items = Array.isArray(data.data) ? data.data : [];
|
||||
if (!items.length) {
|
||||
applyFallbackModels("empty_model_catalog");
|
||||
return;
|
||||
}
|
||||
ALL_MODELS = {};
|
||||
(data.data || []).forEach(m => { ALL_MODELS[m.id] = m.description; });
|
||||
items.forEach(m => { ALL_MODELS[m.id] = m.description; });
|
||||
renderSidebar();
|
||||
} catch (e) {
|
||||
console.error("加载模型失败:", e);
|
||||
renderSidebarFallback();
|
||||
applyFallbackModels(e.message || "load_failed");
|
||||
}
|
||||
}
|
||||
|
||||
function renderSidebarFallback() {
|
||||
// Use hardcoded categories if API fails
|
||||
renderSidebar();
|
||||
applyFallbackModels("legacy_fallback");
|
||||
}
|
||||
|
||||
function getModelType(modelId) {
|
||||
@@ -231,7 +271,11 @@ function renderSidebar() {
|
||||
const sidebar = document.getElementById("modelSidebar");
|
||||
sidebar.innerHTML = "";
|
||||
|
||||
const modelIds = Object.keys(ALL_MODELS);
|
||||
const modelIds = Object.keys(ALL_MODELS).sort();
|
||||
if (modelIds.length === 0) {
|
||||
sidebar.innerHTML = '<div class="model-section"><div class="model-list" style="border-radius:8px;"><div class="model-item" style="cursor:default;color:#888;">未获取到可用模型,请检查 API Key 或稍后重试</div></div></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [catName, catDef] of Object.entries(MODEL_CATEGORIES)) {
|
||||
const models = modelIds.filter(catDef.filter);
|
||||
@@ -364,10 +408,12 @@ document.getElementById("btnGenerate").onclick = generate;
|
||||
async function generate() {
|
||||
if (!STATE.selectedModel || STATE.generating) return;
|
||||
|
||||
const baseUrl = document.getElementById("baseUrl").value || "";
|
||||
const apiKey = document.getElementById("apiKey").value;
|
||||
const baseUrl = (document.getElementById("baseUrl").value || "").trim();
|
||||
const apiKey = (document.getElementById("apiKey").value || "").trim();
|
||||
const prompt = document.getElementById("promptInput").value.trim();
|
||||
if (!prompt) { alert("请输入提示词"); return; }
|
||||
if (!apiKey) { alert("请输入 API Key"); return; }
|
||||
if (!baseUrl) { alert("请输入服务地址"); return; }
|
||||
|
||||
STATE.generating = true;
|
||||
const btn = document.getElementById("btnGenerate");
|
||||
@@ -507,8 +553,13 @@ function renderResult(content) {
|
||||
|
||||
// Auto-detect base URL
|
||||
(function() {
|
||||
const input = document.getElementById("baseUrl");
|
||||
input.value = window.location.origin;
|
||||
const baseUrlInput = document.getElementById("baseUrl");
|
||||
const apiKeyInput = document.getElementById("apiKey");
|
||||
baseUrlInput.value = window.location.origin;
|
||||
baseUrlInput.addEventListener("change", loadModels);
|
||||
apiKeyInput.addEventListener("change", loadModels);
|
||||
loadModels();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user