Codex/generation task reliability #20

Merged
stringadmin merged 20 commits from codex/generation-task-reliability into master 2026-06-08 05:56:38 +00:00
10 changed files with 90 additions and 32 deletions
Showing only changes of commit a1fd24a5f6 - Show all commits
+7 -1
View File
@@ -67,7 +67,13 @@ function normalizeAssetStatus(value: unknown): WebAssetItem["status"] {
} }
function normalizeTags(value: unknown): string[] { function normalizeTags(value: unknown): string[] {
return Array.isArray(value) ? value.map((item) => toStringValue(item)).filter(Boolean) : []; if (!Array.isArray(value)) return [];
const tags: string[] = [];
for (const item of value) {
const tag = toStringValue(item);
if (tag) tags.push(tag);
}
return tags;
} }
function normalizeAsset(raw: unknown): ServerAssetItem { function normalizeAsset(raw: unknown): ServerAssetItem {
+7 -3
View File
@@ -62,9 +62,13 @@ function toStringValue(value: unknown, fallback = ""): string {
} }
function toStringArray(value: unknown): string[] { function toStringArray(value: unknown): string[] {
return Array.isArray(value) if (!Array.isArray(value)) return [];
? value.map((item) => toStringValue(item)).filter(Boolean) const result: string[] = [];
: []; for (const item of value) {
const text = toStringValue(item);
if (text) result.push(text);
}
return result;
} }
function toMetadata(value: unknown): Record<string, unknown> { function toMetadata(value: unknown): Record<string, unknown> {
+12 -7
View File
@@ -376,13 +376,18 @@ function createWorkflowFingerprint(workflow: WebCanvasWorkflow): string {
function toActivePackages(value: unknown): WebUserSession["user"]["activePackages"] { function toActivePackages(value: unknown): WebUserSession["user"]["activePackages"] {
if (!Array.isArray(value)) return undefined; if (!Array.isArray(value)) return undefined;
return value.filter(isRecord).map((entry) => ({ const packages: NonNullable<WebUserSession["user"]["activePackages"]> = [];
name: toStringValue(entry.name, "Preview package"), for (const entry of value) {
expiresAt: toStringValue(entry.expiresAt ?? entry.expires_at, ""), if (!isRecord(entry)) continue;
remainingImage: toNumber(entry.remainingImage ?? entry.remaining_image), packages.push({
remainingVideo: toNumber(entry.remainingVideo ?? entry.remaining_video), name: toStringValue(entry.name, "Preview package"),
remainingText: toNumber(entry.remainingText ?? entry.remaining_text), expiresAt: toStringValue(entry.expiresAt ?? entry.expires_at, ""),
})); remainingImage: toNumber(entry.remainingImage ?? entry.remaining_image),
remainingVideo: toNumber(entry.remainingVideo ?? entry.remaining_video),
remainingText: toNumber(entry.remainingText ?? entry.remaining_text),
});
}
return packages;
} }
function normalizeUser(raw: unknown): WebUserSession["user"] | null { function normalizeUser(raw: unknown): WebUserSession["user"] | null {
+7 -3
View File
@@ -49,9 +49,13 @@ function normalizeModelOption(raw: unknown): ModelCapabilityOption | null {
} }
function normalizeModelList(value: unknown): ModelCapabilityOption[] { function normalizeModelList(value: unknown): ModelCapabilityOption[] {
return Array.isArray(value) if (!Array.isArray(value)) return [];
? value.map(normalizeModelOption).filter((item): item is ModelCapabilityOption => Boolean(item)) const options: ModelCapabilityOption[] = [];
: []; for (const item of value) {
const option = normalizeModelOption(item);
if (option) options.push(option);
}
return options;
} }
function createFallbackCapabilities(): WebModelCapabilities { function createFallbackCapabilities(): WebModelCapabilities {
+17 -4
View File
@@ -71,10 +71,19 @@ function normalizeTask(raw: unknown): ServerProjectTask | null {
} }
function extractTasks(payload: unknown): ServerProjectTask[] { function extractTasks(payload: unknown): ServerProjectTask[] {
if (Array.isArray(payload)) return payload.map(normalizeTask).filter(Boolean) as ServerProjectTask[]; const normalizeTasks = (rows: unknown[]): ServerProjectTask[] => {
const tasks: ServerProjectTask[] = [];
for (const row of rows) {
const task = normalizeTask(row);
if (task) tasks.push(task);
}
return tasks;
};
if (Array.isArray(payload)) return normalizeTasks(payload);
if (!isRecord(payload)) return []; if (!isRecord(payload)) return [];
const rows = payload.tasks ?? payload.items; const rows = payload.tasks ?? payload.items;
return Array.isArray(rows) ? (rows.map(normalizeTask).filter(Boolean) as ServerProjectTask[]) : []; return Array.isArray(rows) ? normalizeTasks(rows) : [];
} }
function taskTitle(task: ServerProjectTask): string { function taskTitle(task: ServerProjectTask): string {
@@ -110,8 +119,12 @@ export const projectTaskClient = {
}, },
async listForProjects(projectIds: string[]): Promise<WebGenerationPreviewTask[]> { async listForProjects(projectIds: string[]): Promise<WebGenerationPreviewTask[]> {
const uniqueIds = Array.from(new Set(projectIds.map((id) => id.trim()).filter(Boolean))); const uniqueIds = new Set<string>();
const results = await Promise.all(uniqueIds.map((id) => listProjectTasks(id))); for (const projectId of projectIds) {
const id = projectId.trim();
if (id) uniqueIds.add(id);
}
const results = await Promise.all(Array.from(uniqueIds, (id) => listProjectTasks(id)));
return results.flat(); return results.flat();
}, },
+2 -1
View File
@@ -106,8 +106,9 @@ function AppShell({
const visibleNavItems = useMemo( const visibleNavItems = useMemo(
() => { () => {
const navItemByKey = new Map(navItems.map((item) => [item.key, item]));
return PRIMARY_NAV_ORDER return PRIMARY_NAV_ORDER
.map((key) => navItems.find((item) => item.key === key)) .map((key) => navItemByKey.get(key))
.filter((item): item is WebNavItem => Boolean(item)); .filter((item): item is WebNavItem => Boolean(item));
}, },
[navItems], [navItems],
+8 -3
View File
@@ -3529,9 +3529,14 @@ function CanvasPage({
return; return;
} }
const toDelete = selectedNode ? [selectedNode] : selectedNodes; const toDelete = selectedNode ? [selectedNode] : selectedNodes;
const textIds = new Set(toDelete.filter((n) => n.kind === "text").map((n) => n.id)); const textIds = new Set<string>();
const imageIds = new Set(toDelete.filter((n) => n.kind === "image").map((n) => n.id)); const imageIds = new Set<string>();
const videoIds = new Set(toDelete.filter((n) => n.kind === "video").map((n) => n.id)); const videoIds = new Set<string>();
for (const node of toDelete) {
if (node.kind === "text") textIds.add(node.id);
else if (node.kind === "image") imageIds.add(node.id);
else if (node.kind === "video") videoIds.add(node.id);
}
if (textIds.size) setTextNodes((nodes) => nodes.filter((n) => !textIds.has(n.id))); if (textIds.size) setTextNodes((nodes) => nodes.filter((n) => !textIds.has(n.id)));
if (imageIds.size) setImageNodes((nodes) => nodes.filter((n) => !imageIds.has(n.id))); if (imageIds.size) setImageNodes((nodes) => nodes.filter((n) => !imageIds.has(n.id)));
if (videoIds.size) setVideoNodes((nodes) => nodes.filter((n) => !videoIds.has(n.id))); if (videoIds.size) setVideoNodes((nodes) => nodes.filter((n) => !videoIds.has(n.id)));
+5 -1
View File
@@ -91,7 +91,11 @@ export function getCommunityCaseSurface(item: Pick<ServerCommunityCase, "metadat
); );
if (explicitSurface !== "unknown") return explicitSurface; if (explicitSurface !== "unknown") return explicitSurface;
const tags = item.tags.map((tag) => tag.trim()).filter(Boolean); const tags: string[] = [];
for (const rawTag of item.tags) {
const tag = rawTag.trim();
if (tag) tags.push(tag);
}
if (tags.some((tag) => tag.includes("生成页面社区") || tag === "Web生成")) return "generation"; if (tags.some((tag) => tag.includes("生成页面社区") || tag === "Web生成")) return "generation";
if (tags.some((tag) => tag.includes("画布页面社区") || tag.includes("工作流"))) return "canvas"; if (tags.some((tag) => tag.includes("画布页面社区") || tag.includes("工作流"))) return "canvas";
if (getWorkflowFromCase(item)) return "canvas"; if (getWorkflowFromCase(item)) return "canvas";
+18 -5
View File
@@ -576,6 +576,7 @@ const cloneSetCountOptions: Array<{
{ key: "white", title: "白底图", desc: "白底主图,多角度呈现商品细节" }, { key: "white", title: "白底图", desc: "白底主图,多角度呈现商品细节" },
{ key: "scene", title: "场景图", desc: "展示商品的生活使用场景和人物搭配" }, { key: "scene", title: "场景图", desc: "展示商品的生活使用场景和人物搭配" },
]; ];
const cloneSetCountKeys = cloneSetCountOptions.map((option) => option.key);
const defaultCloneSetCounts: Record<CloneSetCountKey, number> = { const defaultCloneSetCounts: Record<CloneSetCountKey, number> = {
selling: 3, selling: 3,
white: 1, white: 1,
@@ -1634,7 +1635,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const generatedUrls: string[] = []; const generatedUrls: string[] = [];
const stamp = Date.now(); const stamp = Date.now();
for (const countKey of cloneSetCountOptions.map((o) => o.key)) { for (const countKey of cloneSetCountKeys) {
const count = counts[countKey]; const count = counts[countKey];
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
if (imageAbortRef.current.current) break; if (imageAbortRef.current.current) break;
@@ -1910,7 +1911,13 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
platform, ratio, language, market, platform, ratio, language, market,
{ gender: modelGender, age: modelAge, ethnicity: modelEthnicity, body: modelBody, appearance, scenes: selectedScenes, smartScene }, { gender: modelGender, age: modelAge, ethnicity: modelEthnicity, body: modelBody, appearance, scenes: selectedScenes, smartScene },
(s) => setTryOnStatus(s as TryOnStatus), (s) => setTryOnStatus(s as TryOnStatus),
(res) => setTryOnResultImages(res.map((r) => r.src).filter(Boolean)), (res) => {
const urls: string[] = [];
for (const item of res) {
if (item.src) urls.push(item.src);
}
setTryOnResultImages(urls);
},
); );
lastFailedActionRef.current = () => handleTryOnGenerate(); lastFailedActionRef.current = () => handleTryOnGenerate();
}; };
@@ -2030,7 +2037,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
productImages.length === 0 ? "请先上传商品原图" : status === "generating" ? "生成中..." : `生成${selectedCloneOutput.label}`; productImages.length === 0 ? "请先上传商品原图" : status === "generating" ? "生成中..." : `生成${selectedCloneOutput.label}`;
const setPreviewCards: CloneResult[] = []; const setPreviewCards: CloneResult[] = [];
let setIndex = 0; let setIndex = 0;
for (const countKey of cloneSetCountOptions.map((o) => o.key)) { for (const countKey of cloneSetCountKeys) {
const count = cloneSetCounts[countKey]; const count = cloneSetCounts[countKey];
const info = setCountLabels[countKey]; const info = setCountLabels[countKey];
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
@@ -2045,7 +2052,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const clonePreviewCards: CloneResult[] = []; const clonePreviewCards: CloneResult[] = [];
let cloneIndex = 0; let cloneIndex = 0;
for (const countKey of cloneSetCountOptions.map((o) => o.key)) { for (const countKey of cloneSetCountKeys) {
const count = cloneSetCounts[countKey]; const count = cloneSetCounts[countKey];
const info = setCountLabels[countKey]; const info = setCountLabels[countKey];
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
@@ -2057,6 +2064,12 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
cloneIndex++; cloneIndex++;
} }
} }
const detailSourcePreviewImages = detailProductImages.length
? detailProductImages.reduce<string[]>((urls, item) => {
urls.push(item.src);
return urls;
}, [])
: detailProductSamples;
const cloneBasicSelects: Array<{ const cloneBasicSelects: Array<{
key: CloneBasicSelectKey; key: CloneBasicSelectKey;
label: string; label: string;
@@ -2555,7 +2568,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
<section className="product-detail-demo-board"> <section className="product-detail-demo-board">
<div className="product-detail-source-stack"> <div className="product-detail-source-stack">
{(detailProductImages.length ? detailProductImages.map((item) => item.src) : detailProductSamples).map((src, index) => ( {detailSourcePreviewImages.map((src, index) => (
<figure key={`${src}-${index}`}> <figure key={`${src}-${index}`}>
<img src={src} alt={`商品原图 ${index + 1}`} /> <img src={src} alt={`商品原图 ${index + 1}`} />
</figure> </figure>
+7 -4
View File
@@ -13,7 +13,7 @@ import {
VideoCameraOutlined, VideoCameraOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import "../../styles/pages/more.css"; import "../../styles/pages/more.css";
import type { WebImageWorkbenchTool, WebViewKey } from "../../types"; import type { WebImageWorkbenchTool, WebViewKey } from "../../types";
@@ -144,9 +144,12 @@ function MorePage({ onSelectView, onOpenImageTool }: MorePageProps) {
return t.category === filter; return t.category === filter;
}); });
const recentTools = recentIds const toolById = useMemo(() => new Map(tools.map((tool) => [tool.id, tool])), []);
.map((id) => tools.find((t) => t.id === id)) const recentTools = recentIds.reduce<MoreTool[]>((acc, id) => {
.filter((t): t is MoreTool => Boolean(t) && (t?.ready ?? false)); const tool = toolById.get(id);
if (tool?.ready) acc.push(tool);
return acc;
}, []);
const groupedTools = filteredTools.reduce<Record<ToolCategory, MoreTool[]>>((acc, t) => { const groupedTools = filteredTools.reduce<Record<ToolCategory, MoreTool[]>>((acc, t) => {
if (!acc[t.category]) acc[t.category] = []; if (!acc[t.category]) acc[t.category] = [];