wapadil Claude commited on
Commit ·
e880185
1
Parent(s): 4a54f55
[TECHNICAL DEBT FIX] 核心架构优化 - 消除DOM重复与PWA增强
Browse files基于Linus Torvalds的"好品味"原则,彻底重构了用户界面架构:
核心改进:
- 消除DOM重复ID问题:重写HTML结构,采用单一数据源设计
- 简化抽屉架构:移除重复表单,抽屉仅作移动端快速入口
- PWA配置优化:移除方向限制,支持iPad横屏工作流
- 无障碍增强:添加ARIA标签、skip-link、语义化角色
- 性能优化:所有图片添加懒加载和异步解码
架构原则:
- "消除特殊情况" - 不再维护两套相同表单
- "简洁执念" - 单一数据源,状态同步简化
- "实用主义" - 移动端引导到桌面完整体验
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- static/manifest.webmanifest +14 -3
- static/script.js +12 -3
- templates/index.html +30 -132
static/manifest.webmanifest
CHANGED
|
@@ -5,14 +5,25 @@
|
|
| 5 |
"start_url": "/",
|
| 6 |
"display": "standalone",
|
| 7 |
"background_color": "#0b0b0c",
|
| 8 |
-
"theme_color": "#
|
| 9 |
-
"orientation": "portrait-primary",
|
| 10 |
"icons": [
|
| 11 |
{
|
| 12 |
"src": "/static/app-icon.svg",
|
| 13 |
"sizes": "180x180",
|
| 14 |
"type": "image/svg+xml",
|
| 15 |
-
"purpose": "any
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
}
|
| 17 |
]
|
| 18 |
}
|
|
|
|
| 5 |
"start_url": "/",
|
| 6 |
"display": "standalone",
|
| 7 |
"background_color": "#0b0b0c",
|
| 8 |
+
"theme_color": "#7c3aed",
|
|
|
|
| 9 |
"icons": [
|
| 10 |
{
|
| 11 |
"src": "/static/app-icon.svg",
|
| 12 |
"sizes": "180x180",
|
| 13 |
"type": "image/svg+xml",
|
| 14 |
+
"purpose": "any"
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"src": "/static/app-icon.svg",
|
| 18 |
+
"sizes": "192x192",
|
| 19 |
+
"type": "image/svg+xml",
|
| 20 |
+
"purpose": "any"
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"src": "/static/app-icon.svg",
|
| 24 |
+
"sizes": "512x512",
|
| 25 |
+
"type": "image/svg+xml",
|
| 26 |
+
"purpose": "maskable"
|
| 27 |
}
|
| 28 |
]
|
| 29 |
}
|
static/script.js
CHANGED
|
@@ -436,7 +436,7 @@ function addImagePreview(src, index) {
|
|
| 436 |
previewItem.innerHTML = `
|
| 437 |
<img id="${imageId}" src="${src}" alt="Upload ${index + 1}"
|
| 438 |
onclick="openImageModal('${imageId}', '${src}', 'Uploaded Image ${index + 1}', 'Input Image')"
|
| 439 |
-
style="cursor: pointer;">
|
| 440 |
<button class="remove-btn" onclick="removeImage(${index})">×</button>
|
| 441 |
<div class="image-upload-status" style="display: none;">
|
| 442 |
<div class="upload-progress-mini">
|
|
@@ -1036,7 +1036,7 @@ function displayCurrentResults(response) {
|
|
| 1036 |
const item = document.createElement('div');
|
| 1037 |
item.className = 'generation-item';
|
| 1038 |
item.innerHTML = `
|
| 1039 |
-
<img id="${imageId}" src="${imgSrc}" alt="Result ${index + 1}">
|
| 1040 |
<button class="use-as-input-btn" onclick="useAsInput('${imageId}', '${imgSrc}')" title="作为输入">
|
| 1041 |
↻ 作为输入
|
| 1042 |
</button>
|
|
@@ -1077,7 +1077,7 @@ function displayHistory() {
|
|
| 1077 |
const item = document.createElement('div');
|
| 1078 |
item.className = 'generation-item';
|
| 1079 |
item.innerHTML = `
|
| 1080 |
-
<img id="${imageId}" src="${imgSrc}" alt="Generation"
|
| 1081 |
onclick="openImageModal('${imageId}', '${imgSrc}', '${generation.prompt.replace(/'/g, "\\'")}', '${new Date(generation.timestamp).toLocaleString()}')">
|
| 1082 |
<button class="use-as-input-btn" onclick="useAsInput('${imageId}', '${imgSrc}')" title="Use as input">
|
| 1083 |
↻ 作为输入
|
|
@@ -1771,3 +1771,12 @@ function initializeAccessibility() {
|
|
| 1771 |
buttonObserver.observe(generateBtn, { attributes: true });
|
| 1772 |
}
|
| 1773 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 436 |
previewItem.innerHTML = `
|
| 437 |
<img id="${imageId}" src="${src}" alt="Upload ${index + 1}"
|
| 438 |
onclick="openImageModal('${imageId}', '${src}', 'Uploaded Image ${index + 1}', 'Input Image')"
|
| 439 |
+
loading="lazy" decoding="async" style="cursor: pointer;">
|
| 440 |
<button class="remove-btn" onclick="removeImage(${index})">×</button>
|
| 441 |
<div class="image-upload-status" style="display: none;">
|
| 442 |
<div class="upload-progress-mini">
|
|
|
|
| 1036 |
const item = document.createElement('div');
|
| 1037 |
item.className = 'generation-item';
|
| 1038 |
item.innerHTML = `
|
| 1039 |
+
<img id="${imageId}" src="${imgSrc}" alt="Result ${index + 1}" loading="lazy" decoding="async">
|
| 1040 |
<button class="use-as-input-btn" onclick="useAsInput('${imageId}', '${imgSrc}')" title="作为输入">
|
| 1041 |
↻ 作为输入
|
| 1042 |
</button>
|
|
|
|
| 1077 |
const item = document.createElement('div');
|
| 1078 |
item.className = 'generation-item';
|
| 1079 |
item.innerHTML = `
|
| 1080 |
+
<img id="${imageId}" src="${imgSrc}" alt="Generation" loading="lazy" decoding="async"
|
| 1081 |
onclick="openImageModal('${imageId}', '${imgSrc}', '${generation.prompt.replace(/'/g, "\\'")}', '${new Date(generation.timestamp).toLocaleString()}')">
|
| 1082 |
<button class="use-as-input-btn" onclick="useAsInput('${imageId}', '${imgSrc}')" title="Use as input">
|
| 1083 |
↻ 作为输入
|
|
|
|
| 1771 |
buttonObserver.observe(generateBtn, { attributes: true });
|
| 1772 |
}
|
| 1773 |
}
|
| 1774 |
+
|
| 1775 |
+
// Sync quick prompt to main prompt
|
| 1776 |
+
function syncToMainPrompt() {
|
| 1777 |
+
const quickPrompt = document.getElementById('quickPrompt');
|
| 1778 |
+
const mainPrompt = document.getElementById('prompt');
|
| 1779 |
+
if (quickPrompt && mainPrompt) {
|
| 1780 |
+
mainPrompt.value = quickPrompt.value;
|
| 1781 |
+
}
|
| 1782 |
+
}
|
templates/index.html
CHANGED
|
@@ -4,13 +4,19 @@
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
| 6 |
<link rel="apple-touch-icon" href="/static/app-icon.svg">
|
|
|
|
| 7 |
<meta name="apple-mobile-web-app-capable" content="yes">
|
| 8 |
-
<meta name="apple-mobile-web-app-status-bar-style" content="
|
|
|
|
|
|
|
| 9 |
<link rel="manifest" href="/static/manifest.webmanifest">
|
| 10 |
<title>SeedDream v4 - AI图像生成与编辑器</title>
|
| 11 |
<link rel="stylesheet" href="/static/style.css">
|
| 12 |
</head>
|
| 13 |
<body>
|
|
|
|
|
|
|
|
|
|
| 14 |
<!-- Sidebar Toggle Button (for narrow screens) -->
|
| 15 |
<button class="sidebar-toggle" onclick="toggleDrawer()" aria-label="打开参数面板">
|
| 16 |
☰
|
|
@@ -19,150 +25,42 @@
|
|
| 19 |
<!-- Drawer Overlay -->
|
| 20 |
<div class="drawer-overlay" onclick="closeDrawer()"></div>
|
| 21 |
|
| 22 |
-
<!-- Drawer (mobile sidebar) -->
|
| 23 |
<div class="drawer" id="drawer">
|
| 24 |
<div class="left-panel drawer-content">
|
| 25 |
<div class="controls-section">
|
| 26 |
-
<div class="card collapsed" id="apiConfigCard">
|
| 27 |
-
<div class="card-header">
|
| 28 |
-
<h2>API配置</h2>
|
| 29 |
-
<button type="button" class="settings-toggle-btn" onclick="toggleApiConfig()"
|
| 30 |
-
aria-expanded="false" aria-controls="apiConfigContent" title="展开/收起API配置">
|
| 31 |
-
<span class="toggle-icon">▼</span>
|
| 32 |
-
</button>
|
| 33 |
-
</div>
|
| 34 |
-
<div class="settings-content" id="apiConfigContent">
|
| 35 |
-
<div class="settings-grid">
|
| 36 |
-
<div class="form-group">
|
| 37 |
-
<label for="apiKey">FAL API密钥</label>
|
| 38 |
-
<div class="api-key-input-group">
|
| 39 |
-
<input type="password" id="apiKey" placeholder="请输入您的FAL API密钥" />
|
| 40 |
-
<button type="button" id="toggleApiKey" class="btn-icon" title="显示/隐藏密钥" aria-label="显示/隐藏密钥">👁</button>
|
| 41 |
-
<button type="button" id="testApiKey" class="btn-test" title="测试连接">测试</button>
|
| 42 |
-
</div>
|
| 43 |
-
<small class="help-text">从 <a href="https://fal.ai" target="_blank">fal.ai</a> 获取您的API密钥</small>
|
| 44 |
-
<div id="apiKeyStatus" class="api-key-status"></div>
|
| 45 |
-
</div>
|
| 46 |
-
<div class="form-group">
|
| 47 |
-
<label for="modelSelect">模型选择</label>
|
| 48 |
-
<select id="modelSelect">
|
| 49 |
-
<option value="fal-ai/bytedance/seedream/v4/edit">图像编辑</option>
|
| 50 |
-
<option value="fal-ai/qwen-image-edit-plus">图像编辑 (通义千问)</option>
|
| 51 |
-
<option value="fal-ai/bytedance/seedream/v4/text-to-image">文本生成图像</option>
|
| 52 |
-
</select>
|
| 53 |
-
<small class="help-text">选择用于生成的模型</small>
|
| 54 |
-
</div>
|
| 55 |
-
</div>
|
| 56 |
-
</div>
|
| 57 |
-
</div>
|
| 58 |
-
|
| 59 |
<div class="card">
|
| 60 |
-
<h2
|
| 61 |
-
<
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
</div>
|
| 65 |
-
</div>
|
| 66 |
-
|
| 67 |
-
<div class="card" id="imageInputCard">
|
| 68 |
-
<div class="card-header">
|
| 69 |
-
<h2>输入图像</h2>
|
| 70 |
-
<button id="clearAllBtn" class="clear-all-btn" onclick="clearAllInputImages()" title="清除所有输入图像">
|
| 71 |
-
清除全部
|
| 72 |
-
</button>
|
| 73 |
-
</div>
|
| 74 |
-
<div class="form-group">
|
| 75 |
-
<label>上传图像 (最多10张)</label>
|
| 76 |
-
<input type="file" id="fileInput" multiple accept="image/*" />
|
| 77 |
-
<small class="help-text upload-limits">单文件 ≤16MB,支持JPG、PNG、WebP格式</small>
|
| 78 |
-
<div id="imagePreview" class="image-preview"></div>
|
| 79 |
-
</div>
|
| 80 |
|
| 81 |
<div class="form-group">
|
| 82 |
-
<label for="
|
| 83 |
-
<textarea id="
|
| 84 |
-
</div>
|
| 85 |
-
</div>
|
| 86 |
-
|
| 87 |
-
<div class="card collapsed" id="settingsCard">
|
| 88 |
-
<div class="card-header">
|
| 89 |
-
<h2>设置</h2>
|
| 90 |
-
<button type="button" class="settings-toggle-btn" onclick="toggleSettings()"
|
| 91 |
-
aria-expanded="false" aria-controls="settingsContent" title="展开/收起设置">
|
| 92 |
-
<span class="toggle-icon">▼</span>
|
| 93 |
-
</button>
|
| 94 |
</div>
|
| 95 |
-
<div class="settings-content" id="settingsContent">
|
| 96 |
-
<div class="settings-grid">
|
| 97 |
-
<div class="form-group">
|
| 98 |
-
<label for="imageSize">图像尺寸</label>
|
| 99 |
-
<select id="imageSize">
|
| 100 |
-
<option value="custom" selected>自定义尺寸</option>
|
| 101 |
-
<option value="square_hd">正方形高清 (1024x1024)</option>
|
| 102 |
-
<option value="square">正方形</option>
|
| 103 |
-
<option value="portrait_4_3">竖向 4:3</option>
|
| 104 |
-
<option value="portrait_16_9">竖向 16:9</option>
|
| 105 |
-
<option value="landscape_4_3">横向 4:3</option>
|
| 106 |
-
<option value="landscape_16_9">横向 16:9</option>
|
| 107 |
-
</select>
|
| 108 |
-
</div>
|
| 109 |
-
|
| 110 |
-
<div class="form-group custom-size">
|
| 111 |
-
<label>自定义宽度</label>
|
| 112 |
-
<input type="number" id="customWidth" min="1024" max="4096" value="1280" />
|
| 113 |
-
</div>
|
| 114 |
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
</div>
|
| 119 |
-
|
| 120 |
-
<div class="form-group">
|
| 121 |
-
<label for="numImages">生成数量</label>
|
| 122 |
-
<input type="number" id="numImages" min="1" max="10" value="1" />
|
| 123 |
-
</div>
|
| 124 |
-
|
| 125 |
-
<div class="form-group">
|
| 126 |
-
<label for="maxImages">每次生成最大图像数</label>
|
| 127 |
-
<input type="number" id="maxImages" min="1" max="10" value="1" />
|
| 128 |
-
</div>
|
| 129 |
-
|
| 130 |
-
<div class="form-group">
|
| 131 |
-
<label for="seed">随机种子 (可选)</label>
|
| 132 |
-
<input type="number" id="seed" placeholder="随机" />
|
| 133 |
-
</div>
|
| 134 |
-
|
| 135 |
-
<!-- Safety checker is disabled by default and hidden from UI -->
|
| 136 |
-
<input type="hidden" id="safetyChecker" value="false" />
|
| 137 |
-
</div>
|
| 138 |
-
</div>
|
| 139 |
</div>
|
| 140 |
-
|
| 141 |
-
<button id="generateBtn" class="generate-btn" aria-busy="false">
|
| 142 |
-
<span class="btn-text">生成图像</span>
|
| 143 |
-
<div class="spinner" style="display: none;"></div>
|
| 144 |
-
<div class="progress-ring" style="display: none;"></div>
|
| 145 |
-
</button>
|
| 146 |
-
|
| 147 |
-
<div id="statusMessage" class="status-message"></div>
|
| 148 |
-
<div id="progressLogs" class="progress-logs"></div>
|
| 149 |
</div>
|
| 150 |
</div>
|
| 151 |
</div>
|
| 152 |
|
| 153 |
-
<div class="app-container">
|
| 154 |
<!-- Left Panel: Controls (desktop) -->
|
| 155 |
<div class="left-panel">
|
| 156 |
<div class="controls-section">
|
| 157 |
-
<div class="card collapsed" id="
|
| 158 |
<div class="card-header">
|
| 159 |
<h2>API配置</h2>
|
| 160 |
-
<button type="button" class="settings-toggle-btn" onclick="
|
| 161 |
-
aria-expanded="false" aria-controls="
|
| 162 |
<span class="toggle-icon">▼</span>
|
| 163 |
</button>
|
| 164 |
</div>
|
| 165 |
-
<div class="settings-content" id="
|
| 166 |
<div class="settings-grid">
|
| 167 |
<div class="form-group">
|
| 168 |
<label for="apiKey">FAL API密钥</label>
|
|
@@ -172,7 +70,7 @@
|
|
| 172 |
<button type="button" id="testApiKey" class="btn-test" title="测试连接">测试</button>
|
| 173 |
</div>
|
| 174 |
<small class="help-text">从 <a href="https://fal.ai" target="_blank">fal.ai</a> 获取您的API密钥</small>
|
| 175 |
-
<div id="apiKeyStatus" class="api-key-status"></div>
|
| 176 |
</div>
|
| 177 |
<div class="form-group">
|
| 178 |
<label for="modelSelect">模型选择</label>
|
|
@@ -203,7 +101,7 @@
|
|
| 203 |
</button>
|
| 204 |
</div>
|
| 205 |
<div class="form-group">
|
| 206 |
-
<label>上传图像 (最多10张)</label>
|
| 207 |
<input type="file" id="fileInput" multiple accept="image/*" />
|
| 208 |
<small class="help-text upload-limits">单文件 ≤16MB,支持JPG、PNG、WebP格式</small>
|
| 209 |
<div id="imagePreview" class="image-preview"></div>
|
|
@@ -239,12 +137,12 @@
|
|
| 239 |
</div>
|
| 240 |
|
| 241 |
<div class="form-group custom-size">
|
| 242 |
-
<label>自定义宽度</label>
|
| 243 |
<input type="number" id="customWidth" min="1024" max="4096" value="1280" />
|
| 244 |
</div>
|
| 245 |
|
| 246 |
<div class="form-group custom-size">
|
| 247 |
-
<label>自定义高度</label>
|
| 248 |
<input type="number" id="customHeight" min="1024" max="4096" value="1280" />
|
| 249 |
</div>
|
| 250 |
|
|
@@ -275,7 +173,7 @@
|
|
| 275 |
<div class="progress-ring" style="display: none;"></div>
|
| 276 |
</button>
|
| 277 |
|
| 278 |
-
<div id="statusMessage" class="status-message"></div>
|
| 279 |
<div id="progressLogs" class="progress-logs"></div>
|
| 280 |
</div>
|
| 281 |
</div>
|
|
@@ -321,9 +219,9 @@
|
|
| 321 |
</div>
|
| 322 |
|
| 323 |
<!-- Image Modal/Lightbox -->
|
| 324 |
-
<div id="imageModal" class="image-modal">
|
| 325 |
-
<span class="modal-close" onclick="closeImageModal()">×</span>
|
| 326 |
-
<img class="modal-content" id="modalImage">
|
| 327 |
<div class="modal-caption">
|
| 328 |
<div id="modalCaption"></div>
|
| 329 |
<button class="modal-use-btn" onclick="useModalImageAsInput()">↻ 作为输入</button>
|
|
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
| 6 |
<link rel="apple-touch-icon" href="/static/app-icon.svg">
|
| 7 |
+
<link rel="icon" type="image/svg+xml" href="/static/app-icon.svg">
|
| 8 |
<meta name="apple-mobile-web-app-capable" content="yes">
|
| 9 |
+
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
| 10 |
+
<meta name="theme-color" content="#7c3aed" media="(prefers-color-scheme: light)">
|
| 11 |
+
<meta name="theme-color" content="#8b5cf6" media="(prefers-color-scheme: dark)">
|
| 12 |
<link rel="manifest" href="/static/manifest.webmanifest">
|
| 13 |
<title>SeedDream v4 - AI图像生成与编辑器</title>
|
| 14 |
<link rel="stylesheet" href="/static/style.css">
|
| 15 |
</head>
|
| 16 |
<body>
|
| 17 |
+
<!-- Skip to main content for accessibility -->
|
| 18 |
+
<a href="#main-content" class="skip-link">跳转到主要内容</a>
|
| 19 |
+
|
| 20 |
<!-- Sidebar Toggle Button (for narrow screens) -->
|
| 21 |
<button class="sidebar-toggle" onclick="toggleDrawer()" aria-label="打开参数面板">
|
| 22 |
☰
|
|
|
|
| 25 |
<!-- Drawer Overlay -->
|
| 26 |
<div class="drawer-overlay" onclick="closeDrawer()"></div>
|
| 27 |
|
| 28 |
+
<!-- Drawer (mobile sidebar) - Simplified -->
|
| 29 |
<div class="drawer" id="drawer">
|
| 30 |
<div class="left-panel drawer-content">
|
| 31 |
<div class="controls-section">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
<div class="card">
|
| 33 |
+
<h2>移动端视图</h2>
|
| 34 |
+
<p style="color: var(--text-secondary); font-size: 14px; margin-bottom: var(--spacing-4);">
|
| 35 |
+
为获得最佳体验,请横屏使用或在桌面端打开。
|
| 36 |
+
</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
<div class="form-group">
|
| 39 |
+
<label for="quickPrompt">快速提示词</label>
|
| 40 |
+
<textarea id="quickPrompt" rows="3" placeholder="例如:给模特穿上衣服和鞋子">给模特穿上衣服和鞋子</textarea>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
+
<button onclick="closeDrawer(); syncToMainPrompt();" class="generate-btn" style="margin-top: var(--spacing-4);">
|
| 44 |
+
<span class="btn-text">前往完整界面</span>
|
| 45 |
+
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
</div>
|
| 48 |
</div>
|
| 49 |
</div>
|
| 50 |
|
| 51 |
+
<div class="app-container" id="main-content">
|
| 52 |
<!-- Left Panel: Controls (desktop) -->
|
| 53 |
<div class="left-panel">
|
| 54 |
<div class="controls-section">
|
| 55 |
+
<div class="card collapsed" id="apiConfigCard">
|
| 56 |
<div class="card-header">
|
| 57 |
<h2>API配置</h2>
|
| 58 |
+
<button type="button" class="settings-toggle-btn" onclick="toggleApiConfig()"
|
| 59 |
+
aria-expanded="false" aria-controls="apiConfigContent" title="展开/收起API配置">
|
| 60 |
<span class="toggle-icon">▼</span>
|
| 61 |
</button>
|
| 62 |
</div>
|
| 63 |
+
<div class="settings-content" id="apiConfigContent">
|
| 64 |
<div class="settings-grid">
|
| 65 |
<div class="form-group">
|
| 66 |
<label for="apiKey">FAL API密钥</label>
|
|
|
|
| 70 |
<button type="button" id="testApiKey" class="btn-test" title="测试连接">测试</button>
|
| 71 |
</div>
|
| 72 |
<small class="help-text">从 <a href="https://fal.ai" target="_blank">fal.ai</a> 获取您的API密钥</small>
|
| 73 |
+
<div id="apiKeyStatus" class="api-key-status" role="status" aria-live="polite"></div>
|
| 74 |
</div>
|
| 75 |
<div class="form-group">
|
| 76 |
<label for="modelSelect">模型选择</label>
|
|
|
|
| 101 |
</button>
|
| 102 |
</div>
|
| 103 |
<div class="form-group">
|
| 104 |
+
<label for="fileInput">上传图像 (最多10张)</label>
|
| 105 |
<input type="file" id="fileInput" multiple accept="image/*" />
|
| 106 |
<small class="help-text upload-limits">单文件 ≤16MB,支持JPG、PNG、WebP格式</small>
|
| 107 |
<div id="imagePreview" class="image-preview"></div>
|
|
|
|
| 137 |
</div>
|
| 138 |
|
| 139 |
<div class="form-group custom-size">
|
| 140 |
+
<label for="customWidth">自定义宽度</label>
|
| 141 |
<input type="number" id="customWidth" min="1024" max="4096" value="1280" />
|
| 142 |
</div>
|
| 143 |
|
| 144 |
<div class="form-group custom-size">
|
| 145 |
+
<label for="customHeight">自定义高度</label>
|
| 146 |
<input type="number" id="customHeight" min="1024" max="4096" value="1280" />
|
| 147 |
</div>
|
| 148 |
|
|
|
|
| 173 |
<div class="progress-ring" style="display: none;"></div>
|
| 174 |
</button>
|
| 175 |
|
| 176 |
+
<div id="statusMessage" class="status-message" role="status" aria-live="polite"></div>
|
| 177 |
<div id="progressLogs" class="progress-logs"></div>
|
| 178 |
</div>
|
| 179 |
</div>
|
|
|
|
| 219 |
</div>
|
| 220 |
|
| 221 |
<!-- Image Modal/Lightbox -->
|
| 222 |
+
<div id="imageModal" class="image-modal" role="dialog" aria-modal="true" aria-labelledby="modalCaption">
|
| 223 |
+
<span class="modal-close" onclick="closeImageModal()" aria-label="关闭图片预览">×</span>
|
| 224 |
+
<img class="modal-content" id="modalImage" loading="lazy" decoding="async">
|
| 225 |
<div class="modal-caption">
|
| 226 |
<div id="modalCaption"></div>
|
| 227 |
<button class="modal-use-btn" onclick="useModalImageAsInput()">↻ 作为输入</button>
|