fix(canvas): restore reliable text editing
This commit is contained in:
parent
f95c6b2b2b
commit
7752ec42ee
@ -29,8 +29,9 @@ export const runtime = "nodejs";
|
|||||||
export const maxDuration = 1200;
|
export const maxDuration = 1200;
|
||||||
|
|
||||||
const logger = createLogger("canvas-text-edit");
|
const logger = createLogger("canvas-text-edit");
|
||||||
const TEXT_EDIT_SOURCE_ARCHIVE_TIMEOUT_MS = 20_000;
|
const TEXT_EDIT_SOURCE_ARCHIVE_TIMEOUT_MS = 120_000;
|
||||||
const TEXT_EDIT_MODEL_FALLBACK_LIMIT = 3;
|
const TEXT_EDIT_MODEL_FALLBACK_LIMIT = 3;
|
||||||
|
const TEXT_EDIT_GENERATION_TIMEOUT_MS = 180_000;
|
||||||
|
|
||||||
const textItemSchema = z.object({
|
const textItemSchema = z.object({
|
||||||
id: z.string().min(1),
|
id: z.string().min(1),
|
||||||
@ -68,6 +69,13 @@ const SUPPORTED_TEXT_EDIT_PROVIDER_TYPES = new Set([
|
|||||||
|
|
||||||
type TextEditModelCandidate = Awaited<ReturnType<typeof getEnabledAiModelsWithProvider>>[number];
|
type TextEditModelCandidate = Awaited<ReturnType<typeof getEnabledAiModelsWithProvider>>[number];
|
||||||
|
|
||||||
|
function isExplicitTextEditModel(candidate: TextEditModelCandidate) {
|
||||||
|
const capabilities = candidate.model.capabilities as Record<string, unknown> | null;
|
||||||
|
const kind = String(capabilities?.kind || "").toLowerCase();
|
||||||
|
const modelId = String(candidate.model.modelId || "").toLowerCase();
|
||||||
|
return kind === "image-edit" || /(?:^|[/_-])edit(?:$|[/_-])/.test(modelId);
|
||||||
|
}
|
||||||
|
|
||||||
function getTextEditModelCandidates(
|
function getTextEditModelCandidates(
|
||||||
models: Awaited<ReturnType<typeof getEnabledAiModelsWithProvider>>
|
models: Awaited<ReturnType<typeof getEnabledAiModelsWithProvider>>
|
||||||
) {
|
) {
|
||||||
@ -87,11 +95,13 @@ function getTextEditModelCandidates(
|
|||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
const aScore =
|
const aScore =
|
||||||
(a.model.isDefaultTextEditModel ? 100 : 0) +
|
(a.model.isDefaultTextEditModel ? 100 : 0) +
|
||||||
|
(isExplicitTextEditModel(a) ? 30 : 0) +
|
||||||
(a.model.isDefault ? 10 : 0) +
|
(a.model.isDefault ? 10 : 0) +
|
||||||
(a.model.modelId?.toLowerCase().includes("seedream") ? 5 : 0) +
|
(a.model.modelId?.toLowerCase().includes("seedream") ? 5 : 0) +
|
||||||
(a.model.displayName?.includes("火山") ? 3 : 0);
|
(a.model.displayName?.includes("火山") ? 3 : 0);
|
||||||
const bScore =
|
const bScore =
|
||||||
(b.model.isDefaultTextEditModel ? 100 : 0) +
|
(b.model.isDefaultTextEditModel ? 100 : 0) +
|
||||||
|
(isExplicitTextEditModel(b) ? 30 : 0) +
|
||||||
(b.model.isDefault ? 10 : 0) +
|
(b.model.isDefault ? 10 : 0) +
|
||||||
(b.model.modelId?.toLowerCase().includes("seedream") ? 5 : 0) +
|
(b.model.modelId?.toLowerCase().includes("seedream") ? 5 : 0) +
|
||||||
(b.model.displayName?.includes("火山") ? 3 : 0);
|
(b.model.displayName?.includes("火山") ? 3 : 0);
|
||||||
@ -220,6 +230,13 @@ async function resolveTextEditSourceImageUrl(imageUrl: string) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn("source_image_archive_failed", {
|
logger.warn("source_image_archive_failed", {
|
||||||
error: error instanceof Error ? error.message : String(error),
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
sourceHost: (() => {
|
||||||
|
try {
|
||||||
|
return new URL(imageUrl).host;
|
||||||
|
} catch {
|
||||||
|
return "invalid-url";
|
||||||
|
}
|
||||||
|
})(),
|
||||||
});
|
});
|
||||||
return imageUrl;
|
return imageUrl;
|
||||||
}
|
}
|
||||||
@ -495,6 +512,7 @@ async function generateTextEditImageWithFailover(params: {
|
|||||||
],
|
],
|
||||||
imageParams: {
|
imageParams: {
|
||||||
imageCount: 1,
|
imageCount: 1,
|
||||||
|
timeoutMs: TEXT_EDIT_GENERATION_TIMEOUT_MS,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (selected.provider.type === "atlascloud" || selected.provider.type === "vip98k") {
|
if (selected.provider.type === "atlascloud" || selected.provider.type === "vip98k") {
|
||||||
|
|||||||
@ -56,6 +56,7 @@ const DEFAULT_SAFETY_TOLERANCE = "4";
|
|||||||
const DEFAULT_TIMEOUT_MS = 600000;
|
const DEFAULT_TIMEOUT_MS = 600000;
|
||||||
const MIN_TIMEOUT_MS = 30_000;
|
const MIN_TIMEOUT_MS = 30_000;
|
||||||
const MAX_TIMEOUT_MS = 600_000;
|
const MAX_TIMEOUT_MS = 600_000;
|
||||||
|
const FAL_INPUT_IMAGE_DOWNLOAD_TIMEOUT_MS = 120_000;
|
||||||
const FAL_GENERIC_MAX_EDGE = 4096;
|
const FAL_GENERIC_MAX_EDGE = 4096;
|
||||||
const FAL_GENERIC_MAX_PIXELS = 4096 * 4096;
|
const FAL_GENERIC_MAX_PIXELS = 4096 * 4096;
|
||||||
const GPT_IMAGE_2_MAX_EDGE = 3840;
|
const GPT_IMAGE_2_MAX_EDGE = 3840;
|
||||||
@ -679,7 +680,9 @@ async function uploadImageUrlToFalStorage(params: {
|
|||||||
const { fal, url } = params;
|
const { fal, url } = params;
|
||||||
if (/fal\.(?:media|run)\//i.test(url) || /fal-ai/i.test(url)) return url;
|
if (/fal\.(?:media|run)\//i.test(url) || /fal-ai/i.test(url)) return url;
|
||||||
|
|
||||||
const response = await fetch(url, { signal: AbortSignal.timeout(60_000) });
|
const response = await fetch(url, {
|
||||||
|
signal: AbortSignal.timeout(FAL_INPUT_IMAGE_DOWNLOAD_TIMEOUT_MS),
|
||||||
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`FAL input image download failed before storage upload: ${response.status}`);
|
throw new Error(`FAL input image download failed before storage upload: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user