486CHD commited on
Commit
d962083
·
verified ·
1 Parent(s): 935a74e

Upload 13 files

Browse files
Files changed (3) hide show
  1. index.html +36 -27
  2. server.js +34 -8
  3. style.css +16 -5
index.html CHANGED
@@ -495,12 +495,18 @@
495
  );
496
  },
497
 
498
- observe(img, src) {
499
- if (!src) return;
500
- if (!this.observer) {
501
- img.src = src;
502
- return;
503
- }
 
 
 
 
 
 
504
  img.dataset.src = src;
505
  this.observer.observe(img);
506
  }
@@ -687,13 +693,13 @@
687
  }
688
 
689
  // Main image
690
- const img = document.createElement('img');
691
- img.loading = 'lazy';
692
- img.decoding = 'async';
693
- img.fetchPriority = 'low';
694
  const displaySrc = item.thumb || item.image || item.imageUrl;
695
  LazyImageLoader.observe(img, displaySrc);
696
- img.alt = `作品 ${item.id}`;
697
  img.onerror = () => {
698
  console.error('图片加载失败, ID:', item.id);
699
  img.src = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect fill="%23333" width="100" height="100"/><text fill="%23666" x="50%" y="50%" text-anchor="middle" dy=".3em">Error</text></svg>';
@@ -1092,14 +1098,14 @@
1092
  card.className = 'public-card';
1093
  card.dataset.id = item.id;
1094
 
1095
- const img = document.createElement('img');
1096
- img.loading = 'lazy';
1097
- img.decoding = 'async';
1098
- img.fetchPriority = 'low';
1099
  const imageSrc = item.image || item.imageUrl;
1100
  LazyImageLoader.observe(img, imageSrc);
1101
- img.alt = item.prompt || '创意作品';
1102
- card.appendChild(img);
1103
 
1104
  const info = document.createElement('div');
1105
  info.className = 'public-card-info';
@@ -1354,16 +1360,19 @@
1354
  const timeoutMs = Device.isMobile ? 300000 : 120000;
1355
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
1356
 
1357
- const response = await fetch('/api/generate', {
1358
- method: 'POST',
1359
- headers: { 'Content-Type': 'application/json' },
1360
- body: JSON.stringify({
1361
- prompt: prompt,
1362
- images: AppState.currentImages,
1363
- preferUrl: Device.isMobile
1364
- }),
1365
- signal: controller.signal
1366
- });
 
 
 
1367
  clearTimeout(timeoutId);
1368
 
1369
  const data = await response.json();
 
495
  );
496
  },
497
 
498
+ observe(img, src) {
499
+ if (!src) return;
500
+ if (Device.isMobile) {
501
+ img.loading = 'eager';
502
+ img.decoding = 'auto';
503
+ img.src = src;
504
+ return;
505
+ }
506
+ if (!this.observer) {
507
+ img.src = src;
508
+ return;
509
+ }
510
  img.dataset.src = src;
511
  this.observer.observe(img);
512
  }
 
693
  }
694
 
695
  // Main image
696
+ const img = document.createElement('img');
697
+ img.loading = Device.isMobile ? 'eager' : 'lazy';
698
+ img.decoding = Device.isMobile ? 'auto' : 'async';
699
+ img.fetchPriority = Device.isMobile ? 'auto' : 'low';
700
  const displaySrc = item.thumb || item.image || item.imageUrl;
701
  LazyImageLoader.observe(img, displaySrc);
702
+ img.alt = `作品 ${item.id}`;
703
  img.onerror = () => {
704
  console.error('图片加载失败, ID:', item.id);
705
  img.src = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect fill="%23333" width="100" height="100"/><text fill="%23666" x="50%" y="50%" text-anchor="middle" dy=".3em">Error</text></svg>';
 
1098
  card.className = 'public-card';
1099
  card.dataset.id = item.id;
1100
 
1101
+ const img = document.createElement('img');
1102
+ img.loading = Device.isMobile ? 'eager' : 'lazy';
1103
+ img.decoding = Device.isMobile ? 'auto' : 'async';
1104
+ img.fetchPriority = Device.isMobile ? 'auto' : 'low';
1105
  const imageSrc = item.image || item.imageUrl;
1106
  LazyImageLoader.observe(img, imageSrc);
1107
+ img.alt = item.prompt || '创意作品';
1108
+ card.appendChild(img);
1109
 
1110
  const info = document.createElement('div');
1111
  info.className = 'public-card-info';
 
1360
  const timeoutMs = Device.isMobile ? 300000 : 120000;
1361
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
1362
 
1363
+ const requestBody = {
1364
+ prompt: prompt,
1365
+ images: AppState.currentImages,
1366
+ preferUrl: Device.isMobile,
1367
+ imageSize: '4K'
1368
+ };
1369
+
1370
+ const response = await fetch('/api/generate', {
1371
+ method: 'POST',
1372
+ headers: { 'Content-Type': 'application/json' },
1373
+ body: JSON.stringify(requestBody),
1374
+ signal: controller.signal
1375
+ });
1376
  clearTimeout(timeoutId);
1377
 
1378
  const data = await response.json();
server.js CHANGED
@@ -168,12 +168,23 @@ const APIService = {
168
  * @param {string} prompt - User prompt
169
  * @param {Array<string>} images - Uploaded images
170
  */
171
- async generateImage(prompt, images = []) {
172
- const parts = MessageBuilder.buildParts(prompt, images);
173
-
174
- const payload = {
175
- contents: [{ parts }]
176
- };
 
 
 
 
 
 
 
 
 
 
 
177
 
178
  const apiUrl = CONFIG.apiUrl.includes('{model}')
179
  ? CONFIG.apiUrl.replace('{model}', CONFIG.modelName)
@@ -565,7 +576,7 @@ app.get('/api/check-auth', (req, res) => {
565
 
566
  // Generate image (multi-image)
567
  app.post('/api/generate', authMiddleware, async (req, res) => {
568
- const { prompt, images, preferUrl } = req.body;
569
  const requestStart = Date.now();
570
  StatsStore.activeRequests += 1;
571
  let recorded = false;
@@ -616,7 +627,22 @@ app.post('/api/generate', authMiddleware, async (req, res) => {
616
  console.log('[DEBUG] Model:', CONFIG.modelName);
617
  console.log(`[${new Date().toISOString()}] API key: ${CONFIG.apiKey.substring(0, 10)}...`);
618
 
619
- const apiResponse = await APIService.generateImage(trimmedPrompt, uploadedImages);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
620
  const imageData = APIService.extractImageFromResponse(apiResponse);
621
  const wantsUrl = Boolean(preferUrl);
622
  let imageUrl = null;
 
168
  * @param {string} prompt - User prompt
169
  * @param {Array<string>} images - Uploaded images
170
  */
171
+ async generateImage(prompt, images = [], options = {}) {
172
+ const parts = MessageBuilder.buildParts(prompt, images);
173
+
174
+ const payload = {
175
+ contents: [{ parts }]
176
+ };
177
+
178
+ const imageConfig = {};
179
+ if (options.imageSize) {
180
+ imageConfig.imageSize = options.imageSize;
181
+ }
182
+ if (options.aspectRatio) {
183
+ imageConfig.aspectRatio = options.aspectRatio;
184
+ }
185
+ if (Object.keys(imageConfig).length > 0) {
186
+ payload.generationConfig = { imageConfig };
187
+ }
188
 
189
  const apiUrl = CONFIG.apiUrl.includes('{model}')
190
  ? CONFIG.apiUrl.replace('{model}', CONFIG.modelName)
 
576
 
577
  // Generate image (multi-image)
578
  app.post('/api/generate', authMiddleware, async (req, res) => {
579
+ const { prompt, images, preferUrl, imageSize, aspectRatio } = req.body;
580
  const requestStart = Date.now();
581
  StatsStore.activeRequests += 1;
582
  let recorded = false;
 
627
  console.log('[DEBUG] Model:', CONFIG.modelName);
628
  console.log(`[${new Date().toISOString()}] API key: ${CONFIG.apiKey.substring(0, 10)}...`);
629
 
630
+ const allowImageConfig = /gemini-3/i.test(CONFIG.modelName);
631
+ const resolvedImageSize = typeof imageSize === 'string'
632
+ ? imageSize
633
+ : (preferUrl ? '1K' : null);
634
+ const resolvedAspectRatio = typeof aspectRatio === 'string' ? aspectRatio : null;
635
+ const apiOptions = {};
636
+ if (allowImageConfig) {
637
+ if (resolvedImageSize) {
638
+ apiOptions.imageSize = resolvedImageSize;
639
+ }
640
+ if (resolvedAspectRatio) {
641
+ apiOptions.aspectRatio = resolvedAspectRatio;
642
+ }
643
+ }
644
+
645
+ const apiResponse = await APIService.generateImage(trimmedPrompt, uploadedImages, apiOptions);
646
  const imageData = APIService.extractImageFromResponse(apiResponse);
647
  const wantsUrl = Boolean(preferUrl);
648
  let imageUrl = null;
style.css CHANGED
@@ -561,11 +561,22 @@ body.has-preview header {
561
  animation: spin 0.8s linear infinite;
562
  }
563
 
564
- @media (max-width: 768px) {
565
- .icon-btn.spinning {
566
- animation: spin 1s linear infinite;
567
- opacity: 0.8;
568
- }
 
 
 
 
 
 
 
 
 
 
 
569
  }
570
 
571
  .history-item {
 
561
  animation: spin 0.8s linear infinite;
562
  }
563
 
564
+ @media (max-width: 768px) {
565
+ .public-card,
566
+ .history-item {
567
+ content-visibility: visible;
568
+ contain-intrinsic-size: auto;
569
+ }
570
+
571
+ .public-card img,
572
+ .history-item img {
573
+ transform: none !important;
574
+ }
575
+
576
+ .icon-btn.spinning {
577
+ animation: spin 1s linear infinite;
578
+ opacity: 0.8;
579
+ }
580
  }
581
 
582
  .history-item {