test(agent): cover detail replacement recovery
This commit is contained in:
parent
d5737b70c0
commit
3c6b7b7778
@ -7,12 +7,10 @@ import {
|
||||
extractAgentProjectStateFromCanvasData,
|
||||
isAgentProjectRow,
|
||||
} from "@/lib/agents/agent-project-compat";
|
||||
import { normalizeAgentGenerationProjectPersistedState } from "@/lib/agents/generation-project-state";
|
||||
import type {
|
||||
AgentGenerationProjectPersistedState,
|
||||
AgentGenerationProjectReplacementResultState,
|
||||
AgentGenerationProjectResultCollections,
|
||||
} from "@/types/agent";
|
||||
import {
|
||||
getAllResultUrlsFromAgentProjectState,
|
||||
mergeAgentProjectStateForSave,
|
||||
} from "@/lib/agents/agent-project-save-merge";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = 0;
|
||||
@ -30,144 +28,6 @@ function mapAgentProjectRow(row: typeof canvasProject.$inferSelect) {
|
||||
};
|
||||
}
|
||||
|
||||
const RESULT_COLLECTION_KEYS = [
|
||||
"scene_generation",
|
||||
"detail_generation",
|
||||
"detail_replacement",
|
||||
"sku_replacement",
|
||||
] as const;
|
||||
|
||||
const REPLACEMENT_RESULT_KEYS = [
|
||||
"detail_replacement",
|
||||
"sku_replacement",
|
||||
] as const;
|
||||
|
||||
function normalizeProjectResultUrlList(urls: string[] | null | undefined) {
|
||||
return Array.from(
|
||||
new Set(
|
||||
(urls || [])
|
||||
.map((url) => String(url || "").trim())
|
||||
.filter((url) => /^https?:\/\//i.test(url))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function mergeResultCollections(
|
||||
existing: AgentGenerationProjectResultCollections | null | undefined,
|
||||
incoming: AgentGenerationProjectResultCollections | null | undefined
|
||||
): AgentGenerationProjectResultCollections {
|
||||
return RESULT_COLLECTION_KEYS.reduce<AgentGenerationProjectResultCollections>(
|
||||
(next, key) => {
|
||||
next[key] = normalizeProjectResultUrlList([
|
||||
...normalizeProjectResultUrlList(existing?.[key]),
|
||||
...normalizeProjectResultUrlList(incoming?.[key]),
|
||||
]);
|
||||
return next;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
function getResultCollectionUrlSet(
|
||||
collections: AgentGenerationProjectResultCollections | null | undefined
|
||||
) {
|
||||
return new Set(
|
||||
RESULT_COLLECTION_KEYS.flatMap((key) => normalizeProjectResultUrlList(collections?.[key]))
|
||||
);
|
||||
}
|
||||
|
||||
function removeDeletedUrlsFromCollections(
|
||||
collections: AgentGenerationProjectResultCollections,
|
||||
deletedUrls: Set<string>
|
||||
): AgentGenerationProjectResultCollections {
|
||||
return RESULT_COLLECTION_KEYS.reduce<AgentGenerationProjectResultCollections>(
|
||||
(next, key) => {
|
||||
next[key] = normalizeProjectResultUrlList(collections[key]).filter(
|
||||
(url) => !deletedUrls.has(url)
|
||||
);
|
||||
return next;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
function mergeReplacementResultState(
|
||||
existing: AgentGenerationProjectReplacementResultState | null | undefined,
|
||||
incoming: AgentGenerationProjectReplacementResultState | null | undefined,
|
||||
deletedUrls: Set<string>
|
||||
): AgentGenerationProjectReplacementResultState {
|
||||
return REPLACEMENT_RESULT_KEYS.reduce<AgentGenerationProjectReplacementResultState>(
|
||||
(next, key) => {
|
||||
const existingModeState = existing?.[key] || {};
|
||||
const incomingModeState = incoming?.[key] || {};
|
||||
const mergedModeState = { ...existingModeState, ...incomingModeState };
|
||||
next[key] = Object.fromEntries(
|
||||
Object.entries(mergedModeState).filter(([, record]) => {
|
||||
const url = String(record?.url || "").trim();
|
||||
return !url || !deletedUrls.has(url);
|
||||
})
|
||||
);
|
||||
return next;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
function getReplacementResultUrls(
|
||||
state: AgentGenerationProjectReplacementResultState | null | undefined
|
||||
) {
|
||||
return REPLACEMENT_RESULT_KEYS.flatMap((key) =>
|
||||
Object.values(state?.[key] || {})
|
||||
.map((record) => String(record?.url || "").trim())
|
||||
.filter((url) => /^https?:\/\//i.test(url))
|
||||
);
|
||||
}
|
||||
|
||||
function getAllResultUrlsFromState(state: AgentGenerationProjectPersistedState) {
|
||||
return normalizeProjectResultUrlList([
|
||||
...normalizeProjectResultUrlList(state.resultImageUrls),
|
||||
...RESULT_COLLECTION_KEYS.flatMap((key) =>
|
||||
normalizeProjectResultUrlList(state.resultCollections?.[key])
|
||||
),
|
||||
...getReplacementResultUrls(state.replacementResultState),
|
||||
]);
|
||||
}
|
||||
|
||||
function mergeAgentProjectStateForSave(params: {
|
||||
existingState: AgentGenerationProjectPersistedState;
|
||||
incomingState: unknown;
|
||||
}) {
|
||||
const existingState = normalizeAgentGenerationProjectPersistedState(params.existingState);
|
||||
const incomingState = normalizeAgentGenerationProjectPersistedState(params.incomingState);
|
||||
const deletedResultCollections = mergeResultCollections(
|
||||
existingState.deletedResultCollections,
|
||||
incomingState.deletedResultCollections
|
||||
);
|
||||
const deletedUrls = getResultCollectionUrlSet(deletedResultCollections);
|
||||
const replacementResultState = mergeReplacementResultState(
|
||||
existingState.replacementResultState,
|
||||
incomingState.replacementResultState,
|
||||
deletedUrls
|
||||
);
|
||||
const resultCollections = removeDeletedUrlsFromCollections(
|
||||
mergeResultCollections(existingState.resultCollections, incomingState.resultCollections),
|
||||
deletedUrls
|
||||
);
|
||||
|
||||
return normalizeAgentGenerationProjectPersistedState({
|
||||
...incomingState,
|
||||
resultCollections,
|
||||
deletedResultCollections,
|
||||
replacementResultState,
|
||||
resultImageUrls: normalizeProjectResultUrlList([
|
||||
...getAllResultUrlsFromState(existingState),
|
||||
...getAllResultUrlsFromState(incomingState),
|
||||
...RESULT_COLLECTION_KEYS.flatMap((key) => resultCollections[key] || []),
|
||||
...getReplacementResultUrls(replacementResultState),
|
||||
]).filter((url) => !deletedUrls.has(url)),
|
||||
});
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
@ -239,7 +99,7 @@ export async function PUT(
|
||||
incomingState: body.projectState,
|
||||
})
|
||||
: existingProjectState;
|
||||
const firstResultUrl = getAllResultUrlsFromState(nextProjectState)[0] || null;
|
||||
const firstResultUrl = getAllResultUrlsFromAgentProjectState(nextProjectState)[0] || null;
|
||||
const requestedThumbnail =
|
||||
body.thumbnail !== undefined ? String(body.thumbnail || "").trim() || null : undefined;
|
||||
const thumbnail =
|
||||
|
||||
289
lib/agents/agent-project-save-merge.ts
Normal file
289
lib/agents/agent-project-save-merge.ts
Normal file
@ -0,0 +1,289 @@
|
||||
import { normalizeAgentGenerationProjectPersistedState } from "@/lib/agents/generation-project-state";
|
||||
import type {
|
||||
AgentGenerationProjectPersistedState,
|
||||
AgentGenerationProjectReplacementResultState,
|
||||
AgentGenerationProjectResultCollections,
|
||||
AgentWorkflowBusinessModeKey,
|
||||
} from "@/types/agent";
|
||||
|
||||
const RESULT_COLLECTION_KEYS = [
|
||||
"scene_generation",
|
||||
"detail_generation",
|
||||
"detail_replacement",
|
||||
"sku_replacement",
|
||||
] as const;
|
||||
|
||||
const REPLACEMENT_RESULT_KEYS = [
|
||||
"detail_replacement",
|
||||
"sku_replacement",
|
||||
] as const;
|
||||
|
||||
function createFallbackWorkspaceState() {
|
||||
return {
|
||||
input: "",
|
||||
attachments: [],
|
||||
selectedModelId: "auto",
|
||||
activeBusinessModeKey: "main_sku" as const,
|
||||
imageParams: {
|
||||
aspectRatio: "auto",
|
||||
imageSize: "2K",
|
||||
imageCount: 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeProjectResultUrlList(urls: string[] | null | undefined) {
|
||||
return Array.from(
|
||||
new Set(
|
||||
(urls || [])
|
||||
.map((url) => String(url || "").trim())
|
||||
.filter((url) => /^https?:\/\//i.test(url))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function mergeResultCollections(
|
||||
existing: AgentGenerationProjectResultCollections | null | undefined,
|
||||
incoming: AgentGenerationProjectResultCollections | null | undefined
|
||||
): AgentGenerationProjectResultCollections {
|
||||
return RESULT_COLLECTION_KEYS.reduce<AgentGenerationProjectResultCollections>(
|
||||
(next, key) => {
|
||||
next[key] = normalizeProjectResultUrlList([
|
||||
...normalizeProjectResultUrlList(existing?.[key]),
|
||||
...normalizeProjectResultUrlList(incoming?.[key]),
|
||||
]);
|
||||
return next;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
function getResultCollectionUrlSet(
|
||||
collections: AgentGenerationProjectResultCollections | null | undefined
|
||||
) {
|
||||
return new Set(
|
||||
RESULT_COLLECTION_KEYS.flatMap((key) => normalizeProjectResultUrlList(collections?.[key]))
|
||||
);
|
||||
}
|
||||
|
||||
function removeDeletedUrlsFromCollections(
|
||||
collections: AgentGenerationProjectResultCollections,
|
||||
deletedUrls: Set<string>
|
||||
): AgentGenerationProjectResultCollections {
|
||||
return RESULT_COLLECTION_KEYS.reduce<AgentGenerationProjectResultCollections>(
|
||||
(next, key) => {
|
||||
next[key] = normalizeProjectResultUrlList(collections[key]).filter(
|
||||
(url) => !deletedUrls.has(url)
|
||||
);
|
||||
return next;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
function mergeReplacementResultState(
|
||||
existing: AgentGenerationProjectReplacementResultState | null | undefined,
|
||||
incoming: AgentGenerationProjectReplacementResultState | null | undefined,
|
||||
deletedUrls: Set<string>
|
||||
): AgentGenerationProjectReplacementResultState {
|
||||
return REPLACEMENT_RESULT_KEYS.reduce<AgentGenerationProjectReplacementResultState>(
|
||||
(next, key) => {
|
||||
const existingModeState = existing?.[key] || {};
|
||||
const incomingModeState = incoming?.[key] || {};
|
||||
const mergedModeState = { ...existingModeState, ...incomingModeState };
|
||||
next[key] = Object.fromEntries(
|
||||
Object.entries(mergedModeState).filter(([, record]) => {
|
||||
const url = String(record?.url || "").trim();
|
||||
return !url || !deletedUrls.has(url);
|
||||
})
|
||||
);
|
||||
return next;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
function getReplacementResultUrls(
|
||||
state: AgentGenerationProjectReplacementResultState | null | undefined
|
||||
) {
|
||||
return REPLACEMENT_RESULT_KEYS.flatMap((key) =>
|
||||
Object.values(state?.[key] || {})
|
||||
.map((record) => String(record?.url || "").trim())
|
||||
.filter((url) => /^https?:\/\//i.test(url))
|
||||
);
|
||||
}
|
||||
|
||||
export function getAllResultUrlsFromAgentProjectState(
|
||||
state: AgentGenerationProjectPersistedState
|
||||
) {
|
||||
return normalizeProjectResultUrlList([
|
||||
...normalizeProjectResultUrlList(state.resultImageUrls),
|
||||
...RESULT_COLLECTION_KEYS.flatMap((key) =>
|
||||
normalizeProjectResultUrlList(state.resultCollections?.[key])
|
||||
),
|
||||
...getReplacementResultUrls(state.replacementResultState),
|
||||
]);
|
||||
}
|
||||
|
||||
function hasDetailModeSignals(state: AgentGenerationProjectPersistedState) {
|
||||
const detailReferenceCount =
|
||||
state.workflowState.agentModeState?.detailReplacement?.references?.length || 0;
|
||||
const skuReferenceCount =
|
||||
state.workflowState.agentModeState?.skuReplacement?.references?.length || 0;
|
||||
return (
|
||||
detailReferenceCount > 0 ||
|
||||
skuReferenceCount > 0 ||
|
||||
normalizeProjectResultUrlList(state.resultCollections?.detail_generation).length > 0 ||
|
||||
normalizeProjectResultUrlList(state.resultCollections?.detail_replacement).length > 0 ||
|
||||
normalizeProjectResultUrlList(state.resultCollections?.sku_replacement).length > 0 ||
|
||||
getReplacementResultUrls(state.replacementResultState).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
function hasDetailReplacementResultSignals(params: {
|
||||
resultCollections: AgentGenerationProjectResultCollections;
|
||||
replacementResultState: AgentGenerationProjectReplacementResultState;
|
||||
}) {
|
||||
return (
|
||||
normalizeProjectResultUrlList(params.resultCollections.detail_replacement).length > 0 ||
|
||||
getReplacementResultUrls({
|
||||
detail_replacement: params.replacementResultState.detail_replacement,
|
||||
}).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
function hasSkuReplacementResultSignals(params: {
|
||||
resultCollections: AgentGenerationProjectResultCollections;
|
||||
replacementResultState: AgentGenerationProjectReplacementResultState;
|
||||
}) {
|
||||
return (
|
||||
normalizeProjectResultUrlList(params.resultCollections.sku_replacement).length > 0 ||
|
||||
getReplacementResultUrls({
|
||||
sku_replacement: params.replacementResultState.sku_replacement,
|
||||
}).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
function mergeWorkflowStateForSave(params: {
|
||||
existingState: AgentGenerationProjectPersistedState;
|
||||
incomingState: AgentGenerationProjectPersistedState;
|
||||
resultCollections: AgentGenerationProjectResultCollections;
|
||||
replacementResultState: AgentGenerationProjectReplacementResultState;
|
||||
}) {
|
||||
const incomingAgentModeState = params.incomingState.workflowState.agentModeState;
|
||||
const existingAgentModeState = params.existingState.workflowState.agentModeState;
|
||||
const shouldKeepDetailReplacementState =
|
||||
hasDetailReplacementResultSignals(params) &&
|
||||
(existingAgentModeState?.detailReplacement?.references?.length || 0) > 0 &&
|
||||
(incomingAgentModeState?.detailReplacement?.references?.length || 0) === 0;
|
||||
const shouldKeepSkuReplacementState =
|
||||
hasSkuReplacementResultSignals(params) &&
|
||||
(existingAgentModeState?.skuReplacement?.references?.length || 0) > 0 &&
|
||||
(incomingAgentModeState?.skuReplacement?.references?.length || 0) === 0;
|
||||
|
||||
if (!shouldKeepDetailReplacementState && !shouldKeepSkuReplacementState) {
|
||||
return params.incomingState.workflowState;
|
||||
}
|
||||
|
||||
return {
|
||||
...params.incomingState.workflowState,
|
||||
agentModeState: {
|
||||
...incomingAgentModeState,
|
||||
detailReplacement: shouldKeepDetailReplacementState
|
||||
? existingAgentModeState?.detailReplacement
|
||||
: incomingAgentModeState?.detailReplacement,
|
||||
skuReplacement: shouldKeepSkuReplacementState
|
||||
? existingAgentModeState?.skuReplacement
|
||||
: incomingAgentModeState?.skuReplacement,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function hasExplicitIncomingBusinessMode(value: unknown) {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return false;
|
||||
}
|
||||
const raw = value as Record<string, unknown>;
|
||||
const workspaceState =
|
||||
raw.workspaceState && typeof raw.workspaceState === "object" && !Array.isArray(raw.workspaceState)
|
||||
? (raw.workspaceState as Record<string, unknown>)
|
||||
: {};
|
||||
return Boolean(String(workspaceState.activeBusinessModeKey || "").trim());
|
||||
}
|
||||
|
||||
function resolveMergedBusinessModeKey(params: {
|
||||
existingState: AgentGenerationProjectPersistedState;
|
||||
incomingState: AgentGenerationProjectPersistedState;
|
||||
rawIncomingState: unknown;
|
||||
mergedState: AgentGenerationProjectPersistedState;
|
||||
}): AgentWorkflowBusinessModeKey {
|
||||
if (hasDetailModeSignals(params.mergedState)) {
|
||||
const incomingKey = params.incomingState.workspaceState?.activeBusinessModeKey;
|
||||
const existingKey = params.existingState.workspaceState?.activeBusinessModeKey;
|
||||
const incomingHasExplicitKey = hasExplicitIncomingBusinessMode(params.rawIncomingState);
|
||||
|
||||
if (!incomingHasExplicitKey || existingKey === "detail" || incomingKey === "main_sku") {
|
||||
return "detail";
|
||||
}
|
||||
}
|
||||
|
||||
return params.incomingState.workspaceState?.activeBusinessModeKey ||
|
||||
params.existingState.workspaceState?.activeBusinessModeKey ||
|
||||
"main_sku";
|
||||
}
|
||||
|
||||
export function mergeAgentProjectStateForSave(params: {
|
||||
existingState: AgentGenerationProjectPersistedState;
|
||||
incomingState: unknown;
|
||||
}): AgentGenerationProjectPersistedState {
|
||||
const existingState = normalizeAgentGenerationProjectPersistedState(params.existingState);
|
||||
const incomingState = normalizeAgentGenerationProjectPersistedState(params.incomingState);
|
||||
const deletedResultCollections = mergeResultCollections(
|
||||
existingState.deletedResultCollections,
|
||||
incomingState.deletedResultCollections
|
||||
);
|
||||
const deletedUrls = getResultCollectionUrlSet(deletedResultCollections);
|
||||
const replacementResultState = mergeReplacementResultState(
|
||||
existingState.replacementResultState,
|
||||
incomingState.replacementResultState,
|
||||
deletedUrls
|
||||
);
|
||||
const resultCollections = removeDeletedUrlsFromCollections(
|
||||
mergeResultCollections(existingState.resultCollections, incomingState.resultCollections),
|
||||
deletedUrls
|
||||
);
|
||||
const workflowState = mergeWorkflowStateForSave({
|
||||
existingState,
|
||||
incomingState,
|
||||
resultCollections,
|
||||
replacementResultState,
|
||||
});
|
||||
const mergedState = normalizeAgentGenerationProjectPersistedState({
|
||||
...incomingState,
|
||||
workflowState,
|
||||
resultCollections,
|
||||
deletedResultCollections,
|
||||
replacementResultState,
|
||||
resultImageUrls: normalizeProjectResultUrlList([
|
||||
...getAllResultUrlsFromAgentProjectState(existingState),
|
||||
...getAllResultUrlsFromAgentProjectState(incomingState),
|
||||
...RESULT_COLLECTION_KEYS.flatMap((key) => resultCollections[key] || []),
|
||||
...getReplacementResultUrls(replacementResultState),
|
||||
]).filter((url) => !deletedUrls.has(url)),
|
||||
});
|
||||
|
||||
const workspaceState = mergedState.workspaceState || createFallbackWorkspaceState();
|
||||
|
||||
return {
|
||||
...mergedState,
|
||||
workspaceState: {
|
||||
...workspaceState,
|
||||
activeBusinessModeKey: resolveMergedBusinessModeKey({
|
||||
existingState,
|
||||
incomingState,
|
||||
rawIncomingState: params.incomingState,
|
||||
mergedState,
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
292
scripts/test-agent-detail-replacement-recovery.ts
Normal file
292
scripts/test-agent-detail-replacement-recovery.ts
Normal file
@ -0,0 +1,292 @@
|
||||
import {
|
||||
getAllResultUrlsFromAgentProjectState,
|
||||
mergeAgentProjectStateForSave,
|
||||
} from "@/lib/agents/agent-project-save-merge";
|
||||
import { normalizeAgentGenerationProjectPersistedState } from "@/lib/agents/generation-project-state";
|
||||
import type { AgentGenerationProjectPersistedState } from "@/types/agent";
|
||||
|
||||
const DETAIL_RESULT_URL = "https://scqny.jiansun.vip/uploads/1785470582893-rmh0xr.png";
|
||||
const DETAIL_RESULT_URL_2 = "https://scqny.jiansun.vip/uploads/1785470577924-owj79k.png";
|
||||
const SKU_RESULT_URL = "https://scqny.jiansun.vip/uploads/1785470458027-c6yuar.png";
|
||||
const DETAIL_REFERENCE_URL = "https://example.com/detail-reference.png";
|
||||
|
||||
function createWorkflowState(params?: {
|
||||
detailReferences?: number;
|
||||
skuReferences?: number;
|
||||
}) {
|
||||
return {
|
||||
inputs: {
|
||||
primarySku: "",
|
||||
secondarySku: "",
|
||||
brand: "",
|
||||
store: "",
|
||||
thirdCategory: "",
|
||||
fourthCategory: "",
|
||||
fifthCategory: "",
|
||||
priceBand: "",
|
||||
},
|
||||
selections: {},
|
||||
currentStage: "mode",
|
||||
agentModeState: {
|
||||
detailReplacement: {
|
||||
query: "766308902517",
|
||||
references: Array.from({ length: params?.detailReferences || 0 }, (_, index) => ({
|
||||
id: `detail-card-${index + 1}`,
|
||||
label: `Detail ${index + 1}`,
|
||||
imageUrl: DETAIL_REFERENCE_URL,
|
||||
previewUrl: DETAIL_REFERENCE_URL,
|
||||
})),
|
||||
},
|
||||
skuReplacement: {
|
||||
query: "sku-query",
|
||||
references: Array.from({ length: params?.skuReferences || 0 }, (_, index) => ({
|
||||
id: `sku-card-${index + 1}`,
|
||||
label: `SKU ${index + 1}`,
|
||||
imageUrl: DETAIL_REFERENCE_URL,
|
||||
previewUrl: DETAIL_REFERENCE_URL,
|
||||
})),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createExistingDetailProjectState(): AgentGenerationProjectPersistedState {
|
||||
return normalizeAgentGenerationProjectPersistedState({
|
||||
workflowState: createWorkflowState({ detailReferences: 1 }),
|
||||
workspaceState: {
|
||||
input: "legacy detail replacement",
|
||||
selectedModelId: "auto",
|
||||
},
|
||||
resultCollections: {
|
||||
detail_replacement: [DETAIL_RESULT_URL],
|
||||
},
|
||||
replacementResultState: {
|
||||
detail_replacement: {
|
||||
"detail-card-1": {
|
||||
url: DETAIL_RESULT_URL,
|
||||
text: "completed",
|
||||
status: "completed",
|
||||
updatedAt: 1,
|
||||
compareSourceUrl: DETAIL_REFERENCE_URL,
|
||||
referenceImageUrl: DETAIL_REFERENCE_URL,
|
||||
referencePreviewUrl: DETAIL_REFERENCE_URL,
|
||||
label: "Detail 1",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function expect(condition: boolean, message: string) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
function expectEqual<T>(actual: T, expected: T, label: string) {
|
||||
if (!Object.is(actual, expected)) {
|
||||
throw new Error(`${label}: expected ${String(expected)}, received ${String(actual)}`);
|
||||
}
|
||||
}
|
||||
|
||||
function expectIncludesUrl(urls: string[] | undefined, url: string) {
|
||||
expect(Boolean(urls?.includes(url)), `expected ${url} to be present`);
|
||||
}
|
||||
|
||||
function expectExcludesUrl(urls: string[] | undefined, url: string) {
|
||||
expect(!urls?.includes(url), `expected ${url} to be absent`);
|
||||
}
|
||||
|
||||
const legacyDetailState = createExistingDetailProjectState();
|
||||
expectEqual(
|
||||
legacyDetailState.workspaceState?.activeBusinessModeKey,
|
||||
"detail",
|
||||
"legacy detail mode"
|
||||
);
|
||||
expectIncludesUrl(legacyDetailState.resultCollections?.detail_replacement, DETAIL_RESULT_URL);
|
||||
expectEqual(
|
||||
legacyDetailState.workflowState.agentModeState?.detailReplacement.references.length,
|
||||
1,
|
||||
"legacy detail reference count"
|
||||
);
|
||||
|
||||
const resultOnlyLegacyState = normalizeAgentGenerationProjectPersistedState({
|
||||
workflowState: createWorkflowState(),
|
||||
workspaceState: {
|
||||
input: "legacy result-only detail replacement",
|
||||
selectedModelId: "auto",
|
||||
},
|
||||
resultCollections: {
|
||||
detail_replacement: [DETAIL_RESULT_URL_2],
|
||||
},
|
||||
});
|
||||
expectEqual(
|
||||
resultOnlyLegacyState.workspaceState?.activeBusinessModeKey,
|
||||
"detail",
|
||||
"result-only detail mode"
|
||||
);
|
||||
expectIncludesUrl(
|
||||
resultOnlyLegacyState.resultCollections?.detail_replacement,
|
||||
DETAIL_RESULT_URL_2
|
||||
);
|
||||
|
||||
const emptyProjectState = normalizeAgentGenerationProjectPersistedState({
|
||||
workflowState: createWorkflowState(),
|
||||
workspaceState: {
|
||||
input: "",
|
||||
selectedModelId: "auto",
|
||||
},
|
||||
});
|
||||
expectEqual(
|
||||
emptyProjectState.workspaceState?.activeBusinessModeKey,
|
||||
"main_sku",
|
||||
"empty project mode"
|
||||
);
|
||||
|
||||
const staleEmptySaveState = mergeAgentProjectStateForSave({
|
||||
existingState: legacyDetailState,
|
||||
incomingState: {
|
||||
workflowState: createWorkflowState(),
|
||||
workspaceState: {
|
||||
input: "",
|
||||
selectedModelId: "auto",
|
||||
activeBusinessModeKey: "main_sku",
|
||||
imageParams: {
|
||||
aspectRatio: "auto",
|
||||
imageSize: "2K",
|
||||
imageCount: 1,
|
||||
},
|
||||
},
|
||||
resultImageUrls: [],
|
||||
resultCollections: {},
|
||||
replacementResultState: {},
|
||||
},
|
||||
});
|
||||
expectEqual(
|
||||
staleEmptySaveState.workspaceState?.activeBusinessModeKey,
|
||||
"detail",
|
||||
"stale empty save mode"
|
||||
);
|
||||
expectIncludesUrl(
|
||||
staleEmptySaveState.resultCollections?.detail_replacement,
|
||||
DETAIL_RESULT_URL
|
||||
);
|
||||
expectIncludesUrl(getAllResultUrlsFromAgentProjectState(staleEmptySaveState), DETAIL_RESULT_URL);
|
||||
expectEqual(
|
||||
staleEmptySaveState.workflowState.agentModeState?.detailReplacement.references.length,
|
||||
1,
|
||||
"stale empty save reference count"
|
||||
);
|
||||
expectEqual(
|
||||
staleEmptySaveState.replacementResultState?.detail_replacement?.["detail-card-1"]?.url,
|
||||
DETAIL_RESULT_URL,
|
||||
"stale empty save replacement record"
|
||||
);
|
||||
|
||||
const replacementStateOnlySave = mergeAgentProjectStateForSave({
|
||||
existingState: normalizeAgentGenerationProjectPersistedState({
|
||||
workflowState: createWorkflowState({ detailReferences: 1 }),
|
||||
workspaceState: {
|
||||
input: "",
|
||||
selectedModelId: "auto",
|
||||
},
|
||||
}),
|
||||
incomingState: {
|
||||
workflowState: createWorkflowState({ detailReferences: 1 }),
|
||||
workspaceState: {
|
||||
input: "",
|
||||
selectedModelId: "auto",
|
||||
},
|
||||
replacementResultState: {
|
||||
detail_replacement: {
|
||||
"detail-card-1": {
|
||||
url: DETAIL_RESULT_URL_2,
|
||||
text: "completed",
|
||||
status: "completed",
|
||||
updatedAt: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expectEqual(
|
||||
replacementStateOnlySave.workspaceState?.activeBusinessModeKey,
|
||||
"detail",
|
||||
"replacement-state-only mode"
|
||||
);
|
||||
expectIncludesUrl(
|
||||
replacementStateOnlySave.resultCollections?.detail_replacement,
|
||||
DETAIL_RESULT_URL_2
|
||||
);
|
||||
expectIncludesUrl(
|
||||
getAllResultUrlsFromAgentProjectState(replacementStateOnlySave),
|
||||
DETAIL_RESULT_URL_2
|
||||
);
|
||||
|
||||
const deletedResultSave = mergeAgentProjectStateForSave({
|
||||
existingState: legacyDetailState,
|
||||
incomingState: {
|
||||
workflowState: createWorkflowState({ detailReferences: 1 }),
|
||||
workspaceState: {
|
||||
input: "",
|
||||
selectedModelId: "auto",
|
||||
activeBusinessModeKey: "detail",
|
||||
},
|
||||
deletedResultCollections: {
|
||||
detail_replacement: [DETAIL_RESULT_URL],
|
||||
},
|
||||
resultCollections: {},
|
||||
replacementResultState: {},
|
||||
},
|
||||
});
|
||||
expectExcludesUrl(deletedResultSave.resultCollections?.detail_replacement, DETAIL_RESULT_URL);
|
||||
expectExcludesUrl(getAllResultUrlsFromAgentProjectState(deletedResultSave), DETAIL_RESULT_URL);
|
||||
expectEqual(
|
||||
deletedResultSave.replacementResultState?.detail_replacement?.["detail-card-1"],
|
||||
undefined,
|
||||
"deleted replacement record"
|
||||
);
|
||||
|
||||
const skuReplacementState = normalizeAgentGenerationProjectPersistedState({
|
||||
workflowState: createWorkflowState({ skuReferences: 1 }),
|
||||
workspaceState: {
|
||||
input: "",
|
||||
selectedModelId: "auto",
|
||||
},
|
||||
replacementResultState: {
|
||||
sku_replacement: {
|
||||
"sku-card-1": {
|
||||
url: SKU_RESULT_URL,
|
||||
text: "completed",
|
||||
status: "completed",
|
||||
updatedAt: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expectEqual(
|
||||
skuReplacementState.workspaceState?.activeBusinessModeKey,
|
||||
"detail",
|
||||
"sku replacement mode"
|
||||
);
|
||||
expectIncludesUrl(skuReplacementState.resultCollections?.sku_replacement, SKU_RESULT_URL);
|
||||
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
ok: true,
|
||||
covered: [
|
||||
"legacy detail mode inference",
|
||||
"result-only detail mode inference",
|
||||
"empty project stays main_sku",
|
||||
"stale empty save keeps detail results",
|
||||
"stale empty save keeps detail reference cards",
|
||||
"replacementResultState repopulates resultCollections",
|
||||
"explicit deletion is respected",
|
||||
"sku replacement recovery",
|
||||
],
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
Loading…
x
Reference in New Issue
Block a user