Spaces:
Runtime error
Runtime error
Upload 13 files
Browse files- index.html +36 -27
- server.js +34 -8
- style.css +16 -5
index.html
CHANGED
|
@@ -495,12 +495,18 @@
|
|
| 495 |
);
|
| 496 |
},
|
| 497 |
|
| 498 |
-
observe(img, src) {
|
| 499 |
-
if (!src) return;
|
| 500 |
-
if (
|
| 501 |
-
img.
|
| 502 |
-
|
| 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
|
| 1358 |
-
|
| 1359 |
-
|
| 1360 |
-
|
| 1361 |
-
|
| 1362 |
-
|
| 1363 |
-
|
| 1364 |
-
|
| 1365 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
.
|
| 566 |
-
|
| 567 |
-
|
| 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 {
|