Логирование в stdout (видно в Portainer) + асинхронные задания edit/upscale с опросом статуса (фикс 504/499 при длинных запросах)
This commit is contained in:
149
public/app.js
149
public/app.js
@@ -342,8 +342,8 @@ async function runJob(kind) {
|
||||
setJobStatus(
|
||||
"loading",
|
||||
isEdit
|
||||
? "Codex редактирует фотографию… Обычно 1–3 минуты."
|
||||
: "Увеличиваем разрешение фотографии…"
|
||||
? "Codex редактирует фотографию… Обычно 1–3 минуты. Страницу можно закрыть — результат появится в истории."
|
||||
: "Увеличиваем разрешение фотографии… Страницу можно закрыть — результат появится в истории."
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -359,57 +359,118 @@ async function runJob(kind) {
|
||||
throw new Error(data.error || "Не удалось обработать фотографию.");
|
||||
}
|
||||
|
||||
const version = data.version;
|
||||
|
||||
if (version) {
|
||||
historyVersions.push(version);
|
||||
currentVersionId = version.id;
|
||||
updateDownloadCurrent(version);
|
||||
renderHistory();
|
||||
if (!data.jobId) {
|
||||
throw new Error("Сервер не вернул идентификатор задания.");
|
||||
}
|
||||
|
||||
const url = version?.url || data.result?.url;
|
||||
|
||||
resultImage.src = url;
|
||||
resultImage.hidden = false;
|
||||
resultPlaceholder.hidden = true;
|
||||
|
||||
resultLabel.textContent = isEdit ? "Редактирование · Codex" : "4× upscale";
|
||||
|
||||
downloadLink.href = url;
|
||||
downloadLink.download =
|
||||
data.result?.filename ||
|
||||
(isEdit ? "edited-photo.jpg" : "upscaled-photo.png");
|
||||
downloadLink.hidden = false;
|
||||
|
||||
const summary = isEdit && data.summary
|
||||
? String(data.summary).replace(/\s+/g, " ").trim().slice(0, 180)
|
||||
: "";
|
||||
|
||||
setJobStatus(
|
||||
"success",
|
||||
isEdit
|
||||
? summary
|
||||
? `Готово — фотография отредактирована. Codex: ${summary}`
|
||||
: "Готово — фотография отредактирована."
|
||||
: `Готово — разрешение увеличено с помощью ${data.job?.model || "upscale-модели"}.`
|
||||
);
|
||||
|
||||
if (window.matchMedia("(max-width: 1040px)").matches) {
|
||||
resultImage.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center"
|
||||
});
|
||||
}
|
||||
pollJob(data.jobId, isEdit);
|
||||
} catch (error) {
|
||||
setBusy(false);
|
||||
setJobStatus(
|
||||
"error",
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Не удалось обработать фотографию."
|
||||
);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
let jobPollFailures = 0;
|
||||
|
||||
async function pollJob(jobId, isEdit) {
|
||||
try {
|
||||
const response = await apiFetch(`/api/jobs/${jobId}/status`, {
|
||||
cache: "no-store"
|
||||
});
|
||||
const data = await readJsonResponse(response);
|
||||
|
||||
if (!response.ok || !data.ok) {
|
||||
throw new Error(data.error || "Не удалось получить статус задания.");
|
||||
}
|
||||
|
||||
jobPollFailures = 0;
|
||||
|
||||
if (data.status === "running") {
|
||||
setTimeout(() => pollJob(jobId, isEdit), 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.status === "done") {
|
||||
renderJobSuccess(data, isEdit);
|
||||
setBusy(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.status === "error") {
|
||||
throw new Error(data.error || "Задание завершилось с ошибкой.");
|
||||
}
|
||||
|
||||
throw new Error("Неизвестный статус задания.");
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === "Требуется авторизация.") {
|
||||
setBusy(false);
|
||||
return;
|
||||
}
|
||||
|
||||
jobPollFailures += 1;
|
||||
|
||||
if (jobPollFailures >= 5) {
|
||||
setBusy(false);
|
||||
setJobStatus(
|
||||
"error",
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Ошибка связи с сервером. Проверьте результат в истории."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => pollJob(jobId, isEdit), 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function renderJobSuccess(data, isEdit) {
|
||||
const version = data.version;
|
||||
|
||||
if (version) {
|
||||
historyVersions.push(version);
|
||||
currentVersionId = version.id;
|
||||
updateDownloadCurrent(version);
|
||||
renderHistory();
|
||||
}
|
||||
|
||||
const url = version?.url || data.result?.url;
|
||||
|
||||
resultImage.src = url;
|
||||
resultImage.hidden = false;
|
||||
resultPlaceholder.hidden = true;
|
||||
|
||||
resultLabel.textContent = isEdit ? "Редактирование · Codex" : "4× upscale";
|
||||
|
||||
downloadLink.href = url;
|
||||
downloadLink.download =
|
||||
data.result?.filename ||
|
||||
(isEdit ? "edited-photo.jpg" : "upscaled-photo.png");
|
||||
downloadLink.hidden = false;
|
||||
|
||||
const summary = isEdit && data.summary
|
||||
? String(data.summary).replace(/\s+/g, " ").trim().slice(0, 180)
|
||||
: "";
|
||||
|
||||
setJobStatus(
|
||||
"success",
|
||||
isEdit
|
||||
? summary
|
||||
? `Готово — фотография отредактирована. Codex: ${summary}`
|
||||
: "Готово — фотография отредактирована."
|
||||
: `Готово — разрешение увеличено с помощью ${data.job?.model || "upscale-модели"}.`
|
||||
);
|
||||
|
||||
if (window.matchMedia("(max-width: 1040px)").matches) {
|
||||
resultImage.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
rel="icon"
|
||||
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='16' fill='%23202321'/%3E%3Ctext x='32' y='45' font-size='38' font-weight='bold' text-anchor='middle' fill='%23ffffff' font-family='sans-serif'%3E%D0%9A%3C/text%3E%3C/svg%3E"
|
||||
>
|
||||
<link rel="stylesheet" href="/style.css?v=9">
|
||||
<link rel="stylesheet" href="/style.css?v=10">
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
@@ -261,6 +261,6 @@
|
||||
</div>
|
||||
</noscript>
|
||||
|
||||
<script src="/app.js?v=9" defer></script>
|
||||
<script src="/app.js?v=10" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user