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 CHANGED
@@ -5,14 +5,25 @@
5
  "start_url": "/",
6
  "display": "standalone",
7
  "background_color": "#0b0b0c",
8
- "theme_color": "#007AFF",
9
- "orientation": "portrait-primary",
10
  "icons": [
11
  {
12
  "src": "/static/app-icon.svg",
13
  "sizes": "180x180",
14
  "type": "image/svg+xml",
15
- "purpose": "any maskable"
 
 
 
 
 
 
 
 
 
 
 
 
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="black-translucent">
 
 
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 id="promptTitle">编辑指令</h2>
61
- <div class="form-group">
62
- <label for="prompt" id="promptLabel">编辑提示词</label>
63
- <textarea id="prompt" rows="3" placeholder="例如:给模特穿上衣服和鞋子">给模特穿上衣服和鞋子</textarea>
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="imageUrls">或输入图像URL (每行一个)</label>
83
- <textarea id="imageUrls" rows="3" placeholder="https://example.com/image1.jpg&#10;https://example.com/image2.jpg"></textarea>
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
- <div class="form-group custom-size">
116
- <label>自定义高度</label>
117
- <input type="number" id="customHeight" min="1024" max="4096" value="1280" />
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="apiConfigCard2">
158
  <div class="card-header">
159
  <h2>API配置</h2>
160
- <button type="button" class="settings-toggle-btn" onclick="toggleApiConfig2()"
161
- aria-expanded="false" aria-controls="apiConfigContent2" title="展开/收起API配置">
162
  <span class="toggle-icon">▼</span>
163
  </button>
164
  </div>
165
- <div class="settings-content" id="apiConfigContent2">
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()">&times;</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="关闭图片预览">&times;</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>