samlax12 commited on
Commit
7c9ffd6
·
verified ·
1 Parent(s): 8811a49

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +613 -578
templates/index.html CHANGED
@@ -1,579 +1,614 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>AI图像工作室</title>
7
- <style>
8
- * {
9
- margin: 0;
10
- padding: 0;
11
- box-sizing: border-box;
12
- }
13
-
14
- body {
15
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
16
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
- min-height: 100vh;
18
- color: #333;
19
- }
20
-
21
- .container {
22
- max-width: 1200px;
23
- margin: 0 auto;
24
- padding: 2rem;
25
- }
26
-
27
- .header {
28
- text-align: center;
29
- margin-bottom: 3rem;
30
- color: white;
31
- }
32
-
33
- .header h1 {
34
- font-size: 3rem;
35
- margin-bottom: 0.5rem;
36
- text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
37
- }
38
-
39
- .header p {
40
- font-size: 1.2rem;
41
- opacity: 0.9;
42
- }
43
-
44
- .tabs {
45
- display: flex;
46
- justify-content: center;
47
- margin-bottom: 2rem;
48
- gap: 1rem;
49
- }
50
-
51
- .tab {
52
- padding: 1rem 2rem;
53
- background: rgba(255, 255, 255, 0.2);
54
- border: 2px solid rgba(255, 255, 255, 0.3);
55
- border-radius: 50px;
56
- color: white;
57
- cursor: pointer;
58
- transition: all 0.3s ease;
59
- font-weight: 600;
60
- backdrop-filter: blur(10px);
61
- }
62
-
63
- .tab:hover {
64
- background: rgba(255, 255, 255, 0.3);
65
- transform: translateY(-2px);
66
- }
67
-
68
- .tab.active {
69
- background: white;
70
- color: #667eea;
71
- }
72
-
73
- .content {
74
- background: white;
75
- border-radius: 20px;
76
- padding: 2rem;
77
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
78
- }
79
-
80
- .form-group {
81
- margin-bottom: 1.5rem;
82
- }
83
-
84
- label {
85
- display: block;
86
- margin-bottom: 0.5rem;
87
- font-weight: 600;
88
- color: #555;
89
- }
90
-
91
- textarea {
92
- width: 100%;
93
- min-height: 120px;
94
- padding: 1rem;
95
- border: 2px solid #e0e0e0;
96
- border-radius: 10px;
97
- font-size: 1rem;
98
- transition: border-color 0.3s ease;
99
- resize: vertical;
100
- }
101
-
102
- textarea:focus {
103
- outline: none;
104
- border-color: #667eea;
105
- }
106
-
107
- select {
108
- width: 100%;
109
- padding: 0.75rem;
110
- border: 2px solid #e0e0e0;
111
- border-radius: 10px;
112
- font-size: 1rem;
113
- background: white;
114
- cursor: pointer;
115
- }
116
-
117
- .options-grid {
118
- display: grid;
119
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
120
- gap: 1rem;
121
- }
122
-
123
- .file-upload {
124
- position: relative;
125
- display: inline-block;
126
- cursor: pointer;
127
- width: 100%;
128
- }
129
-
130
- .file-upload input[type="file"] {
131
- position: absolute;
132
- opacity: 0;
133
- width: 100%;
134
- height: 100%;
135
- cursor: pointer;
136
- }
137
-
138
- .file-upload-label {
139
- display: block;
140
- padding: 2rem;
141
- border: 3px dashed #667eea;
142
- border-radius: 10px;
143
- text-align: center;
144
- background: #f8f9ff;
145
- transition: all 0.3s ease;
146
- }
147
-
148
- .file-upload:hover .file-upload-label {
149
- background: #eef0ff;
150
- border-color: #764ba2;
151
- }
152
-
153
- .generate-btn {
154
- width: 100%;
155
- padding: 1.25rem;
156
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
157
- color: white;
158
- border: none;
159
- border-radius: 10px;
160
- font-size: 1.1rem;
161
- font-weight: 600;
162
- cursor: pointer;
163
- transition: all 0.3s ease;
164
- margin-top: 1rem;
165
- }
166
-
167
- .generate-btn:hover {
168
- transform: translateY(-2px);
169
- box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
170
- }
171
-
172
- .generate-btn:disabled {
173
- opacity: 0.6;
174
- cursor: not-allowed;
175
- transform: none;
176
- }
177
-
178
- .result-section {
179
- margin-top: 3rem;
180
- text-align: center;
181
- }
182
-
183
- .result-image {
184
- max-width: 100%;
185
- border-radius: 10px;
186
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
187
- margin-top: 1rem;
188
- }
189
-
190
- .loading {
191
- display: inline-block;
192
- width: 20px;
193
- height: 20px;
194
- border: 3px solid #f3f3f3;
195
- border-top: 3px solid #667eea;
196
- border-radius: 50%;
197
- animation: spin 1s linear infinite;
198
- margin-right: 10px;
199
- }
200
-
201
- @keyframes spin {
202
- 0% { transform: rotate(0deg); }
203
- 100% { transform: rotate(360deg); }
204
- }
205
-
206
- .error-message {
207
- background: #fee;
208
- color: #c33;
209
- padding: 1rem;
210
- border-radius: 10px;
211
- margin-top: 1rem;
212
- }
213
-
214
- .success-message {
215
- background: #efe;
216
- color: #3c3;
217
- padding: 1rem;
218
- border-radius: 10px;
219
- margin-top: 1rem;
220
- }
221
-
222
- .hidden {
223
- display: none;
224
- }
225
-
226
- .image-preview {
227
- display: flex;
228
- flex-wrap: wrap;
229
- gap: 1rem;
230
- margin-top: 1rem;
231
- }
232
-
233
- .preview-item {
234
- position: relative;
235
- width: 150px;
236
- height: 150px;
237
- border-radius: 10px;
238
- overflow: hidden;
239
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
240
- }
241
-
242
- .preview-item img {
243
- width: 100%;
244
- height: 100%;
245
- object-fit: cover;
246
- }
247
-
248
- .preview-item .remove {
249
- position: absolute;
250
- top: 5px;
251
- right: 5px;
252
- background: rgba(255, 255, 255, 0.9);
253
- color: #c33;
254
- border: none;
255
- border-radius: 50%;
256
- width: 30px;
257
- height: 30px;
258
- cursor: pointer;
259
- font-weight: bold;
260
- }
261
-
262
- .usage-info {
263
- margin-top: 1rem;
264
- padding: 1rem;
265
- background: #f0f4ff;
266
- border-radius: 10px;
267
- font-size: 0.9rem;
268
- color: #666;
269
- }
270
-
271
- .download-btn {
272
- margin-top: 1rem;
273
- padding: 0.75rem 1.5rem;
274
- background: #4CAF50;
275
- color: white;
276
- border: none;
277
- border-radius: 10px;
278
- font-size: 1rem;
279
- cursor: pointer;
280
- transition: all 0.3s ease;
281
- }
282
-
283
- .download-btn:hover {
284
- background: #45a049;
285
- transform: translateY(-2px);
286
- }
287
- </style>
288
- </head>
289
- <body>
290
- <div class="container">
291
- <header class="header">
292
- <h1>🎨 AI 图像工作室</h1>
293
- <p>使用人工智能的力量创造惊艳图像</p>
294
- </header>
295
-
296
- <div class="tabs">
297
- <div class="tab active" onclick="switchTab('generate')">生成图像</div>
298
- <div class="tab" onclick="switchTab('edit')">编辑图像</div>
299
- </div>
300
-
301
- <div class="content">
302
- <!-- Generate Tab -->
303
- <div id="generate-tab" class="tab-content">
304
- <form id="generate-form">
305
- <div class="form-group">
306
- <label for="prompt">描述您想要的图像</label>
307
- <textarea id="prompt" name="prompt" placeholder="夕阳下的宁静风景,山峦倒映在晶莹剔透的湖面上,水彩画风格..." required></textarea>
308
- </div>
309
-
310
- <div class="options-grid">
311
- <div class="form-group">
312
- <label for="size">图像尺寸</label>
313
- <select id="size" name="size">
314
- <option value="1024x1024">正方形 (1024x1024)</option>
315
- <option value="1792x1024">横向 (1792x1024)</option>
316
- <option value="1024x1792">纵向 (1024x1792)</option>
317
- </select>
318
- </div>
319
-
320
- <div class="form-group">
321
- <label for="quality">图像质量</label>
322
- <select id="quality" name="quality">
323
- <option value="standard">标准</option>
324
- <option value="hd">高清 (费用更高)</option>
325
- </select>
326
- </div>
327
-
328
- <div class="form-group">
329
- <label for="style">风格</label>
330
- <select id="style" name="style">
331
- <option value="vivid">生动</option>
332
- <option value="natural">自然</option>
333
- </select>
334
- </div>
335
- </div>
336
-
337
- <button type="submit" class="generate-btn">
338
- <span class="btn-text">生成图像</span>
339
- </button>
340
- </form>
341
- </div>
342
-
343
- <!-- Edit Tab -->
344
- <div id="edit-tab" class="tab-content hidden">
345
- <form id="edit-form">
346
- <div class="form-group">
347
- <label>上传图像(最多16张)</label>
348
- <div class="file-upload">
349
- <input type="file" id="images" name="images" multiple accept="image/*" required>
350
- <label for="images" class="file-upload-label">
351
- <div>📁 点击或拖拽图像到这里</div>
352
- <small>支持 PNG、JPG、WEBP 格式(每个文件最大 25MB)</small>
353
- </label>
354
- </div>
355
- <div id="image-preview" class="image-preview"></div>
356
- </div>
357
-
358
- <div class="form-group">
359
- <label for="edit-prompt">描述如何组合/编辑这些图像</label>
360
- <textarea id="edit-prompt" name="prompt" placeholder="将这些图像制作成复古相册风格的精美拼贴画..." required></textarea>
361
- </div>
362
-
363
- <div class="form-group">
364
- <label for="edit-size">输出尺寸</label>
365
- <select id="edit-size" name="size">
366
- <option value="256x256">小尺寸 (256x256)</option>
367
- <option value="512x512">中尺寸 (512x512)</option>
368
- <option value="1024x1024">大尺寸 (1024x1024)</option>
369
- </select>
370
- </div>
371
-
372
- <button type="submit" class="generate-btn">
373
- <span class="btn-text">编辑图像</span>
374
- </button>
375
- </form>
376
- </div>
377
-
378
- <div id="result-section" class="result-section hidden">
379
- <h2>您生成的图像</h2>
380
- <img id="result-image" class="result-image" alt="生成的图像">
381
- <div id="usage-info" class="usage-info hidden"></div>
382
- <button id="download-btn" class="download-btn" onclick="downloadImage()">下载图像</button>
383
- </div>
384
-
385
- <div id="message" class="hidden"></div>
386
- </div>
387
- </div>
388
-
389
- <script>
390
- let currentTab = 'generate';
391
- let generatedImageData = null;
392
- let selectedFiles = [];
393
-
394
- function switchTab(tab) {
395
- currentTab = tab;
396
- document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
397
- document.querySelectorAll('.tab-content').forEach(t => t.classList.add('hidden'));
398
-
399
- event.target.classList.add('active');
400
- document.getElementById(`${tab}-tab`).classList.remove('hidden');
401
-
402
- // Hide result when switching tabs
403
- document.getElementById('result-section').classList.add('hidden');
404
- }
405
-
406
- // Generate form submission
407
- document.getElementById('generate-form').addEventListener('submit', async (e) => {
408
- e.preventDefault();
409
-
410
- const btn = e.target.querySelector('.generate-btn');
411
- const btnText = btn.querySelector('.btn-text');
412
-
413
- // Disable button and show loading
414
- btn.disabled = true;
415
- btnText.innerHTML = '<span class="loading"></span>生成中...';
416
-
417
- // Hide previous results
418
- showMessage('');
419
- document.getElementById('result-section').classList.add('hidden');
420
-
421
- const formData = {
422
- prompt: document.getElementById('prompt').value,
423
- size: document.getElementById('size').value,
424
- quality: document.getElementById('quality').value,
425
- style: document.getElementById('style').value
426
- };
427
-
428
- try {
429
- const response = await fetch('/generate', {
430
- method: 'POST',
431
- headers: {
432
- 'Content-Type': 'application/json'
433
- },
434
- body: JSON.stringify(formData)
435
- });
436
-
437
- const data = await response.json();
438
-
439
- if (data.success) {
440
- generatedImageData = data.image;
441
- document.getElementById('result-image').src = data.image;
442
- document.getElementById('result-section').classList.remove('hidden');
443
-
444
- if (data.usage) {
445
- const usageInfo = document.getElementById('usage-info');
446
- usageInfo.innerHTML = `使用的令牌数: ${data.usage.total_tokens || '未知'}`;
447
- usageInfo.classList.remove('hidden');
448
- }
449
-
450
- showMessage('图像生成成功!', 'success');
451
- } else {
452
- showMessage(data.error || '发生错误', 'error');
453
- }
454
- } catch (error) {
455
- showMessage('网络错误: ' + error.message, 'error');
456
- } finally {
457
- btn.disabled = false;
458
- btnText.innerHTML = '生成图像';
459
- }
460
- });
461
-
462
- // Edit form submission
463
- document.getElementById('edit-form').addEventListener('submit', async (e) => {
464
- e.preventDefault();
465
-
466
- const btn = e.target.querySelector('.generate-btn');
467
- const btnText = btn.querySelector('.btn-text');
468
-
469
- if (selectedFiles.length === 0) {
470
- showMessage('请至少选择一张图像', 'error');
471
- return;
472
- }
473
-
474
- // Disable button and show loading
475
- btn.disabled = true;
476
- btnText.innerHTML = '<span class="loading"></span>处理中...';
477
-
478
- // Hide previous results
479
- showMessage('');
480
- document.getElementById('result-section').classList.add('hidden');
481
-
482
- const formData = new FormData();
483
- selectedFiles.forEach(file => {
484
- formData.append('images', file);
485
- });
486
- formData.append('prompt', document.getElementById('edit-prompt').value);
487
- formData.append('size', document.getElementById('edit-size').value);
488
-
489
- try {
490
- const response = await fetch('/edit', {
491
- method: 'POST',
492
- body: formData
493
- });
494
-
495
- const data = await response.json();
496
-
497
- if (data.success) {
498
- generatedImageData = data.image;
499
- document.getElementById('result-image').src = data.image;
500
- document.getElementById('result-section').classList.remove('hidden');
501
-
502
- if (data.usage) {
503
- const usageInfo = document.getElementById('usage-info');
504
- usageInfo.innerHTML = `使用的令牌数: ${data.usage.total_tokens || '未知'}`;
505
- usageInfo.classList.remove('hidden');
506
- }
507
-
508
- showMessage('图像编辑成功!', 'success');
509
- } else {
510
- showMessage(data.error || '发生错误', 'error');
511
- }
512
- } catch (error) {
513
- showMessage('网络错误: ' + error.message, 'error');
514
- } finally {
515
- btn.disabled = false;
516
- btnText.innerHTML = '编辑图像';
517
- }
518
- });
519
-
520
- // File upload handling
521
- document.getElementById('images').addEventListener('change', (e) => {
522
- const files = Array.from(e.target.files);
523
- selectedFiles = files.slice(0, 16); // Limit to 16 files
524
-
525
- const preview = document.getElementById('image-preview');
526
- preview.innerHTML = '';
527
-
528
- selectedFiles.forEach((file, index) => {
529
- const reader = new FileReader();
530
- reader.onload = (e) => {
531
- const div = document.createElement('div');
532
- div.className = 'preview-item';
533
- div.innerHTML = `
534
- <img src="${e.target.result}" alt="Preview">
535
- <button class="remove" onclick="removeImage(${index})">×</button>
536
- `;
537
- preview.appendChild(div);
538
- };
539
- reader.readAsDataURL(file);
540
- });
541
- });
542
-
543
- function removeImage(index) {
544
- selectedFiles.splice(index, 1);
545
-
546
- // Update file input
547
- const dt = new DataTransfer();
548
- selectedFiles.forEach(file => dt.items.add(file));
549
- document.getElementById('images').files = dt.files;
550
-
551
- // Trigger change event to update preview
552
- document.getElementById('images').dispatchEvent(new Event('change'));
553
- }
554
-
555
- function showMessage(message, type = '') {
556
- const messageEl = document.getElementById('message');
557
- if (!message) {
558
- messageEl.classList.add('hidden');
559
- return;
560
- }
561
-
562
- messageEl.textContent = message;
563
- messageEl.className = type === 'error' ? 'error-message' :
564
- type === 'success' ? 'success-message' : '';
565
- }
566
-
567
- function downloadImage() {
568
- if (!generatedImageData) return;
569
-
570
- const link = document.createElement('a');
571
- link.href = generatedImageData;
572
- link.download = `ai-generated-${Date.now()}.png`;
573
- document.body.appendChild(link);
574
- link.click();
575
- document.body.removeChild(link);
576
- }
577
- </script>
578
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
579
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI图像工作室</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ color: #333;
19
+ }
20
+
21
+ .container {
22
+ max-width: 1200px;
23
+ margin: 0 auto;
24
+ padding: 2rem;
25
+ }
26
+
27
+ .header {
28
+ text-align: center;
29
+ margin-bottom: 3rem;
30
+ color: white;
31
+ }
32
+
33
+ .header h1 {
34
+ font-size: 3rem;
35
+ margin-bottom: 0.5rem;
36
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
37
+ }
38
+
39
+ .header p {
40
+ font-size: 1.2rem;
41
+ opacity: 0.9;
42
+ }
43
+
44
+ .tabs {
45
+ display: flex;
46
+ justify-content: center;
47
+ margin-bottom: 2rem;
48
+ gap: 1rem;
49
+ }
50
+
51
+ .tab {
52
+ padding: 1rem 2rem;
53
+ background: rgba(255, 255, 255, 0.2);
54
+ border: 2px solid rgba(255, 255, 255, 0.3);
55
+ border-radius: 50px;
56
+ color: white;
57
+ cursor: pointer;
58
+ transition: all 0.3s ease;
59
+ font-weight: 600;
60
+ backdrop-filter: blur(10px);
61
+ }
62
+
63
+ .tab:hover {
64
+ background: rgba(255, 255, 255, 0.3);
65
+ transform: translateY(-2px);
66
+ }
67
+
68
+ .tab.active {
69
+ background: white;
70
+ color: #667eea;
71
+ }
72
+
73
+ .content {
74
+ background: white;
75
+ border-radius: 20px;
76
+ padding: 2rem;
77
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
78
+ }
79
+
80
+ .form-group {
81
+ margin-bottom: 1.5rem;
82
+ }
83
+
84
+ label {
85
+ display: block;
86
+ margin-bottom: 0.5rem;
87
+ font-weight: 600;
88
+ color: #555;
89
+ }
90
+
91
+ textarea {
92
+ width: 100%;
93
+ min-height: 120px;
94
+ padding: 1rem;
95
+ border: 2px solid #e0e0e0;
96
+ border-radius: 10px;
97
+ font-size: 1rem;
98
+ transition: border-color 0.3s ease;
99
+ resize: vertical;
100
+ }
101
+
102
+ textarea:focus {
103
+ outline: none;
104
+ border-color: #667eea;
105
+ }
106
+
107
+ select {
108
+ width: 100%;
109
+ padding: 0.75rem;
110
+ border: 2px solid #e0e0e0;
111
+ border-radius: 10px;
112
+ font-size: 1rem;
113
+ background: white;
114
+ cursor: pointer;
115
+ }
116
+
117
+ .options-grid {
118
+ display: grid;
119
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
120
+ gap: 1rem;
121
+ }
122
+
123
+ .file-upload {
124
+ position: relative;
125
+ display: inline-block;
126
+ cursor: pointer;
127
+ width: 100%;
128
+ }
129
+
130
+ .file-upload input[type="file"] {
131
+ position: absolute;
132
+ opacity: 0;
133
+ width: 100%;
134
+ height: 100%;
135
+ cursor: pointer;
136
+ }
137
+
138
+ .file-upload-label {
139
+ display: block;
140
+ padding: 2rem;
141
+ border: 3px dashed #667eea;
142
+ border-radius: 10px;
143
+ text-align: center;
144
+ background: #f8f9ff;
145
+ transition: all 0.3s ease;
146
+ }
147
+
148
+ .file-upload:hover .file-upload-label {
149
+ background: #eef0ff;
150
+ border-color: #764ba2;
151
+ }
152
+
153
+ .generate-btn {
154
+ width: 100%;
155
+ padding: 1.25rem;
156
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
157
+ color: white;
158
+ border: none;
159
+ border-radius: 10px;
160
+ font-size: 1.1rem;
161
+ font-weight: 600;
162
+ cursor: pointer;
163
+ transition: all 0.3s ease;
164
+ margin-top: 1rem;
165
+ }
166
+
167
+ .generate-btn:hover {
168
+ transform: translateY(-2px);
169
+ box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
170
+ }
171
+
172
+ .generate-btn:disabled {
173
+ opacity: 0.6;
174
+ cursor: not-allowed;
175
+ transform: none;
176
+ }
177
+
178
+ .result-section {
179
+ margin-top: 3rem;
180
+ text-align: center;
181
+ }
182
+
183
+ .result-image {
184
+ max-width: 100%;
185
+ border-radius: 10px;
186
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
187
+ margin-top: 1rem;
188
+ }
189
+
190
+ .loading {
191
+ display: inline-block;
192
+ width: 20px;
193
+ height: 20px;
194
+ border: 3px solid #f3f3f3;
195
+ border-top: 3px solid #667eea;
196
+ border-radius: 50%;
197
+ animation: spin 1s linear infinite;
198
+ margin-right: 10px;
199
+ }
200
+
201
+ @keyframes spin {
202
+ 0% { transform: rotate(0deg); }
203
+ 100% { transform: rotate(360deg); }
204
+ }
205
+
206
+ .error-message {
207
+ background: #fee;
208
+ color: #c33;
209
+ padding: 1rem;
210
+ border-radius: 10px;
211
+ margin-top: 1rem;
212
+ }
213
+
214
+ .success-message {
215
+ background: #efe;
216
+ color: #3c3;
217
+ padding: 1rem;
218
+ border-radius: 10px;
219
+ margin-top: 1rem;
220
+ }
221
+
222
+ .hidden {
223
+ display: none;
224
+ }
225
+
226
+ .image-preview {
227
+ display: flex;
228
+ flex-wrap: wrap;
229
+ gap: 1rem;
230
+ margin-top: 1rem;
231
+ }
232
+
233
+ .preview-item {
234
+ position: relative;
235
+ width: 150px;
236
+ height: 150px;
237
+ border-radius: 10px;
238
+ overflow: hidden;
239
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
240
+ }
241
+
242
+ .preview-item img {
243
+ width: 100%;
244
+ height: 100%;
245
+ object-fit: cover;
246
+ }
247
+
248
+ .preview-item .remove {
249
+ position: absolute;
250
+ top: 5px;
251
+ right: 5px;
252
+ background: rgba(255, 255, 255, 0.9);
253
+ color: #c33;
254
+ border: none;
255
+ border-radius: 50%;
256
+ width: 30px;
257
+ height: 30px;
258
+ cursor: pointer;
259
+ font-weight: bold;
260
+ }
261
+
262
+ .usage-info {
263
+ margin-top: 1rem;
264
+ padding: 1rem;
265
+ background: #f0f4ff;
266
+ border-radius: 10px;
267
+ font-size: 0.9rem;
268
+ color: #666;
269
+ }
270
+
271
+ .download-btn {
272
+ margin-top: 1rem;
273
+ padding: 0.75rem 1.5rem;
274
+ background: #4CAF50;
275
+ color: white;
276
+ border: none;
277
+ border-radius: 10px;
278
+ font-size: 1rem;
279
+ cursor: pointer;
280
+ transition: all 0.3s ease;
281
+ }
282
+
283
+ .download-btn:hover {
284
+ background: #45a049;
285
+ transform: translateY(-2px);
286
+ }
287
+ </style>
288
+ </head>
289
+ <body>
290
+ <div class="container">
291
+ <header class="header">
292
+ <h1>🎨 AI 图像工作室</h1>
293
+ <p>使用人工智能的力量创造惊艳图像</p>
294
+ </header>
295
+
296
+ <div class="tabs">
297
+ <div class="tab active" onclick="switchTab('generate')">生成图像</div>
298
+ <div class="tab" onclick="switchTab('edit')">编辑图像</div>
299
+ </div>
300
+
301
+ <div class="content">
302
+ <!-- Generate Tab -->
303
+ <div id="generate-tab" class="tab-content">
304
+ <form id="generate-form">
305
+ <div class="form-group">
306
+ <label for="prompt">描述您想要的图像</label>
307
+ <textarea id="prompt" name="prompt" placeholder="夕阳下的宁静风景,山峦倒映在晶莹剔透的湖面上,水彩画风格..." required></textarea>
308
+ </div>
309
+
310
+ <div class="options-grid">
311
+ <div class="form-group">
312
+ <label for="size">图像尺寸</label>
313
+ <select id="size" name="size">
314
+ <option value="1024x1024">正方形 (1024x1024)</option>
315
+ <option value="1792x1024">横向 (1792x1024)</option>
316
+ <option value="1024x1792">纵向 (1024x1792)</option>
317
+ </select>
318
+ </div>
319
+
320
+ <div class="form-group">
321
+ <label for="quality">图像质量</label>
322
+ <select id="quality" name="quality">
323
+ <option value="standard">标准</option>
324
+ <option value="hd">高清 (费用更高)</option>
325
+ </select>
326
+ </div>
327
+
328
+ <div class="form-group">
329
+ <label for="style">风格</label>
330
+ <select id="style" name="style">
331
+ <option value="vivid">生动</option>
332
+ <option value="natural">自然</option>
333
+ </select>
334
+ </div>
335
+ </div>
336
+
337
+ <button type="submit" class="generate-btn">
338
+ <span class="btn-text">生成图像</span>
339
+ </button>
340
+ </form>
341
+ </div>
342
+
343
+ <!-- Edit Tab -->
344
+ <div id="edit-tab" class="tab-content hidden">
345
+ <form id="edit-form">
346
+ <div class="form-group">
347
+ <label>上传图像(仅支持1张)</label>
348
+ <div class="file-upload">
349
+ <input type="file" id="image" name="image" accept="image/*" required>
350
+ <label for="image" class="file-upload-label">
351
+ <div>📁 点击或拖拽图像到这里</div>
352
+ <small>支持 PNG 格式(正方形,最大 4MB)</small>
353
+ </label>
354
+ </div>
355
+ <div id="image-preview" class="image-preview"></div>
356
+ </div>
357
+
358
+ <div class="form-group">
359
+ <label>上传蒙版(可选)</label>
360
+ <div class="file-upload">
361
+ <input type="file" id="mask" name="mask" accept="image/png">
362
+ <label for="mask" class="file-upload-label">
363
+ <div>🎭 上传蒙版图像(可选)</div>
364
+ <small>PNG 格式,透明区域表示要编辑的部分</small>
365
+ </label>
366
+ </div>
367
+ <div id="mask-preview" class="image-preview"></div>
368
+ </div>
369
+
370
+ <div class="form-group">
371
+ <label for="edit-prompt">描述您想要的编辑效果</label>
372
+ <textarea id="edit-prompt" name="prompt" placeholder="在透明区域添加一只可爱的猫咪..." required></textarea>
373
+ </div>
374
+
375
+ <div class="form-group">
376
+ <label for="edit-size">输出尺寸</label>
377
+ <select id="edit-size" name="size">
378
+ <option value="256x256">小尺寸 (256x256)</option>
379
+ <option value="512x512">中尺寸 (512x512)</option>
380
+ <option value="1024x1024">大尺寸 (1024x1024)</option>
381
+ </select>
382
+ </div>
383
+
384
+ <button type="submit" class="generate-btn">
385
+ <span class="btn-text">编辑图像</span>
386
+ </button>
387
+ </form>
388
+ </div>
389
+
390
+ <div id="result-section" class="result-section hidden">
391
+ <h2>您生成的图像</h2>
392
+ <img id="result-image" class="result-image" alt="生成的图像">
393
+ <div id="usage-info" class="usage-info hidden"></div>
394
+ <button id="download-btn" class="download-btn" onclick="downloadImage()">下载图像</button>
395
+ </div>
396
+
397
+ <div id="message" class="hidden"></div>
398
+ </div>
399
+ </div>
400
+
401
+ <script>
402
+ let currentTab = 'generate';
403
+ let generatedImageData = null;
404
+ let selectedImage = null;
405
+ let selectedMask = null;
406
+
407
+ function switchTab(tab) {
408
+ currentTab = tab;
409
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
410
+ document.querySelectorAll('.tab-content').forEach(t => t.classList.add('hidden'));
411
+
412
+ event.target.classList.add('active');
413
+ document.getElementById(`${tab}-tab`).classList.remove('hidden');
414
+
415
+ // Hide result when switching tabs
416
+ document.getElementById('result-section').classList.add('hidden');
417
+ }
418
+
419
+ // Generate form submission
420
+ document.getElementById('generate-form').addEventListener('submit', async (e) => {
421
+ e.preventDefault();
422
+
423
+ const btn = e.target.querySelector('.generate-btn');
424
+ const btnText = btn.querySelector('.btn-text');
425
+
426
+ // Disable button and show loading
427
+ btn.disabled = true;
428
+ btnText.innerHTML = '<span class="loading"></span>生成中...';
429
+
430
+ // Hide previous results
431
+ showMessage('');
432
+ document.getElementById('result-section').classList.add('hidden');
433
+
434
+ const formData = {
435
+ prompt: document.getElementById('prompt').value,
436
+ size: document.getElementById('size').value,
437
+ quality: document.getElementById('quality').value,
438
+ style: document.getElementById('style').value
439
+ };
440
+
441
+ try {
442
+ const response = await fetch('/generate', {
443
+ method: 'POST',
444
+ headers: {
445
+ 'Content-Type': 'application/json'
446
+ },
447
+ body: JSON.stringify(formData)
448
+ });
449
+
450
+ const data = await response.json();
451
+
452
+ if (data.success) {
453
+ generatedImageData = data.image;
454
+ document.getElementById('result-image').src = data.image;
455
+ document.getElementById('result-section').classList.remove('hidden');
456
+
457
+ if (data.usage) {
458
+ const usageInfo = document.getElementById('usage-info');
459
+ usageInfo.innerHTML = `使用的令牌数: ${data.usage.total_tokens || '未知'}`;
460
+ usageInfo.classList.remove('hidden');
461
+ }
462
+
463
+ showMessage('图像生成成功!', 'success');
464
+ } else {
465
+ showMessage(data.error || '发生错误', 'error');
466
+ }
467
+ } catch (error) {
468
+ showMessage('网络错误: ' + error.message, 'error');
469
+ } finally {
470
+ btn.disabled = false;
471
+ btnText.innerHTML = '生成图像';
472
+ }
473
+ });
474
+
475
+ // Edit form submission
476
+ document.getElementById('edit-form').addEventListener('submit', async (e) => {
477
+ e.preventDefault();
478
+
479
+ const btn = e.target.querySelector('.generate-btn');
480
+ const btnText = btn.querySelector('.btn-text');
481
+
482
+ if (!selectedImage) {
483
+ showMessage('请选择一张图像', 'error');
484
+ return;
485
+ }
486
+
487
+ // Disable button and show loading
488
+ btn.disabled = true;
489
+ btnText.innerHTML = '<span class="loading"></span>处理中...';
490
+
491
+ // Hide previous results
492
+ showMessage('');
493
+ document.getElementById('result-section').classList.add('hidden');
494
+
495
+ const formData = new FormData();
496
+ formData.append('image', selectedImage);
497
+ if (selectedMask) {
498
+ formData.append('mask', selectedMask);
499
+ }
500
+ formData.append('prompt', document.getElementById('edit-prompt').value);
501
+ formData.append('size', document.getElementById('edit-size').value);
502
+
503
+ try {
504
+ const response = await fetch('/edit', {
505
+ method: 'POST',
506
+ body: formData
507
+ });
508
+
509
+ const data = await response.json();
510
+
511
+ if (data.success) {
512
+ generatedImageData = data.image;
513
+ document.getElementById('result-image').src = data.image;
514
+ document.getElementById('result-section').classList.remove('hidden');
515
+
516
+ if (data.usage) {
517
+ const usageInfo = document.getElementById('usage-info');
518
+ usageInfo.innerHTML = `使用的令牌数: ${data.usage.total_tokens || '未知'}`;
519
+ usageInfo.classList.remove('hidden');
520
+ }
521
+
522
+ showMessage('图像编辑成功!', 'success');
523
+ } else {
524
+ showMessage(data.error || '发生错误', 'error');
525
+ }
526
+ } catch (error) {
527
+ showMessage('网络错误: ' + error.message, 'error');
528
+ } finally {
529
+ btn.disabled = false;
530
+ btnText.innerHTML = '编辑图像';
531
+ }
532
+ });
533
+
534
+ // Image upload handling
535
+ document.getElementById('image').addEventListener('change', (e) => {
536
+ const file = e.target.files[0];
537
+ if (file) {
538
+ selectedImage = file;
539
+ const preview = document.getElementById('image-preview');
540
+ preview.innerHTML = '';
541
+
542
+ const reader = new FileReader();
543
+ reader.onload = (e) => {
544
+ const div = document.createElement('div');
545
+ div.className = 'preview-item';
546
+ div.innerHTML = `
547
+ <img src="${e.target.result}" alt="Preview">
548
+ <button class="remove" onclick="removeImage()">×</button>
549
+ `;
550
+ preview.appendChild(div);
551
+ };
552
+ reader.readAsDataURL(file);
553
+ }
554
+ });
555
+
556
+ // Mask upload handling
557
+ document.getElementById('mask').addEventListener('change', (e) => {
558
+ const file = e.target.files[0];
559
+ if (file) {
560
+ selectedMask = file;
561
+ const preview = document.getElementById('mask-preview');
562
+ preview.innerHTML = '';
563
+
564
+ const reader = new FileReader();
565
+ reader.onload = (e) => {
566
+ const div = document.createElement('div');
567
+ div.className = 'preview-item';
568
+ div.innerHTML = `
569
+ <img src="${e.target.result}" alt="Mask Preview">
570
+ <button class="remove" onclick="removeMask()">×</button>
571
+ `;
572
+ preview.appendChild(div);
573
+ };
574
+ reader.readAsDataURL(file);
575
+ }
576
+ });
577
+
578
+ function removeImage() {
579
+ selectedImage = null;
580
+ document.getElementById('image').value = '';
581
+ document.getElementById('image-preview').innerHTML = '';
582
+ }
583
+
584
+ function removeMask() {
585
+ selectedMask = null;
586
+ document.getElementById('mask').value = '';
587
+ document.getElementById('mask-preview').innerHTML = '';
588
+ }
589
+
590
+ function showMessage(message, type = '') {
591
+ const messageEl = document.getElementById('message');
592
+ if (!message) {
593
+ messageEl.classList.add('hidden');
594
+ return;
595
+ }
596
+
597
+ messageEl.textContent = message;
598
+ messageEl.className = type === 'error' ? 'error-message' :
599
+ type === 'success' ? 'success-message' : '';
600
+ }
601
+
602
+ function downloadImage() {
603
+ if (!generatedImageData) return;
604
+
605
+ const link = document.createElement('a');
606
+ link.href = generatedImageData;
607
+ link.download = `ai-generated-${Date.now()}.png`;
608
+ document.body.appendChild(link);
609
+ link.click();
610
+ document.body.removeChild(link);
611
+ }
612
+ </script>
613
+ </body>
614
  </html>