Logankunfall commited on
Commit
327d153
·
verified ·
1 Parent(s): a05a0c6

Upload 10 files

Browse files
Files changed (1) hide show
  1. public/index.html +559 -79
public/index.html CHANGED
@@ -10,26 +10,45 @@
10
  --fg: #1e293b;
11
  --card: #ffffff;
12
  --muted: #64748b;
13
- --accent: #3b82f6;
 
14
  --border: #e2e8f0;
 
 
15
  }
16
  [data-theme="dark"] {
17
- --bg: #0f172a;
18
- --fg: #f1f5f9;
19
- --card: #1e293b;
20
  --muted: #94a3b8;
21
- --accent: #3b82f6;
22
- --border: #334155;
 
 
 
23
  }
24
- * { box-sizing: border-box; }
25
- html, body { height: 100%; margin: 0; padding: 0; }
 
 
 
 
 
 
 
 
 
 
 
26
  body {
27
  background: var(--bg);
28
  color: var(--fg);
29
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
30
- transition: background-color 0.2s, color 0.2s;
 
31
  }
32
 
 
33
  header {
34
  display: flex;
35
  align-items: center;
@@ -39,169 +58,486 @@
39
  background: var(--card);
40
  position: sticky;
41
  top: 0;
42
- z-index: 10;
 
 
 
 
 
 
 
 
 
 
 
 
43
  }
44
- header h1 { font-size: 18px; margin: 0; }
45
 
46
  .controls {
47
  display: flex;
48
  gap: 8px;
49
  align-items: center;
 
50
  }
 
51
  .toggle-btn {
52
- padding: 4px 8px;
53
  border: 1px solid var(--border);
54
- border-radius: 12px;
55
  background: var(--bg);
56
  color: var(--fg);
57
  font-size: 12px;
58
  cursor: pointer;
59
- transition: all 0.2s;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  }
61
- .toggle-btn:hover { background: var(--accent); color: white; }
62
- .toggle-btn.active { background: var(--accent); color: white; }
63
 
64
  .api-input {
65
  display: flex;
66
  align-items: center;
67
- gap: 6px;
68
- padding: 4px 8px;
69
  border: 1px solid var(--border);
70
- border-radius: 12px;
71
  background: var(--bg);
72
  font-size: 12px;
 
73
  }
 
 
 
 
 
 
74
  .api-input input {
75
  border: none;
76
  background: transparent;
77
  color: var(--fg);
78
- width: 180px;
79
  padding: 2px;
80
  font-size: 12px;
81
  }
 
82
  .api-input input:focus { outline: none; }
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  main {
85
  display: grid;
86
- grid-template-columns: 280px 1fr;
87
- gap: 8px;
88
- padding: 8px;
89
- height: calc(100vh - 60px);
90
  }
91
 
 
92
  .panel {
93
  background: var(--card);
94
  border: 1px solid var(--border);
95
- border-radius: 12px;
96
- padding: 8px;
97
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  }
99
- .panel.results { overflow: auto; }
100
 
101
- .group { margin-bottom: 8px; }
102
  .group label {
103
  display: block;
104
- font-size: 13px;
105
  color: var(--muted);
106
- margin-bottom: 4px;
107
  font-weight: 500;
 
108
  }
 
109
  .group input, .group select, .group textarea {
110
  width: 100%;
111
- padding: 6px;
112
  border: 1px solid var(--border);
113
- border-radius: 8px;
114
  background: var(--bg);
115
  color: var(--fg);
116
  font-size: 14px;
 
 
117
  }
 
118
  .group input:focus, .group select:focus, .group textarea:focus {
119
  outline: none;
120
  border-color: var(--accent);
 
 
 
 
 
 
 
121
  }
122
 
123
  .form-grid {
124
  display: grid;
125
  grid-template-columns: 1fr 1fr;
126
- gap: 6px;
127
  }
128
 
129
  .row {
130
  display: flex;
131
- gap: 6px;
132
  align-items: center;
 
133
  }
134
 
 
135
  button {
136
- background: var(--accent);
137
  color: white;
138
  border: none;
139
- border-radius: 8px;
140
- padding: 8px 12px;
141
  cursor: pointer;
142
  font-size: 14px;
143
- transition: all 0.2s;
 
 
 
 
 
 
144
  }
145
- button:hover { opacity: 0.9; }
 
 
 
 
 
 
 
 
 
146
  button.secondary {
147
- background: transparent;
148
  color: var(--fg);
149
  border: 1px solid var(--border);
150
  }
 
151
  button.secondary:hover {
152
  background: var(--border);
 
153
  }
154
 
155
  .seed-toggle {
156
  background: var(--bg);
157
  color: var(--fg);
158
  border: 1px solid var(--border);
159
- border-radius: 8px;
160
- padding: 6px 10px;
161
  cursor: pointer;
162
  font-size: 14px;
163
- transition: all 0.2s;
 
 
 
 
 
164
  }
 
165
  .seed-toggle.active {
166
  background: var(--accent);
167
  color: white;
168
  border-color: var(--accent);
 
 
 
 
 
 
169
  }
170
 
171
- .status { font-size: 14px; color: var(--muted); }
 
 
 
 
172
 
 
173
  .gallery {
174
  display: grid;
175
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
176
- gap: 8px;
177
  }
 
178
  .card {
179
  background: var(--card);
180
  border: 1px solid var(--border);
181
- border-radius: 8px;
182
  overflow: hidden;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  }
184
- .card img { width: 100%; display: block; }
185
  .card .meta {
186
- padding: 8px;
187
  border-top: 1px solid var(--border);
188
  font-size: 13px;
189
  color: var(--muted);
190
- line-height: 1.4;
191
  }
 
192
  .placeholder {
193
  display: flex;
194
  align-items: center;
195
  justify-content: center;
196
- height: 80px;
197
  color: var(--muted);
198
  font-size: 14px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  }
200
  </style>
201
  </head>
202
  <body>
203
  <header>
204
  <h1>Chutes 图像生成</h1>
 
205
  <div class="controls">
206
  <button id="soundToggle" class="toggle-btn">提示音</button>
207
  <button id="themeToggle" class="toggle-btn">深色主题</button>
@@ -211,8 +547,65 @@
211
  </div>
212
  </div>
213
  </header>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  <main>
215
- <section class="panel">
216
  <div class="group">
217
  <label>模型</label>
218
  <select id="modelSelect"></select>
@@ -284,7 +677,9 @@
284
  theme: 'light',
285
  apiKey: '',
286
  folderHandle: null,
287
- seedRandom: true
 
 
288
  };
289
 
290
  function setTheme(t) {
@@ -317,11 +712,14 @@
317
  }
318
 
319
  function updateSeedToggle() {
320
- const btn = qs('#seedToggle');
321
- if (btn) {
322
- btn.classList.toggle('active', state.seedRandom);
323
- btn.textContent = state.seedRandom ? '使用随机' : '固定种子';
324
- }
 
 
 
325
  }
326
 
327
  function genRandomSeed() {
@@ -345,27 +743,34 @@
345
  }
346
 
347
  function renderModels() {
348
- const sel = qs('#modelSelect');
349
- sel.innerHTML = '';
350
- state.models.forEach(m => {
351
- const opt = document.createElement('option');
352
- opt.value = m.id;
353
- opt.textContent = m.name;
354
- sel.appendChild(opt);
 
 
 
 
 
 
 
355
  });
356
- const last = ls.get('lastParams', null);
357
- if (last && last.model) sel.value = last.model;
358
  }
359
 
360
  function currentParams() {
 
 
361
  return {
362
- model: qs('#modelSelect').value || '',
363
- prompt: (qs('#prompt').value || '').trim(),
364
- negative_prompt: (qs('#negative_prompt').value || '').trim(),
365
- width: Number(qs('#width').value) || 1024,
366
- height: Number(qs('#height').value) || 1024,
367
- guidance_scale: Number(qs('#guidance_scale').value) || 6,
368
- num_inference_steps: Number(qs('#num_inference_steps').value) || 20,
369
  seed: state.seedRandom ? null : 0
370
  };
371
  }
@@ -446,13 +851,66 @@
446
  try {
447
  if (!('showDirectoryPicker' in window)) {
448
  qs('#folderStatus').textContent = '浏览器不支持';
 
449
  return;
450
  }
451
  const handle = await window.showDirectoryPicker();
452
  state.folderHandle = handle;
453
  qs('#folderStatus').textContent = '已选择';
 
454
  } catch(e) {
455
  qs('#folderStatus').textContent = '未选择';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
  }
457
  }
458
 
@@ -545,7 +1003,7 @@
545
  setApiKey(ls.get('apiKey', ''));
546
  updateSeedToggle();
547
 
548
- // 绑定事件
549
  qs('#themeToggle').addEventListener('click', () => {
550
  setTheme(state.theme === 'dark' ? 'light' : 'dark');
551
  });
@@ -564,19 +1022,41 @@
564
  });
565
 
566
  qs('#chooseFolder').addEventListener('click', chooseFolder);
567
-
568
  qs('#generateBtn').addEventListener('click', generate);
569
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570
  qs('#downloadAll').addEventListener('click', () => {
571
  qsa('#gallery img').forEach((img, i) => {
572
  downloadImage(`image_${i+1}.jpg`, img.src);
573
  });
574
  });
575
 
 
576
  document.addEventListener('keydown', (e) => {
577
- if (e.key === 'Enter') generate();
 
578
  });
579
 
 
 
 
 
580
  // 加载模型
581
  fetchModels();
582
  }
 
10
  --fg: #1e293b;
11
  --card: #ffffff;
12
  --muted: #64748b;
13
+ --accent: #6366f1;
14
+ --accent-hover: #4f46e5;
15
  --border: #e2e8f0;
16
+ --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
17
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
18
  }
19
  [data-theme="dark"] {
20
+ --bg: #0f0f23;
21
+ --fg: #e2e8f0;
22
+ --card: #1a1a2e;
23
  --muted: #94a3b8;
24
+ --accent: #7c3aed;
25
+ --accent-hover: #6d28d9;
26
+ --border: #2d2d44;
27
+ --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px 0 rgba(0, 0, 0, 0.2);
28
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
29
  }
30
+
31
+ * {
32
+ box-sizing: border-box;
33
+ -webkit-tap-highlight-color: transparent;
34
+ }
35
+
36
+ html, body {
37
+ height: 100%;
38
+ margin: 0;
39
+ padding: 0;
40
+ overflow-x: hidden;
41
+ }
42
+
43
  body {
44
  background: var(--bg);
45
  color: var(--fg);
46
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans', sans-serif;
47
+ transition: background-color 0.3s ease, color 0.3s ease;
48
+ line-height: 1.5;
49
  }
50
 
51
+ /* Header Styles */
52
  header {
53
  display: flex;
54
  align-items: center;
 
58
  background: var(--card);
59
  position: sticky;
60
  top: 0;
61
+ z-index: 100;
62
+ backdrop-filter: blur(8px);
63
+ box-shadow: var(--shadow);
64
+ }
65
+
66
+ header h1 {
67
+ font-size: 20px;
68
+ margin: 0;
69
+ font-weight: 600;
70
+ background: linear-gradient(135deg, var(--accent), var(--accent-hover));
71
+ -webkit-background-clip: text;
72
+ -webkit-text-fill-color: transparent;
73
+ background-clip: text;
74
  }
 
75
 
76
  .controls {
77
  display: flex;
78
  gap: 8px;
79
  align-items: center;
80
+ flex-wrap: wrap;
81
  }
82
+
83
  .toggle-btn {
84
+ padding: 6px 12px;
85
  border: 1px solid var(--border);
86
+ border-radius: 16px;
87
  background: var(--bg);
88
  color: var(--fg);
89
  font-size: 12px;
90
  cursor: pointer;
91
+ transition: all 0.2s ease;
92
+ white-space: nowrap;
93
+ user-select: none;
94
+ }
95
+
96
+ .toggle-btn:hover {
97
+ background: var(--accent);
98
+ color: white;
99
+ transform: translateY(-1px);
100
+ box-shadow: var(--shadow);
101
+ }
102
+
103
+ .toggle-btn.active {
104
+ background: var(--accent);
105
+ color: white;
106
+ box-shadow: var(--shadow);
107
  }
 
 
108
 
109
  .api-input {
110
  display: flex;
111
  align-items: center;
112
+ gap: 8px;
113
+ padding: 6px 12px;
114
  border: 1px solid var(--border);
115
+ border-radius: 16px;
116
  background: var(--bg);
117
  font-size: 12px;
118
+ transition: all 0.2s ease;
119
  }
120
+
121
+ .api-input:focus-within {
122
+ border-color: var(--accent);
123
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
124
+ }
125
+
126
  .api-input input {
127
  border: none;
128
  background: transparent;
129
  color: var(--fg);
130
+ width: 160px;
131
  padding: 2px;
132
  font-size: 12px;
133
  }
134
+
135
  .api-input input:focus { outline: none; }
136
 
137
+ /* Mobile Menu Toggle */
138
+ .mobile-menu-toggle {
139
+ display: none;
140
+ background: none;
141
+ border: none;
142
+ color: var(--fg);
143
+ font-size: 20px;
144
+ cursor: pointer;
145
+ padding: 8px;
146
+ border-radius: 8px;
147
+ transition: background-color 0.2s ease;
148
+ }
149
+
150
+ .mobile-menu-toggle:hover {
151
+ background: var(--border);
152
+ }
153
+
154
+ /* Main Layout */
155
  main {
156
  display: grid;
157
+ grid-template-columns: 320px 1fr;
158
+ gap: 16px;
159
+ padding: 16px;
160
+ min-height: calc(100vh - 80px);
161
  }
162
 
163
+ /* Panel Styles */
164
  .panel {
165
  background: var(--card);
166
  border: 1px solid var(--border);
167
+ border-radius: 16px;
168
+ padding: 20px;
169
+ box-shadow: var(--shadow);
170
+ transition: all 0.3s ease;
171
+ }
172
+
173
+ .panel.results {
174
+ overflow: auto;
175
+ max-height: calc(100vh - 120px);
176
+ }
177
+
178
+ .panel:hover {
179
+ box-shadow: var(--shadow-lg);
180
+ }
181
+
182
+ /* Form Styles */
183
+ .group {
184
+ margin-bottom: 16px;
185
  }
 
186
 
 
187
  .group label {
188
  display: block;
189
+ font-size: 14px;
190
  color: var(--muted);
191
+ margin-bottom: 6px;
192
  font-weight: 500;
193
+ letter-spacing: 0.025em;
194
  }
195
+
196
  .group input, .group select, .group textarea {
197
  width: 100%;
198
+ padding: 12px 16px;
199
  border: 1px solid var(--border);
200
+ border-radius: 12px;
201
  background: var(--bg);
202
  color: var(--fg);
203
  font-size: 14px;
204
+ transition: all 0.2s ease;
205
+ font-family: inherit;
206
  }
207
+
208
  .group input:focus, .group select:focus, .group textarea:focus {
209
  outline: none;
210
  border-color: var(--accent);
211
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
212
+ transform: translateY(-1px);
213
+ }
214
+
215
+ .group textarea {
216
+ resize: vertical;
217
+ min-height: 80px;
218
  }
219
 
220
  .form-grid {
221
  display: grid;
222
  grid-template-columns: 1fr 1fr;
223
+ gap: 12px;
224
  }
225
 
226
  .row {
227
  display: flex;
228
+ gap: 12px;
229
  align-items: center;
230
+ flex-wrap: wrap;
231
  }
232
 
233
+ /* Button Styles */
234
  button {
235
+ background: linear-gradient(135deg, var(--accent), var(--accent-hover));
236
  color: white;
237
  border: none;
238
+ border-radius: 12px;
239
+ padding: 12px 20px;
240
  cursor: pointer;
241
  font-size: 14px;
242
+ font-weight: 500;
243
+ transition: all 0.2s ease;
244
+ user-select: none;
245
+ min-height: 44px;
246
+ display: inline-flex;
247
+ align-items: center;
248
+ justify-content: center;
249
  }
250
+
251
+ button:hover {
252
+ transform: translateY(-2px);
253
+ box-shadow: var(--shadow-lg);
254
+ }
255
+
256
+ button:active {
257
+ transform: translateY(0);
258
+ }
259
+
260
  button.secondary {
261
+ background: var(--bg);
262
  color: var(--fg);
263
  border: 1px solid var(--border);
264
  }
265
+
266
  button.secondary:hover {
267
  background: var(--border);
268
+ border-color: var(--accent);
269
  }
270
 
271
  .seed-toggle {
272
  background: var(--bg);
273
  color: var(--fg);
274
  border: 1px solid var(--border);
275
+ border-radius: 12px;
276
+ padding: 12px 16px;
277
  cursor: pointer;
278
  font-size: 14px;
279
+ transition: all 0.2s ease;
280
+ width: 100%;
281
+ min-height: 44px;
282
+ display: flex;
283
+ align-items: center;
284
+ justify-content: center;
285
  }
286
+
287
  .seed-toggle.active {
288
  background: var(--accent);
289
  color: white;
290
  border-color: var(--accent);
291
+ box-shadow: var(--shadow);
292
+ }
293
+
294
+ .seed-toggle:hover {
295
+ transform: translateY(-1px);
296
+ box-shadow: var(--shadow);
297
  }
298
 
299
+ .status {
300
+ font-size: 14px;
301
+ color: var(--muted);
302
+ line-height: 1.4;
303
+ }
304
 
305
+ /* Gallery Styles */
306
  .gallery {
307
  display: grid;
308
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
309
+ gap: 16px;
310
  }
311
+
312
  .card {
313
  background: var(--card);
314
  border: 1px solid var(--border);
315
+ border-radius: 16px;
316
  overflow: hidden;
317
+ transition: all 0.3s ease;
318
+ box-shadow: var(--shadow);
319
+ }
320
+
321
+ .card:hover {
322
+ transform: translateY(-4px);
323
+ box-shadow: var(--shadow-lg);
324
+ }
325
+
326
+ .card img {
327
+ width: 100%;
328
+ display: block;
329
+ aspect-ratio: 1;
330
+ object-fit: cover;
331
  }
332
+
333
  .card .meta {
334
+ padding: 16px;
335
  border-top: 1px solid var(--border);
336
  font-size: 13px;
337
  color: var(--muted);
338
+ line-height: 1.5;
339
  }
340
+
341
  .placeholder {
342
  display: flex;
343
  align-items: center;
344
  justify-content: center;
345
+ height: 120px;
346
  color: var(--muted);
347
  font-size: 14px;
348
+ background: var(--bg);
349
+ border-radius: 12px;
350
+ margin: 8px;
351
+ }
352
+
353
+ /* Sidebar for mobile */
354
+ .sidebar {
355
+ position: fixed;
356
+ top: 0;
357
+ left: -100%;
358
+ width: 320px;
359
+ height: 100vh;
360
+ background: var(--card);
361
+ border-right: 1px solid var(--border);
362
+ z-index: 200;
363
+ transition: left 0.3s ease;
364
+ overflow-y: auto;
365
+ box-shadow: var(--shadow-lg);
366
+ }
367
+
368
+ .sidebar.open {
369
+ left: 0;
370
+ }
371
+
372
+ .sidebar-header {
373
+ padding: 16px;
374
+ border-bottom: 1px solid var(--border);
375
+ display: flex;
376
+ justify-content: space-between;
377
+ align-items: center;
378
+ }
379
+
380
+ .sidebar-close {
381
+ background: none;
382
+ border: none;
383
+ color: var(--fg);
384
+ font-size: 20px;
385
+ cursor: pointer;
386
+ padding: 4px;
387
+ border-radius: 4px;
388
+ min-height: auto;
389
+ }
390
+
391
+ .sidebar-close:hover {
392
+ background: var(--border);
393
+ transform: none;
394
+ box-shadow: none;
395
+ }
396
+
397
+ .sidebar-content {
398
+ padding: 16px;
399
+ }
400
+
401
+ .overlay {
402
+ position: fixed;
403
+ top: 0;
404
+ left: 0;
405
+ width: 100%;
406
+ height: 100%;
407
+ background: rgba(0, 0, 0, 0.5);
408
+ z-index: 150;
409
+ opacity: 0;
410
+ visibility: hidden;
411
+ transition: all 0.3s ease;
412
+ }
413
+
414
+ .overlay.show {
415
+ opacity: 1;
416
+ visibility: visible;
417
+ }
418
+
419
+ /* Mobile Responsive Styles */
420
+ @media (max-width: 768px) {
421
+ header {
422
+ padding: 12px 16px;
423
+ flex-wrap: wrap;
424
+ gap: 8px;
425
+ }
426
+
427
+ header h1 {
428
+ font-size: 18px;
429
+ flex: 1;
430
+ }
431
+
432
+ .controls {
433
+ gap: 6px;
434
+ order: 3;
435
+ width: 100%;
436
+ justify-content: space-between;
437
+ margin-top: 8px;
438
+ }
439
+
440
+ .toggle-btn {
441
+ padding: 8px 12px;
442
+ font-size: 11px;
443
+ flex: 1;
444
+ text-align: center;
445
+ }
446
+
447
+ .api-input {
448
+ flex: 1;
449
+ min-width: 0;
450
+ }
451
+
452
+ .api-input input {
453
+ width: 100%;
454
+ min-width: 0;
455
+ }
456
+
457
+ .mobile-menu-toggle {
458
+ display: block;
459
+ order: 2;
460
+ }
461
+
462
+ main {
463
+ grid-template-columns: 1fr;
464
+ gap: 12px;
465
+ padding: 12px;
466
+ min-height: auto;
467
+ }
468
+
469
+ .panel {
470
+ padding: 16px;
471
+ border-radius: 12px;
472
+ }
473
+
474
+ .form-grid {
475
+ grid-template-columns: 1fr;
476
+ gap: 8px;
477
+ }
478
+
479
+ .row {
480
+ flex-direction: column;
481
+ align-items: stretch;
482
+ gap: 8px;
483
+ }
484
+
485
+ .gallery {
486
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
487
+ gap: 12px;
488
+ }
489
+
490
+ .card .meta {
491
+ padding: 12px;
492
+ font-size: 12px;
493
+ }
494
+
495
+ button, .seed-toggle {
496
+ min-height: 48px;
497
+ font-size: 16px;
498
+ }
499
+
500
+ .group input, .group select, .group textarea {
501
+ padding: 14px 16px;
502
+ font-size: 16px;
503
+ border-radius: 8px;
504
+ }
505
+ }
506
+
507
+ @media (max-width: 480px) {
508
+ header {
509
+ padding: 8px 12px;
510
+ }
511
+
512
+ main {
513
+ padding: 8px;
514
+ gap: 8px;
515
+ }
516
+
517
+ .panel {
518
+ padding: 12px;
519
+ }
520
+
521
+ .gallery {
522
+ grid-template-columns: 1fr 1fr;
523
+ gap: 8px;
524
+ }
525
+
526
+ .controls {
527
+ flex-direction: column;
528
+ gap: 8px;
529
+ }
530
+
531
+ .toggle-btn {
532
+ width: 100%;
533
+ }
534
  }
535
  </style>
536
  </head>
537
  <body>
538
  <header>
539
  <h1>Chutes 图像生成</h1>
540
+ <button class="mobile-menu-toggle" id="mobileMenuToggle">☰</button>
541
  <div class="controls">
542
  <button id="soundToggle" class="toggle-btn">提示音</button>
543
  <button id="themeToggle" class="toggle-btn">深色主题</button>
 
547
  </div>
548
  </div>
549
  </header>
550
+
551
+ <!-- Mobile Sidebar -->
552
+ <div class="overlay" id="overlay"></div>
553
+ <div class="sidebar" id="sidebar">
554
+ <div class="sidebar-header">
555
+ <h3 style="margin: 0; font-size: 16px;">参数设置</h3>
556
+ <button class="sidebar-close" id="sidebarClose">✕</button>
557
+ </div>
558
+ <div class="sidebar-content">
559
+ <div class="group">
560
+ <label>模型</label>
561
+ <select id="modelSelectMobile"></select>
562
+ </div>
563
+ <div class="group">
564
+ <label>提示词</label>
565
+ <textarea id="promptMobile" rows="3" placeholder="例如:原神角色 插画风,清晰细节,高质量"></textarea>
566
+ </div>
567
+ <div class="group">
568
+ <label>反向提示词</label>
569
+ <textarea id="negative_promptMobile" rows="2" placeholder="blurry, lowres, bad anatomy, artifacts"></textarea>
570
+ </div>
571
+ <div class="group form-grid">
572
+ <div class="group">
573
+ <label>宽度</label>
574
+ <input type="number" id="widthMobile" value="1024">
575
+ </div>
576
+ <div class="group">
577
+ <label>高度</label>
578
+ <input type="number" id="heightMobile" value="1024">
579
+ </div>
580
+ <div class="group">
581
+ <label>指导系数</label>
582
+ <input type="number" id="guidance_scaleMobile" step="0.1" value="6">
583
+ </div>
584
+ <div class="group">
585
+ <label>推理步数</label>
586
+ <input type="number" id="num_inference_stepsMobile" value="20">
587
+ </div>
588
+ </div>
589
+ <div class="group">
590
+ <button id="seedToggleMobile" class="seed-toggle">使用随机</button>
591
+ </div>
592
+ <div class="group">
593
+ <label>数量(最多10)</label>
594
+ <input type="number" id="batchCountMobile" value="1" placeholder="1-10">
595
+ </div>
596
+ <div class="group">
597
+ <label>保存位置</label>
598
+ <button id="chooseFolderMobile" class="secondary" type="button">选择保存文件夹</button>
599
+ <div class="status" id="folderStatusMobile" style="margin-top: 8px;">未选择</div>
600
+ </div>
601
+ <div class="group">
602
+ <button id="generateBtnMobile">生成图像</button>
603
+ </div>
604
+ </div>
605
+ </div>
606
+
607
  <main>
608
+ <section class="panel" id="desktopPanel">
609
  <div class="group">
610
  <label>模型</label>
611
  <select id="modelSelect"></select>
 
677
  theme: 'light',
678
  apiKey: '',
679
  folderHandle: null,
680
+ seedRandom: true,
681
+ isMobile: window.innerWidth <= 768,
682
+ sidebarOpen: false
683
  };
684
 
685
  function setTheme(t) {
 
712
  }
713
 
714
  function updateSeedToggle() {
715
+ const buttons = ['#seedToggle', '#seedToggleMobile'];
716
+ buttons.forEach(selector => {
717
+ const btn = qs(selector);
718
+ if (btn) {
719
+ btn.classList.toggle('active', state.seedRandom);
720
+ btn.textContent = state.seedRandom ? '使用随机' : '固定种子';
721
+ }
722
+ });
723
  }
724
 
725
  function genRandomSeed() {
 
743
  }
744
 
745
  function renderModels() {
746
+ const selectors = ['#modelSelect', '#modelSelectMobile'];
747
+ selectors.forEach(selector => {
748
+ const sel = qs(selector);
749
+ if (sel) {
750
+ sel.innerHTML = '';
751
+ state.models.forEach(m => {
752
+ const opt = document.createElement('option');
753
+ opt.value = m.id;
754
+ opt.textContent = m.name;
755
+ sel.appendChild(opt);
756
+ });
757
+ const last = ls.get('lastParams', null);
758
+ if (last && last.model) sel.value = last.model;
759
+ }
760
  });
 
 
761
  }
762
 
763
  function currentParams() {
764
+ const isMobile = window.innerWidth <= 768;
765
+ const prefix = isMobile ? 'Mobile' : '';
766
  return {
767
+ model: qs(`#modelSelect${prefix}`).value || '',
768
+ prompt: (qs(`#prompt${prefix}`).value || '').trim(),
769
+ negative_prompt: (qs(`#negative_prompt${prefix}`).value || '').trim(),
770
+ width: Number(qs(`#width${prefix}`).value) || 1024,
771
+ height: Number(qs(`#height${prefix}`).value) || 1024,
772
+ guidance_scale: Number(qs(`#guidance_scale${prefix}`).value) || 6,
773
+ num_inference_steps: Number(qs(`#num_inference_steps${prefix}`).value) || 20,
774
  seed: state.seedRandom ? null : 0
775
  };
776
  }
 
851
  try {
852
  if (!('showDirectoryPicker' in window)) {
853
  qs('#folderStatus').textContent = '浏览器不支持';
854
+ qs('#folderStatusMobile').textContent = '浏览器不支持';
855
  return;
856
  }
857
  const handle = await window.showDirectoryPicker();
858
  state.folderHandle = handle;
859
  qs('#folderStatus').textContent = '已选择';
860
+ qs('#folderStatusMobile').textContent = '已选择';
861
  } catch(e) {
862
  qs('#folderStatus').textContent = '未选择';
863
+ qs('#folderStatusMobile').textContent = '未选择';
864
+ }
865
+ }
866
+
867
+ // 移动端侧边栏控制
868
+ function toggleSidebar() {
869
+ state.sidebarOpen = !state.sidebarOpen;
870
+ const sidebar = qs('#sidebar');
871
+ const overlay = qs('#overlay');
872
+
873
+ if (state.sidebarOpen) {
874
+ sidebar.classList.add('open');
875
+ overlay.classList.add('show');
876
+ syncFormData('desktop', 'mobile');
877
+ } else {
878
+ sidebar.classList.remove('open');
879
+ overlay.classList.remove('show');
880
+ syncFormData('mobile', 'desktop');
881
+ }
882
+ }
883
+
884
+ function closeSidebar() {
885
+ state.sidebarOpen = false;
886
+ qs('#sidebar').classList.remove('open');
887
+ qs('#overlay').classList.remove('show');
888
+ syncFormData('mobile', 'desktop');
889
+ }
890
+
891
+ // 同步表单数据
892
+ function syncFormData(from, to) {
893
+ const fromPrefix = from === 'mobile' ? 'Mobile' : '';
894
+ const toPrefix = to === 'mobile' ? 'Mobile' : '';
895
+
896
+ const fields = ['modelSelect', 'prompt', 'negative_prompt', 'width', 'height', 'guidance_scale', 'num_inference_steps', 'batchCount'];
897
+
898
+ fields.forEach(field => {
899
+ const fromEl = qs(`#${field}${fromPrefix}`);
900
+ const toEl = qs(`#${field}${toPrefix}`);
901
+ if (fromEl && toEl && fromEl.value !== toEl.value) {
902
+ toEl.value = fromEl.value;
903
+ }
904
+ });
905
+ }
906
+
907
+ // 响应式检测
908
+ function checkMobile() {
909
+ const wasMobile = state.isMobile;
910
+ state.isMobile = window.innerWidth <= 768;
911
+
912
+ if (wasMobile !== state.isMobile && state.sidebarOpen) {
913
+ closeSidebar();
914
  }
915
  }
916
 
 
1003
  setApiKey(ls.get('apiKey', ''));
1004
  updateSeedToggle();
1005
 
1006
+ // 桌面端事件绑定
1007
  qs('#themeToggle').addEventListener('click', () => {
1008
  setTheme(state.theme === 'dark' ? 'light' : 'dark');
1009
  });
 
1022
  });
1023
 
1024
  qs('#chooseFolder').addEventListener('click', chooseFolder);
 
1025
  qs('#generateBtn').addEventListener('click', generate);
1026
 
1027
+ // 移动端事件绑定
1028
+ qs('#mobileMenuToggle').addEventListener('click', toggleSidebar);
1029
+ qs('#sidebarClose').addEventListener('click', closeSidebar);
1030
+ qs('#overlay').addEventListener('click', closeSidebar);
1031
+
1032
+ qs('#seedToggleMobile').addEventListener('click', () => {
1033
+ state.seedRandom = !state.seedRandom;
1034
+ updateSeedToggle();
1035
+ });
1036
+
1037
+ qs('#chooseFolderMobile').addEventListener('click', chooseFolder);
1038
+ qs('#generateBtnMobile').addEventListener('click', () => {
1039
+ generate();
1040
+ closeSidebar();
1041
+ });
1042
+
1043
+ // 通用事件绑定
1044
  qs('#downloadAll').addEventListener('click', () => {
1045
  qsa('#gallery img').forEach((img, i) => {
1046
  downloadImage(`image_${i+1}.jpg`, img.src);
1047
  });
1048
  });
1049
 
1050
+ // 键盘事件
1051
  document.addEventListener('keydown', (e) => {
1052
+ if (e.key === 'Enter' && !state.sidebarOpen) generate();
1053
+ if (e.key === 'Escape' && state.sidebarOpen) closeSidebar();
1054
  });
1055
 
1056
+ // 响应式检测
1057
+ window.addEventListener('resize', checkMobile);
1058
+ checkMobile();
1059
+
1060
  // 加载模型
1061
  fetchModels();
1062
  }