Spaces:
Configuration error
Configuration error
Update index.html
Browse files- index.html +140 -36
index.html
CHANGED
|
@@ -1248,21 +1248,10 @@
|
|
| 1248 |
return promptParts.join("\n\n");
|
| 1249 |
}
|
| 1250 |
|
| 1251 |
-
function createRequestBody(finalPrompt) {
|
| 1252 |
const selectedModel = elements.model.value;
|
| 1253 |
-
const responseFormat = {
|
| 1254 |
-
type: "image",
|
| 1255 |
-
mime_type: "image/jpeg",
|
| 1256 |
-
aspect_ratio: elements.aspectRatio.value
|
| 1257 |
-
};
|
| 1258 |
-
|
| 1259 |
-
if (
|
| 1260 |
-
selectedModel !== "gemini-2.5-flash-image"
|
| 1261 |
-
) {
|
| 1262 |
-
responseFormat.image_size = elements.imageSize.value;
|
| 1263 |
-
}
|
| 1264 |
|
| 1265 |
-
|
| 1266 |
model: selectedModel,
|
| 1267 |
input: [
|
| 1268 |
{
|
|
@@ -1270,8 +1259,25 @@
|
|
| 1270 |
text: finalPrompt
|
| 1271 |
}
|
| 1272 |
],
|
| 1273 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1274 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1275 |
}
|
| 1276 |
|
| 1277 |
async function generateImages() {
|
|
@@ -1400,19 +1406,34 @@
|
|
| 1400 |
signal
|
| 1401 |
) {
|
| 1402 |
let retryAttempt = 0;
|
|
|
|
| 1403 |
|
| 1404 |
while (true) {
|
| 1405 |
-
|
| 1406 |
-
|
| 1407 |
-
|
| 1408 |
-
|
| 1409 |
-
|
| 1410 |
-
|
| 1411 |
-
|
| 1412 |
-
|
| 1413 |
-
|
| 1414 |
-
|
| 1415 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1416 |
|
| 1417 |
const responseText = await response.text();
|
| 1418 |
let responseData = {};
|
|
@@ -1431,10 +1452,27 @@
|
|
| 1431 |
return responseData;
|
| 1432 |
}
|
| 1433 |
|
|
|
|
| 1434 |
const apiMessage =
|
| 1435 |
-
|
| 1436 |
responseData?.rawResponse ??
|
| 1437 |
`HTTP ${response.status}`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1438 |
|
| 1439 |
if (
|
| 1440 |
[429, 500, 502, 503, 504].includes(response.status) &&
|
|
@@ -1458,33 +1496,89 @@
|
|
| 1458 |
throw new Error(
|
| 1459 |
createFriendlyApiError(
|
| 1460 |
response.status,
|
| 1461 |
-
apiMessage
|
|
|
|
|
|
|
|
|
|
| 1462 |
)
|
| 1463 |
);
|
| 1464 |
}
|
| 1465 |
}
|
| 1466 |
|
| 1467 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1468 |
const safeMessage = String(apiMessage)
|
| 1469 |
.replace(
|
| 1470 |
/AIza[0-9A-Za-z_-]+/g,
|
| 1471 |
"[API KEY HIDDEN]"
|
| 1472 |
)
|
| 1473 |
-
.slice(0,
|
| 1474 |
|
| 1475 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1476 |
return (
|
| 1477 |
-
"Gemini
|
| 1478 |
-
"
|
|
|
|
|
|
|
| 1479 |
formatApiDetail(safeMessage)
|
| 1480 |
);
|
| 1481 |
}
|
| 1482 |
|
| 1483 |
-
if (
|
|
|
|
|
|
|
|
|
|
| 1484 |
return (
|
| 1485 |
-
"
|
| 1486 |
-
"
|
| 1487 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1488 |
formatApiDetail(safeMessage)
|
| 1489 |
);
|
| 1490 |
}
|
|
@@ -1492,6 +1586,7 @@
|
|
| 1492 |
if (statusCode === 404) {
|
| 1493 |
return (
|
| 1494 |
"The selected image model is unavailable for this API key." +
|
|
|
|
| 1495 |
formatApiDetail(safeMessage)
|
| 1496 |
);
|
| 1497 |
}
|
|
@@ -1499,6 +1594,7 @@
|
|
| 1499 |
if (statusCode === 429) {
|
| 1500 |
return (
|
| 1501 |
"Gemini quota or rate limit reached. Wait and try again." +
|
|
|
|
| 1502 |
formatApiDetail(safeMessage)
|
| 1503 |
);
|
| 1504 |
}
|
|
@@ -1506,16 +1602,24 @@
|
|
| 1506 |
if (statusCode >= 500) {
|
| 1507 |
return (
|
| 1508 |
"Gemini encountered a temporary service error." +
|
|
|
|
| 1509 |
formatApiDetail(safeMessage)
|
| 1510 |
);
|
| 1511 |
}
|
| 1512 |
|
| 1513 |
return (
|
| 1514 |
`Gemini request failed with HTTP ${statusCode}.` +
|
|
|
|
| 1515 |
formatApiDetail(safeMessage)
|
| 1516 |
);
|
| 1517 |
}
|
| 1518 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1519 |
function formatApiDetail(message) {
|
| 1520 |
return message ? `\n\nAPI detail: ${message}` : "";
|
| 1521 |
}
|
|
|
|
| 1248 |
return promptParts.join("\n\n");
|
| 1249 |
}
|
| 1250 |
|
| 1251 |
+
function createRequestBody(finalPrompt, useMinimalRequest = false) {
|
| 1252 |
const selectedModel = elements.model.value;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1253 |
|
| 1254 |
+
const requestBody = {
|
| 1255 |
model: selectedModel,
|
| 1256 |
input: [
|
| 1257 |
{
|
|
|
|
| 1259 |
text: finalPrompt
|
| 1260 |
}
|
| 1261 |
],
|
| 1262 |
+
store: false
|
| 1263 |
+
};
|
| 1264 |
+
|
| 1265 |
+
if (useMinimalRequest) {
|
| 1266 |
+
return requestBody;
|
| 1267 |
+
}
|
| 1268 |
+
|
| 1269 |
+
const responseFormat = {
|
| 1270 |
+
type: "image",
|
| 1271 |
+
mime_type: "image/jpeg",
|
| 1272 |
+
aspect_ratio: elements.aspectRatio.value
|
| 1273 |
};
|
| 1274 |
+
|
| 1275 |
+
if (selectedModel !== "gemini-2.5-flash-image") {
|
| 1276 |
+
responseFormat.image_size = elements.imageSize.value;
|
| 1277 |
+
}
|
| 1278 |
+
|
| 1279 |
+
requestBody.response_format = responseFormat;
|
| 1280 |
+
return requestBody;
|
| 1281 |
}
|
| 1282 |
|
| 1283 |
async function generateImages() {
|
|
|
|
| 1406 |
signal
|
| 1407 |
) {
|
| 1408 |
let retryAttempt = 0;
|
| 1409 |
+
let useMinimalRequest = false;
|
| 1410 |
|
| 1411 |
while (true) {
|
| 1412 |
+
let response;
|
| 1413 |
+
|
| 1414 |
+
try {
|
| 1415 |
+
response = await fetch(API_ENDPOINT, {
|
| 1416 |
+
method: "POST",
|
| 1417 |
+
headers: {
|
| 1418 |
+
"Content-Type": "application/json",
|
| 1419 |
+
"x-goog-api-key": apiKey
|
| 1420 |
+
},
|
| 1421 |
+
body: JSON.stringify(
|
| 1422 |
+
createRequestBody(finalPrompt, useMinimalRequest)
|
| 1423 |
+
),
|
| 1424 |
+
signal
|
| 1425 |
+
});
|
| 1426 |
+
} catch (error) {
|
| 1427 |
+
if (error.name === "AbortError") {
|
| 1428 |
+
throw error;
|
| 1429 |
+
}
|
| 1430 |
+
|
| 1431 |
+
throw new Error(
|
| 1432 |
+
"The browser could not reach the Gemini API. Check your " +
|
| 1433 |
+
"internet connection, browser extensions, VPN, firewall, " +
|
| 1434 |
+
"and whether direct browser requests are permitted."
|
| 1435 |
+
);
|
| 1436 |
+
}
|
| 1437 |
|
| 1438 |
const responseText = await response.text();
|
| 1439 |
let responseData = {};
|
|
|
|
| 1452 |
return responseData;
|
| 1453 |
}
|
| 1454 |
|
| 1455 |
+
const apiError = responseData?.error ?? {};
|
| 1456 |
const apiMessage =
|
| 1457 |
+
apiError.message ??
|
| 1458 |
responseData?.rawResponse ??
|
| 1459 |
`HTTP ${response.status}`;
|
| 1460 |
+
const apiStatus = apiError.status ?? "";
|
| 1461 |
+
const apiCode = apiError.code ?? response.status;
|
| 1462 |
+
|
| 1463 |
+
if (
|
| 1464 |
+
response.status === 400 &&
|
| 1465 |
+
!useMinimalRequest &&
|
| 1466 |
+
!isAuthenticationError(apiMessage, apiStatus)
|
| 1467 |
+
) {
|
| 1468 |
+
useMinimalRequest = true;
|
| 1469 |
+
setProgress(
|
| 1470 |
+
null,
|
| 1471 |
+
"The configured request was rejected. Retrying once with " +
|
| 1472 |
+
"Google's minimal official image-generation request…"
|
| 1473 |
+
);
|
| 1474 |
+
continue;
|
| 1475 |
+
}
|
| 1476 |
|
| 1477 |
if (
|
| 1478 |
[429, 500, 502, 503, 504].includes(response.status) &&
|
|
|
|
| 1496 |
throw new Error(
|
| 1497 |
createFriendlyApiError(
|
| 1498 |
response.status,
|
| 1499 |
+
apiMessage,
|
| 1500 |
+
apiStatus,
|
| 1501 |
+
apiCode,
|
| 1502 |
+
useMinimalRequest
|
| 1503 |
)
|
| 1504 |
);
|
| 1505 |
}
|
| 1506 |
}
|
| 1507 |
|
| 1508 |
+
function isAuthenticationError(apiMessage, apiStatus) {
|
| 1509 |
+
const normalized = `${apiStatus} ${apiMessage}`.toLowerCase();
|
| 1510 |
+
|
| 1511 |
+
return [
|
| 1512 |
+
"api_key_invalid",
|
| 1513 |
+
"api key not valid",
|
| 1514 |
+
"invalid api key",
|
| 1515 |
+
"permission_denied",
|
| 1516 |
+
"unauthenticated",
|
| 1517 |
+
"authentication"
|
| 1518 |
+
].some((term) => normalized.includes(term));
|
| 1519 |
+
}
|
| 1520 |
+
|
| 1521 |
+
function createFriendlyApiError(
|
| 1522 |
+
statusCode,
|
| 1523 |
+
apiMessage,
|
| 1524 |
+
apiStatus = "",
|
| 1525 |
+
apiCode = statusCode,
|
| 1526 |
+
usedMinimalRequest = false
|
| 1527 |
+
) {
|
| 1528 |
const safeMessage = String(apiMessage)
|
| 1529 |
.replace(
|
| 1530 |
/AIza[0-9A-Za-z_-]+/g,
|
| 1531 |
"[API KEY HIDDEN]"
|
| 1532 |
)
|
| 1533 |
+
.slice(0, 900);
|
| 1534 |
|
| 1535 |
+
const normalized =
|
| 1536 |
+
`${apiStatus} ${safeMessage}`.toLowerCase();
|
| 1537 |
+
|
| 1538 |
+
const diagnostic = [
|
| 1539 |
+
apiStatus ? `Status: ${apiStatus}` : "",
|
| 1540 |
+
apiCode ? `Code: ${apiCode}` : "",
|
| 1541 |
+
usedMinimalRequest ? "Minimal request also failed" : ""
|
| 1542 |
+
]
|
| 1543 |
+
.filter(Boolean)
|
| 1544 |
+
.join(" · ");
|
| 1545 |
+
|
| 1546 |
+
if (
|
| 1547 |
+
statusCode === 401 ||
|
| 1548 |
+
normalized.includes("api_key_invalid") ||
|
| 1549 |
+
normalized.includes("api key not valid") ||
|
| 1550 |
+
normalized.includes("invalid api key") ||
|
| 1551 |
+
normalized.includes("unauthenticated")
|
| 1552 |
+
) {
|
| 1553 |
return (
|
| 1554 |
+
"The Gemini API key is invalid, incomplete, revoked, or was " +
|
| 1555 |
+
"copied incorrectly. Paste the complete key value from Google " +
|
| 1556 |
+
"AI Studio; do not enter the name GEMINI_API_KEY." +
|
| 1557 |
+
formatDiagnostic(diagnostic) +
|
| 1558 |
formatApiDetail(safeMessage)
|
| 1559 |
);
|
| 1560 |
}
|
| 1561 |
|
| 1562 |
+
if (
|
| 1563 |
+
statusCode === 403 ||
|
| 1564 |
+
normalized.includes("permission_denied")
|
| 1565 |
+
) {
|
| 1566 |
return (
|
| 1567 |
+
"The key was recognized, but this Google project or account " +
|
| 1568 |
+
"does not have access to the selected image model. Check API " +
|
| 1569 |
+
"restrictions, billing, region, project permissions and quota." +
|
| 1570 |
+
formatDiagnostic(diagnostic) +
|
| 1571 |
+
formatApiDetail(safeMessage)
|
| 1572 |
+
);
|
| 1573 |
+
}
|
| 1574 |
+
|
| 1575 |
+
if (statusCode === 400) {
|
| 1576 |
+
return (
|
| 1577 |
+
"Gemini rejected both the configured request and the minimal " +
|
| 1578 |
+
"official image-generation request. This usually means the API " +
|
| 1579 |
+
"key is invalid, the selected model is unavailable to the project, " +
|
| 1580 |
+
"or the request is blocked by account configuration." +
|
| 1581 |
+
formatDiagnostic(diagnostic) +
|
| 1582 |
formatApiDetail(safeMessage)
|
| 1583 |
);
|
| 1584 |
}
|
|
|
|
| 1586 |
if (statusCode === 404) {
|
| 1587 |
return (
|
| 1588 |
"The selected image model is unavailable for this API key." +
|
| 1589 |
+
formatDiagnostic(diagnostic) +
|
| 1590 |
formatApiDetail(safeMessage)
|
| 1591 |
);
|
| 1592 |
}
|
|
|
|
| 1594 |
if (statusCode === 429) {
|
| 1595 |
return (
|
| 1596 |
"Gemini quota or rate limit reached. Wait and try again." +
|
| 1597 |
+
formatDiagnostic(diagnostic) +
|
| 1598 |
formatApiDetail(safeMessage)
|
| 1599 |
);
|
| 1600 |
}
|
|
|
|
| 1602 |
if (statusCode >= 500) {
|
| 1603 |
return (
|
| 1604 |
"Gemini encountered a temporary service error." +
|
| 1605 |
+
formatDiagnostic(diagnostic) +
|
| 1606 |
formatApiDetail(safeMessage)
|
| 1607 |
);
|
| 1608 |
}
|
| 1609 |
|
| 1610 |
return (
|
| 1611 |
`Gemini request failed with HTTP ${statusCode}.` +
|
| 1612 |
+
formatDiagnostic(diagnostic) +
|
| 1613 |
formatApiDetail(safeMessage)
|
| 1614 |
);
|
| 1615 |
}
|
| 1616 |
|
| 1617 |
+
function formatDiagnostic(diagnostic) {
|
| 1618 |
+
return diagnostic ? `
|
| 1619 |
+
|
| 1620 |
+
Diagnostic: ${diagnostic}` : "";
|
| 1621 |
+
}
|
| 1622 |
+
|
| 1623 |
function formatApiDetail(message) {
|
| 1624 |
return message ? `\n\nAPI detail: ${message}` : "";
|
| 1625 |
}
|