ai-chatbot/lib/agents/generation-project-state.ts
Your Name 83102c559f
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: support deleting agent result cards
2026-03-24 17:48:02 +08:00

288 lines
10 KiB
TypeScript

import {
createEmptyAgentModeWorkspaceState,
normalizeAgentModeWorkspaceState,
} from "@/lib/agents/mode-workspace-state";
import {
createEmptyAgentWorkflowMaterialSelections,
normalizeAgentWorkflowMaterialSelections,
} from "@/lib/agents/workflow-material-config";
import type {
AgentGenerationProjectPersistedState,
AgentGenerationProjectResultCollections,
AgentGenerationResultModeType,
AgentGenerationProjectWorkspaceState,
AgentWorkflowPersistedState,
} from "@/types/agent";
export function createEmptyAgentGenerationProjectWorkspaceState(): AgentGenerationProjectWorkspaceState {
return {
input: "",
attachments: [],
selectedModelId: "auto",
imageParams: {
aspectRatio: "auto",
imageSize: "2K",
imageCount: 1,
},
};
}
export function createEmptyAgentWorkflowPersistedState(): AgentWorkflowPersistedState {
return {
inputs: {
primarySku: "",
secondarySku: "",
brand: "",
store: "",
priceBand: "",
},
materials: createEmptyAgentWorkflowMaterialSelections(),
selections: {},
selectedBrandModel: null,
selectedAgentPresetId: null,
currentStage: "workflow",
agentModeState: createEmptyAgentModeWorkspaceState(),
};
}
export function createEmptyAgentGenerationProjectPersistedState(): AgentGenerationProjectPersistedState {
return {
workflowState: createEmptyAgentWorkflowPersistedState(),
workspaceState: createEmptyAgentGenerationProjectWorkspaceState(),
resultImageUrls: [],
resultCollections: {},
deletedResultCollections: {},
};
}
function normalizeResultCollections(value: unknown): AgentGenerationProjectResultCollections {
const raw =
value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
: {};
const normalizeUrlList = (input: unknown) =>
Array.isArray(input)
? Array.from(
new Set(
input
.map((item) => String(item || "").trim())
.filter((item) => /^https?:\/\//i.test(item))
)
).slice(0, 128)
: [];
return {
scene_generation: normalizeUrlList(raw.scene_generation),
detail_replacement: normalizeUrlList(raw.detail_replacement),
sku_replacement: normalizeUrlList(raw.sku_replacement),
};
}
function inferLegacyResultModeType(
workflowState: AgentWorkflowPersistedState,
resultUrls: string[]
): AgentGenerationResultModeType {
const detailReferenceCount = workflowState.agentModeState?.detailReplacement?.references?.length || 0;
const skuReferenceCount = workflowState.agentModeState?.skuReplacement?.references?.length || 0;
const resultCount = resultUrls.length;
if (detailReferenceCount > 0 && skuReferenceCount === 0) {
return "detail_replacement";
}
if (skuReferenceCount > 0 && detailReferenceCount === 0) {
return "sku_replacement";
}
if (detailReferenceCount > 0 || skuReferenceCount > 0) {
const detailGap = Math.abs(resultCount - detailReferenceCount);
const skuGap = Math.abs(resultCount - skuReferenceCount);
return detailGap <= skuGap ? "detail_replacement" : "sku_replacement";
}
return "scene_generation";
}
function normalizeMigratedResultCollections(params: {
workflowState: AgentWorkflowPersistedState;
resultImageUrls: string[];
resultCollections: AgentGenerationProjectResultCollections;
}) {
const normalizedSceneUrls = params.resultCollections.scene_generation || [];
const normalizedDetailUrls = params.resultCollections.detail_replacement || [];
const normalizedSkuUrls = params.resultCollections.sku_replacement || [];
const hasExplicitBuckets =
normalizedSceneUrls.length > 0 ||
normalizedDetailUrls.length > 0 ||
normalizedSkuUrls.length > 0;
if (!hasExplicitBuckets && params.resultImageUrls.length > 0) {
const inferredModeType = inferLegacyResultModeType(
params.workflowState,
params.resultImageUrls
);
return {
scene_generation:
inferredModeType === "scene_generation" ? params.resultImageUrls : [],
detail_replacement:
inferredModeType === "detail_replacement" ? params.resultImageUrls : [],
sku_replacement:
inferredModeType === "sku_replacement" ? params.resultImageUrls : [],
} satisfies AgentGenerationProjectResultCollections;
}
const detailReferenceCount =
params.workflowState.agentModeState?.detailReplacement?.references?.length || 0;
const skuReferenceCount =
params.workflowState.agentModeState?.skuReplacement?.references?.length || 0;
if (
normalizedSceneUrls.length > 0 &&
normalizedDetailUrls.length === 0 &&
normalizedSkuUrls.length === 0
) {
if (
detailReferenceCount > 0 &&
normalizedSceneUrls.length >= Math.max(4, Math.floor(detailReferenceCount * 0.6))
) {
return {
scene_generation: [],
detail_replacement: normalizedSceneUrls,
sku_replacement: [],
} satisfies AgentGenerationProjectResultCollections;
}
if (
skuReferenceCount > 0 &&
normalizedSceneUrls.length >= Math.max(4, Math.floor(skuReferenceCount * 0.6))
) {
return {
scene_generation: [],
detail_replacement: [],
sku_replacement: normalizedSceneUrls,
} satisfies AgentGenerationProjectResultCollections;
}
}
return params.resultCollections;
}
function normalizeWorkspaceState(value: unknown): AgentGenerationProjectWorkspaceState {
const raw =
value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
: {};
const attachments = Array.isArray(raw.attachments)
? raw.attachments
.map((item) => {
if (!item || typeof item !== "object" || Array.isArray(item)) {
return null;
}
const record = item as Record<string, unknown>;
const url = String(record.url || "").trim();
if (!url) return null;
return {
url,
name: String(record.name || "").trim() || null,
contentType: String(record.contentType || "").trim() || null,
assetId: String(record.assetId || "").trim() || null,
};
})
.slice(0, 16)
: [];
const rawImageParams =
raw.imageParams && typeof raw.imageParams === "object" && !Array.isArray(raw.imageParams)
? (raw.imageParams as Record<string, unknown>)
: {};
const imageCount = Number(rawImageParams.imageCount);
return {
input: String(raw.input || "").trim(),
attachments,
selectedModelId: String(raw.selectedModelId || "").trim() || "auto",
imageParams: {
aspectRatio: String(rawImageParams.aspectRatio || "").trim() || "auto",
imageSize: String(rawImageParams.imageSize || "").trim() || "2K",
imageCount: Number.isFinite(imageCount) ? Math.max(1, Math.min(8, Math.round(imageCount))) : 1,
},
};
}
function normalizeWorkflowState(value: unknown): AgentWorkflowPersistedState {
const raw =
value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
: {};
return {
inputs: {
primarySku: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record<string, unknown>).primarySku || "" : "").trim(),
secondarySku: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record<string, unknown>).secondarySku || "" : "").trim(),
brand: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record<string, unknown>).brand || "" : "").trim(),
store: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record<string, unknown>).store || "" : "").trim(),
priceBand: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record<string, unknown>).priceBand || "" : "").trim(),
},
materials: normalizeAgentWorkflowMaterialSelections(raw.materials),
selections:
raw.selections && typeof raw.selections === "object" && !Array.isArray(raw.selections)
? (raw.selections as Record<string, string>)
: {},
selectedBrandModel:
raw.selectedBrandModel && typeof raw.selectedBrandModel === "object" && !Array.isArray(raw.selectedBrandModel)
? {
...(raw.selectedBrandModel as Record<string, unknown>),
brandName: String((raw.selectedBrandModel as Record<string, unknown>).brandName || "").trim(),
id: String((raw.selectedBrandModel as Record<string, unknown>).id || "").trim(),
imageUrl: String((raw.selectedBrandModel as Record<string, unknown>).imageUrl || "").trim(),
assetId: String((raw.selectedBrandModel as Record<string, unknown>).assetId || "").trim() || null,
displayName: String((raw.selectedBrandModel as Record<string, unknown>).displayName || "").trim() || null,
mimeType: String((raw.selectedBrandModel as Record<string, unknown>).mimeType || "").trim() || null,
sortOrder: Number.isFinite(Number((raw.selectedBrandModel as Record<string, unknown>).sortOrder))
? Math.max(0, Math.min(999, Math.round(Number((raw.selectedBrandModel as Record<string, unknown>).sortOrder))))
: 0,
}
: null,
selectedAgentPresetId: String(raw.selectedAgentPresetId || "").trim() || null,
currentStage: raw.currentStage === "mode" ? "mode" : "workflow",
agentModeState: normalizeAgentModeWorkspaceState(raw.agentModeState),
};
}
export function normalizeAgentGenerationProjectPersistedState(
value: unknown
): AgentGenerationProjectPersistedState {
const raw =
value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
: {};
const resultImageUrls = Array.isArray(raw.resultImageUrls)
? Array.from(
new Set(
raw.resultImageUrls
.map((item) => String(item || "").trim())
.filter((item) => /^https?:\/\//i.test(item))
)
).slice(0, 64)
: [];
const workflowState = normalizeWorkflowState(raw.workflowState);
const resultCollections = normalizeMigratedResultCollections({
workflowState,
resultImageUrls,
resultCollections: normalizeResultCollections(raw.resultCollections),
});
const deletedResultCollections = normalizeResultCollections(raw.deletedResultCollections);
return {
workflowState,
workspaceState: normalizeWorkspaceState(raw.workspaceState),
resultImageUrls,
resultCollections,
deletedResultCollections,
};
}