feat: support configurable agent reference sources
This commit is contained in:
parent
8d602bf543
commit
da3f4fad64
@ -17,7 +17,7 @@ export const maxDuration = 180;
|
|||||||
const querySchema = z.object({
|
const querySchema = z.object({
|
||||||
keyword: z.string().trim().min(1).max(64),
|
keyword: z.string().trim().min(1).max(64),
|
||||||
queryType: z.enum(["sku", "spu"]).default("spu"),
|
queryType: z.enum(["sku", "spu"]).default("spu"),
|
||||||
target: z.enum(["detail", "sku"]),
|
target: z.enum(["main", "detail", "sku"]),
|
||||||
});
|
});
|
||||||
|
|
||||||
type ExternalTaskDetailCandidate = {
|
type ExternalTaskDetailCandidate = {
|
||||||
@ -116,17 +116,23 @@ function normalizeCandidate(
|
|||||||
|
|
||||||
function getCandidateTargetUrls(
|
function getCandidateTargetUrls(
|
||||||
candidate: AgentModeReferenceCandidate,
|
candidate: AgentModeReferenceCandidate,
|
||||||
target: "detail" | "sku"
|
target: "main" | "detail" | "sku"
|
||||||
) {
|
) {
|
||||||
|
if (target === "main") return candidate.mainImageUrls;
|
||||||
return target === "detail" ? candidate.detailImageUrls : candidate.skuImageUrls;
|
return target === "detail" ? candidate.detailImageUrls : candidate.skuImageUrls;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildReferenceItems(params: {
|
function buildReferenceItems(params: {
|
||||||
candidate: AgentModeReferenceCandidate;
|
candidate: AgentModeReferenceCandidate;
|
||||||
target: "detail" | "sku";
|
target: "main" | "detail" | "sku";
|
||||||
}): AgentDetailReplacementReferenceItem[] {
|
}): AgentDetailReplacementReferenceItem[] {
|
||||||
const urls = getCandidateTargetUrls(params.candidate, params.target);
|
const urls = getCandidateTargetUrls(params.candidate, params.target);
|
||||||
const targetLabel = params.target === "detail" ? "详情参考图" : "SKU参考图";
|
const targetLabel =
|
||||||
|
params.target === "main"
|
||||||
|
? "主图参考图"
|
||||||
|
: params.target === "detail"
|
||||||
|
? "详情参考图"
|
||||||
|
: "SKU参考图";
|
||||||
const prefix =
|
const prefix =
|
||||||
params.candidate.sceneName ||
|
params.candidate.sceneName ||
|
||||||
params.candidate.taskNo ||
|
params.candidate.taskNo ||
|
||||||
@ -222,11 +228,15 @@ export async function GET(request: NextRequest) {
|
|||||||
success: false,
|
success: false,
|
||||||
error: {
|
error: {
|
||||||
code:
|
code:
|
||||||
parsed.target === "detail"
|
parsed.target === "main"
|
||||||
|
? "MAIN_REFERENCE_NOT_FOUND"
|
||||||
|
: parsed.target === "detail"
|
||||||
? "DETAIL_REFERENCE_NOT_FOUND"
|
? "DETAIL_REFERENCE_NOT_FOUND"
|
||||||
: "SKU_REFERENCE_NOT_FOUND",
|
: "SKU_REFERENCE_NOT_FOUND",
|
||||||
message:
|
message:
|
||||||
parsed.target === "detail"
|
parsed.target === "main"
|
||||||
|
? "当前整套素材中没有可用的主图参考图"
|
||||||
|
: parsed.target === "detail"
|
||||||
? "当前整套素材中没有可用的详情参考图"
|
? "当前整套素材中没有可用的详情参考图"
|
||||||
: "当前整套素材中没有可用的 SKU 参考图",
|
: "当前整套素材中没有可用的 SKU 参考图",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -54,6 +54,7 @@ import type {
|
|||||||
AgentModeReferenceCandidate,
|
AgentModeReferenceCandidate,
|
||||||
AgentModeWorkspaceState,
|
AgentModeWorkspaceState,
|
||||||
AgentPreset,
|
AgentPreset,
|
||||||
|
AgentTaskDetailReferenceSource,
|
||||||
AgentWorkflowShowcaseConfig,
|
AgentWorkflowShowcaseConfig,
|
||||||
AgentWorkflowComposerContext,
|
AgentWorkflowComposerContext,
|
||||||
AgentWorkflowMaterialQueryType,
|
AgentWorkflowMaterialQueryType,
|
||||||
@ -107,7 +108,7 @@ interface AgentModeReferenceSearchResponse {
|
|||||||
data?: {
|
data?: {
|
||||||
query: string;
|
query: string;
|
||||||
queryType: AgentWorkflowMaterialQueryType;
|
queryType: AgentWorkflowMaterialQueryType;
|
||||||
target: "detail" | "sku";
|
target: AgentTaskDetailReferenceSource;
|
||||||
matchedSpu?: string | null;
|
matchedSpu?: string | null;
|
||||||
matchedSku?: string | null;
|
matchedSku?: string | null;
|
||||||
categoryName?: string | null;
|
categoryName?: string | null;
|
||||||
@ -123,6 +124,10 @@ interface AgentModeReferenceSearchResponse {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTaskDetailReferenceSourceLabel(source: AgentTaskDetailReferenceSource) {
|
||||||
|
return source === "main" ? "主图" : source === "sku" ? "SKU图" : "详情图";
|
||||||
|
}
|
||||||
|
|
||||||
function areImageGenerationParamsEqual(
|
function areImageGenerationParamsEqual(
|
||||||
left: ImageGenerationParams,
|
left: ImageGenerationParams,
|
||||||
right: ImageGenerationParams
|
right: ImageGenerationParams
|
||||||
@ -237,15 +242,20 @@ function getImageDimensions(url: string): Promise<{
|
|||||||
|
|
||||||
function buildReferenceCardsFromCandidate(params: {
|
function buildReferenceCardsFromCandidate(params: {
|
||||||
candidate: AgentModeReferenceCandidate | null | undefined;
|
candidate: AgentModeReferenceCandidate | null | undefined;
|
||||||
target: "detail" | "sku";
|
target: AgentTaskDetailReferenceSource;
|
||||||
productCount?: number;
|
productCount?: number;
|
||||||
|
assignProductIndex?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const candidate = params.candidate;
|
const candidate = params.candidate;
|
||||||
if (!candidate) return [] as AgentModeWorkspaceState["detailReplacement"]["references"];
|
if (!candidate) return [] as AgentModeWorkspaceState["detailReplacement"]["references"];
|
||||||
|
|
||||||
const urls =
|
const urls =
|
||||||
params.target === "detail" ? candidate.detailImageUrls || [] : candidate.skuImageUrls || [];
|
params.target === "main"
|
||||||
const targetLabel = params.target === "detail" ? "详情参考图" : "SKU参考图";
|
? candidate.mainImageUrls || []
|
||||||
|
: params.target === "detail"
|
||||||
|
? candidate.detailImageUrls || []
|
||||||
|
: candidate.skuImageUrls || [];
|
||||||
|
const targetLabel = `${getTaskDetailReferenceSourceLabel(params.target)}参考图`;
|
||||||
const prefix =
|
const prefix =
|
||||||
candidate.sceneName || candidate.taskNo || candidate.productModel || "候选整套";
|
candidate.sceneName || candidate.taskNo || candidate.productModel || "候选整套";
|
||||||
|
|
||||||
@ -256,17 +266,19 @@ function buildReferenceCardsFromCandidate(params: {
|
|||||||
candidateId: candidate.id,
|
candidateId: candidate.id,
|
||||||
sortOrder: index,
|
sortOrder: index,
|
||||||
assignedProductIndex:
|
assignedProductIndex:
|
||||||
params.target === "sku" && (params.productCount || 0) > 0
|
params.assignProductIndex === true && (params.productCount || 0) > 0
|
||||||
? Math.min(index, Math.max((params.productCount || 1) - 1, 0))
|
? Math.min(index, Math.max((params.productCount || 1) - 1, 0))
|
||||||
: null,
|
: null,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatCandidateMeta(candidate: AgentModeReferenceCandidate, target: "detail" | "sku") {
|
function formatCandidateMeta(candidate: AgentModeReferenceCandidate, target: AgentTaskDetailReferenceSource) {
|
||||||
const imageCount =
|
const imageCount =
|
||||||
target === "detail"
|
target === "main"
|
||||||
? candidate.detailImageUrls.length
|
? candidate.mainImageUrls.length
|
||||||
: candidate.skuImageUrls.length;
|
: target === "detail"
|
||||||
|
? candidate.detailImageUrls.length
|
||||||
|
: candidate.skuImageUrls.length;
|
||||||
const uploadedAt = candidate.uploadedAt ? new Date(candidate.uploadedAt) : null;
|
const uploadedAt = candidate.uploadedAt ? new Date(candidate.uploadedAt) : null;
|
||||||
const timeText =
|
const timeText =
|
||||||
uploadedAt && !Number.isNaN(uploadedAt.getTime())
|
uploadedAt && !Number.isNaN(uploadedAt.getTime())
|
||||||
@ -289,10 +301,14 @@ function formatCandidateMeta(candidate: AgentModeReferenceCandidate, target: "de
|
|||||||
|
|
||||||
function getCandidatePreviewUrl(
|
function getCandidatePreviewUrl(
|
||||||
candidate: AgentModeReferenceCandidate,
|
candidate: AgentModeReferenceCandidate,
|
||||||
target: "detail" | "sku"
|
target: AgentTaskDetailReferenceSource
|
||||||
) {
|
) {
|
||||||
const targetUrls =
|
const targetUrls =
|
||||||
target === "detail" ? candidate.detailImageUrls || [] : candidate.skuImageUrls || [];
|
target === "main"
|
||||||
|
? candidate.mainImageUrls || []
|
||||||
|
: target === "detail"
|
||||||
|
? candidate.detailImageUrls || []
|
||||||
|
: candidate.skuImageUrls || [];
|
||||||
return (
|
return (
|
||||||
targetUrls[0] ||
|
targetUrls[0] ||
|
||||||
candidate.mainImageUrls[0] ||
|
candidate.mainImageUrls[0] ||
|
||||||
@ -301,6 +317,17 @@ function getCandidatePreviewUrl(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCandidateTargetCount(
|
||||||
|
candidate: AgentModeReferenceCandidate,
|
||||||
|
target: AgentTaskDetailReferenceSource
|
||||||
|
) {
|
||||||
|
return target === "main"
|
||||||
|
? candidate.mainImageUrls.length
|
||||||
|
: target === "detail"
|
||||||
|
? candidate.detailImageUrls.length
|
||||||
|
: candidate.skuImageUrls.length;
|
||||||
|
}
|
||||||
|
|
||||||
type ReplacementCardStatus = "waiting" | "queued" | "generating" | "completed" | "missing";
|
type ReplacementCardStatus = "waiting" | "queued" | "generating" | "completed" | "missing";
|
||||||
|
|
||||||
function getReplacementCardStatus(params: {
|
function getReplacementCardStatus(params: {
|
||||||
@ -535,6 +562,10 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
fallback: modelSlotEnabled ? 3 : productSlotCount,
|
fallback: modelSlotEnabled ? 3 : productSlotCount,
|
||||||
})
|
})
|
||||||
: 0;
|
: 0;
|
||||||
|
const detailReferenceTarget = presetModeConfig.detail.referenceImageSource;
|
||||||
|
const skuReferenceTarget = presetModeConfig.sku.referenceImageSource;
|
||||||
|
const detailReferenceSourceLabel = getTaskDetailReferenceSourceLabel(detailReferenceTarget);
|
||||||
|
const skuReferenceSourceLabel = getTaskDetailReferenceSourceLabel(skuReferenceTarget);
|
||||||
const shouldAllowModelOverride = !isSceneGenerationPreset || presetModeConfig.scene.allowModelOverride;
|
const shouldAllowModelOverride = !isSceneGenerationPreset || presetModeConfig.scene.allowModelOverride;
|
||||||
const selectedProductAttachments = attachments.filter((item, index) =>
|
const selectedProductAttachments = attachments.filter((item, index) =>
|
||||||
modelSlotIndex >= 0 ? index !== modelSlotIndex && Boolean(item) : Boolean(item)
|
modelSlotIndex >= 0 ? index !== modelSlotIndex && Boolean(item) : Boolean(item)
|
||||||
@ -1274,11 +1305,11 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isDetailReplacementPreset && detailReplacementReferenceCards.length === 0) {
|
if (isDetailReplacementPreset && detailReplacementReferenceCards.length === 0) {
|
||||||
toast.error("请先查询并选择详情参考图");
|
toast.error(`请先查询并选择${detailReferenceSourceLabel}参考图`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isSkuReplacementPreset && skuReplacementReferenceCards.length === 0) {
|
if (isSkuReplacementPreset && skuReplacementReferenceCards.length === 0) {
|
||||||
toast.error("请先查询并选择 SKU 参考图");
|
toast.error(`请先查询并选择${skuReferenceSourceLabel}参考图`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const uploadedAttachmentCount = attachments.filter(Boolean).length;
|
const uploadedAttachmentCount = attachments.filter(Boolean).length;
|
||||||
@ -1431,7 +1462,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
const handleDetailReplacementSearch = useCallback(async () => {
|
const handleDetailReplacementSearch = useCallback(async () => {
|
||||||
const normalized = detailReplacementQueryInput.trim().toUpperCase();
|
const normalized = detailReplacementQueryInput.trim().toUpperCase();
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
toast.error("请输入 SPU 后再查询详情图");
|
toast.error(`请输入 SPU 后再查询${detailReferenceSourceLabel}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1444,13 +1475,13 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/agent-workflow/reference-search?keyword=${encodeURIComponent(
|
`/api/agent-workflow/reference-search?keyword=${encodeURIComponent(
|
||||||
normalized
|
normalized
|
||||||
)}&queryType=spu&target=detail`,
|
)}&queryType=spu&target=${detailReferenceTarget}`,
|
||||||
{ cache: "no-store" }
|
{ cache: "no-store" }
|
||||||
);
|
);
|
||||||
const result = (await response.json().catch(() => ({}))) as AgentModeReferenceSearchResponse;
|
const result = (await response.json().catch(() => ({}))) as AgentModeReferenceSearchResponse;
|
||||||
|
|
||||||
if (!response.ok || !result?.success || !result?.data) {
|
if (!response.ok || !result?.success || !result?.data) {
|
||||||
throw new Error(result?.error?.message || "详情参考图查询失败");
|
throw new Error(result?.error?.message || `${detailReferenceSourceLabel}参考图查询失败`);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDetailReplacementState({
|
updateDetailReplacementState({
|
||||||
@ -1465,15 +1496,15 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
lastQuery: normalized,
|
lastQuery: normalized,
|
||||||
});
|
});
|
||||||
toast.success(`已载入 ${result.data.references?.length || 0} 张详情参考图`);
|
toast.success(`已载入 ${result.data.references?.length || 0} 张${detailReferenceSourceLabel}参考图`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setDetailReplacementSearchState({
|
setDetailReplacementSearchState({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
lastQuery: normalized,
|
lastQuery: normalized,
|
||||||
});
|
});
|
||||||
toast.error(error instanceof Error ? error.message : "详情参考图查询失败");
|
toast.error(error instanceof Error ? error.message : `${detailReferenceSourceLabel}参考图查询失败`);
|
||||||
}
|
}
|
||||||
}, [detailReplacementQueryInput, updateDetailReplacementState]);
|
}, [detailReferenceSourceLabel, detailReferenceTarget, detailReplacementQueryInput, updateDetailReplacementState]);
|
||||||
const handleSelectDetailReplacementCandidate = useCallback((candidateId: string) => {
|
const handleSelectDetailReplacementCandidate = useCallback((candidateId: string) => {
|
||||||
updateDetailReplacementState((current) => {
|
updateDetailReplacementState((current) => {
|
||||||
const nextCandidate =
|
const nextCandidate =
|
||||||
@ -1483,12 +1514,12 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
selectedCandidateId: candidateId,
|
selectedCandidateId: candidateId,
|
||||||
references: buildReferenceCardsFromCandidate({
|
references: buildReferenceCardsFromCandidate({
|
||||||
candidate: nextCandidate,
|
candidate: nextCandidate,
|
||||||
target: "detail",
|
target: detailReferenceTarget,
|
||||||
productCount: selectedProductAttachments.length,
|
productCount: selectedProductAttachments.length,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [selectedProductAttachments.length, updateDetailReplacementState]);
|
}, [detailReferenceTarget, selectedProductAttachments.length, updateDetailReplacementState]);
|
||||||
const handleRemoveDetailReferenceCard = useCallback((cardId: string) => {
|
const handleRemoveDetailReferenceCard = useCallback((cardId: string) => {
|
||||||
updateDetailReplacementState((current) => ({
|
updateDetailReplacementState((current) => ({
|
||||||
...current,
|
...current,
|
||||||
@ -1506,7 +1537,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
const handleSkuReplacementSearch = useCallback(async () => {
|
const handleSkuReplacementSearch = useCallback(async () => {
|
||||||
const normalized = skuReplacementQueryInput.trim().toUpperCase();
|
const normalized = skuReplacementQueryInput.trim().toUpperCase();
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
toast.error(`请输入${skuReplacementQueryType === "spu" ? "SPU" : "SKU"}后再查询 SKU 参考图`);
|
toast.error(`请输入${skuReplacementQueryType === "spu" ? "SPU" : "SKU"}后再查询${skuReferenceSourceLabel}参考图`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1519,13 +1550,13 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/agent-workflow/reference-search?keyword=${encodeURIComponent(
|
`/api/agent-workflow/reference-search?keyword=${encodeURIComponent(
|
||||||
normalized
|
normalized
|
||||||
)}&queryType=${skuReplacementQueryType}&target=sku`,
|
)}&queryType=${skuReplacementQueryType}&target=${skuReferenceTarget}`,
|
||||||
{ cache: "no-store" }
|
{ cache: "no-store" }
|
||||||
);
|
);
|
||||||
const result = (await response.json().catch(() => ({}))) as AgentModeReferenceSearchResponse;
|
const result = (await response.json().catch(() => ({}))) as AgentModeReferenceSearchResponse;
|
||||||
|
|
||||||
if (!response.ok || !result?.success || !result?.data) {
|
if (!response.ok || !result?.success || !result?.data) {
|
||||||
throw new Error(result?.error?.message || "SKU 参考图查询失败");
|
throw new Error(result?.error?.message || `${skuReferenceSourceLabel}参考图查询失败`);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSkuReplacementState({
|
updateSkuReplacementState({
|
||||||
@ -1547,16 +1578,18 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
lastQuery: normalized,
|
lastQuery: normalized,
|
||||||
});
|
});
|
||||||
toast.success(`已载入 ${result.data.references?.length || 0} 张 SKU 参考图`);
|
toast.success(`已载入 ${result.data.references?.length || 0} 张${skuReferenceSourceLabel}参考图`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setSkuReplacementSearchState({
|
setSkuReplacementSearchState({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
lastQuery: normalized,
|
lastQuery: normalized,
|
||||||
});
|
});
|
||||||
toast.error(error instanceof Error ? error.message : "SKU 参考图查询失败");
|
toast.error(error instanceof Error ? error.message : `${skuReferenceSourceLabel}参考图查询失败`);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
selectedProductAttachments.length,
|
selectedProductAttachments.length,
|
||||||
|
skuReferenceSourceLabel,
|
||||||
|
skuReferenceTarget,
|
||||||
skuReplacementQueryInput,
|
skuReplacementQueryInput,
|
||||||
skuReplacementQueryType,
|
skuReplacementQueryType,
|
||||||
updateSkuReplacementState,
|
updateSkuReplacementState,
|
||||||
@ -1570,12 +1603,13 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
selectedCandidateId: candidateId,
|
selectedCandidateId: candidateId,
|
||||||
references: buildReferenceCardsFromCandidate({
|
references: buildReferenceCardsFromCandidate({
|
||||||
candidate: nextCandidate,
|
candidate: nextCandidate,
|
||||||
target: "sku",
|
target: skuReferenceTarget,
|
||||||
productCount: selectedProductAttachments.length,
|
productCount: selectedProductAttachments.length,
|
||||||
|
assignProductIndex: true,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [selectedProductAttachments.length, updateSkuReplacementState]);
|
}, [selectedProductAttachments.length, skuReferenceTarget, updateSkuReplacementState]);
|
||||||
const handleAssignSkuReferenceProduct = useCallback((cardId: string, productIndex: number) => {
|
const handleAssignSkuReferenceProduct = useCallback((cardId: string, productIndex: number) => {
|
||||||
updateSkuReplacementState((current) => ({
|
updateSkuReplacementState((current) => ({
|
||||||
...current,
|
...current,
|
||||||
@ -2191,9 +2225,9 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
<div className="min-h-0 border-b border-white/10 p-4 xl:border-b-0 xl:border-r">
|
<div className="min-h-0 border-b border-white/10 p-4 xl:border-b-0 xl:border-r">
|
||||||
<div className="mb-3 flex items-center justify-between">
|
<div className="mb-3 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-semibold text-white">详情参考图</div>
|
<div className="text-sm font-semibold text-white">{detailReferenceSourceLabel}参考图</div>
|
||||||
<div className="text-xs text-white/40">
|
<div className="text-xs text-white/40">
|
||||||
仅支持按 SPU 查询任务-详情整套候选,并切换某一套的详情参考图。
|
仅支持按 SPU 查询任务整套候选,并切换某一套的{detailReferenceSourceLabel}参考图。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-full border border-white/10 bg-white/[0.03] px-3 py-1 text-xs text-white/55">
|
<div className="rounded-full border border-white/10 bg-white/[0.03] px-3 py-1 text-xs text-white/55">
|
||||||
@ -2216,7 +2250,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
queryType: "spu",
|
queryType: "spu",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
placeholder="输入 SPU,查询任务-详情整套中的详情参考图"
|
placeholder={`输入 SPU,查询任务整套中的${detailReferenceSourceLabel}参考图`}
|
||||||
className="min-h-[68px] resize-none rounded-[22px] border-white/10 bg-black/20 text-sm text-white placeholder:text-white/22 focus:border-emerald-400/30"
|
className="min-h-[68px] resize-none rounded-[22px] border-white/10 bg-black/20 text-sm text-white placeholder:text-white/22 focus:border-emerald-400/30"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@ -2231,7 +2265,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
查询中
|
查询中
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"查询详情图"
|
`查询${detailReferenceSourceLabel}`
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
{detailReplacementReferenceCards.length > 0 ? (
|
{detailReplacementReferenceCards.length > 0 ? (
|
||||||
@ -2253,7 +2287,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
{detailReplacementSearchState.lastQuery ||
|
{detailReplacementSearchState.lastQuery ||
|
||||||
detailReplacementQueryInput.trim().toUpperCase()}
|
detailReplacementQueryInput.trim().toUpperCase()}
|
||||||
<span className="ml-2 text-emerald-200/70">
|
<span className="ml-2 text-emerald-200/70">
|
||||||
已接入真实详情参考图接口
|
{`已接入真实${detailReferenceSourceLabel}参考图接口`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
@ -2271,7 +2305,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
<div className="agent-dark-scrollbar flex gap-2 overflow-x-auto pb-1">
|
<div className="agent-dark-scrollbar flex gap-2 overflow-x-auto pb-1">
|
||||||
{detailReplacementCandidates.map((candidate) => {
|
{detailReplacementCandidates.map((candidate) => {
|
||||||
const isSelected = detailReplacementSelectedCandidateId === candidate.id;
|
const isSelected = detailReplacementSelectedCandidateId === candidate.id;
|
||||||
const previewUrl = getCandidatePreviewUrl(candidate, "detail");
|
const previewUrl = getCandidatePreviewUrl(candidate, detailReferenceTarget);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={candidate.id}
|
key={candidate.id}
|
||||||
@ -2293,7 +2327,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="absolute left-3 top-3 rounded-full border border-white/10 bg-black/45 px-2.5 py-1 text-[11px] text-white/78">
|
<div className="absolute left-3 top-3 rounded-full border border-white/10 bg-black/45 px-2.5 py-1 text-[11px] text-white/78">
|
||||||
{candidate.detailImageUrls.length} 张详情图
|
{getCandidateTargetCount(candidate, detailReferenceTarget)} 张{detailReferenceSourceLabel}
|
||||||
</div>
|
</div>
|
||||||
{isSelected ? (
|
{isSelected ? (
|
||||||
<div className="absolute right-3 top-3 rounded-full border border-emerald-300/35 bg-emerald-500/[0.14] px-2.5 py-1 text-[11px] text-emerald-100">
|
<div className="absolute right-3 top-3 rounded-full border border-emerald-300/35 bg-emerald-500/[0.14] px-2.5 py-1 text-[11px] text-emerald-100">
|
||||||
@ -2306,7 +2340,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
{candidate.sceneName || candidate.taskNo || candidate.productModel || "候选整套"}
|
{candidate.sceneName || candidate.taskNo || candidate.productModel || "候选整套"}
|
||||||
</div>
|
</div>
|
||||||
<div className="line-clamp-2 text-[11px] text-white/42">
|
<div className="line-clamp-2 text-[11px] text-white/42">
|
||||||
{formatCandidateMeta(candidate, "detail") || "点击切换这套详情参考图"}
|
{formatCandidateMeta(candidate, detailReferenceTarget) || `点击切换这套${detailReferenceSourceLabel}参考图`}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-1.5 text-[11px] text-white/48">
|
<div className="flex flex-wrap gap-1.5 text-[11px] text-white/48">
|
||||||
{candidate.shopName ? (
|
{candidate.shopName ? (
|
||||||
@ -2370,7 +2404,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/85 via-black/35 to-transparent px-4 py-3">
|
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/85 via-black/35 to-transparent px-4 py-3">
|
||||||
<div className="text-sm font-medium text-white/88">{card.label}</div>
|
<div className="text-sm font-medium text-white/88">{card.label}</div>
|
||||||
<div className="text-xs text-white/45">
|
<div className="text-xs text-white/45">
|
||||||
第 {index + 1} 张详情参考图
|
第 {index + 1} 张{detailReferenceSourceLabel}参考图
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -2383,9 +2417,9 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="flex min-h-[220px] flex-col items-center justify-center rounded-[22px] border border-dashed border-white/12 bg-black/20 px-5 text-center">
|
<div className="flex min-h-[220px] flex-col items-center justify-center rounded-[22px] border border-dashed border-white/12 bg-black/20 px-5 text-center">
|
||||||
<div className="text-sm font-medium text-white/72">等待详情参考图</div>
|
<div className="text-sm font-medium text-white/72">等待{detailReferenceSourceLabel}参考图</div>
|
||||||
<div className="mt-2 text-xs leading-6 text-white/40">
|
<div className="mt-2 text-xs leading-6 text-white/40">
|
||||||
输入 SPU 后,这里会展示任务-详情整套里的真实详情参考图。
|
输入 SPU 后,这里会展示任务整套里的真实{detailReferenceSourceLabel}参考图。
|
||||||
删除不需要的参考图后,列表会自动上移补位。
|
删除不需要的参考图后,列表会自动上移补位。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -2398,7 +2432,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-semibold text-white">替换结果</div>
|
<div className="text-sm font-semibold text-white">替换结果</div>
|
||||||
<div className="text-xs text-white/40">
|
<div className="text-xs text-white/40">
|
||||||
每张详情参考图将对应生成一张替换结果,并支持逐屏文字编辑。
|
每张{detailReferenceSourceLabel}参考图将对应生成一张替换结果,并支持逐屏文字编辑。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-full border border-white/10 bg-white/[0.03] px-3 py-1 text-xs text-emerald-200/80">
|
<div className="rounded-full border border-white/10 bg-white/[0.03] px-3 py-1 text-xs text-emerald-200/80">
|
||||||
@ -2476,7 +2510,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
结果图 {card.index + 1}
|
结果图 {card.index + 1}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-white/35">
|
<div className="text-xs text-white/35">
|
||||||
将按“输入图组 + 第 {card.index + 1} 张详情参考图”生成
|
将按“输入图组 + 第 {card.index + 1} 张{detailReferenceSourceLabel}参考图”生成
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -2500,7 +2534,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
<div className="flex min-h-[220px] flex-col items-center justify-center rounded-[22px] border border-dashed border-white/12 bg-black/20 px-5 text-center">
|
<div className="flex min-h-[220px] flex-col items-center justify-center rounded-[22px] border border-dashed border-white/12 bg-black/20 px-5 text-center">
|
||||||
<div className="text-sm font-medium text-white/72">等待替换结果</div>
|
<div className="text-sm font-medium text-white/72">等待替换结果</div>
|
||||||
<div className="mt-2 text-xs leading-6 text-white/40">
|
<div className="mt-2 text-xs leading-6 text-white/40">
|
||||||
详情参考图加载后,右侧会按相同顺序生成对应结果图,并逐屏支持文字编辑。
|
{detailReferenceSourceLabel}参考图加载后,右侧会按相同顺序生成对应结果图,并逐屏支持文字编辑。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -2514,7 +2548,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-semibold text-white">输入图</div>
|
<div className="text-sm font-semibold text-white">输入图</div>
|
||||||
<div className="text-xs text-white/40">
|
<div className="text-xs text-white/40">
|
||||||
选中的白底图会和 SKU 参考图建立一对一替换关系
|
选中的白底图会和{skuReferenceSourceLabel}参考图建立一对一替换关系
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-full border border-white/10 bg-white/[0.03] px-3 py-1 text-xs text-white/55">
|
<div className="rounded-full border border-white/10 bg-white/[0.03] px-3 py-1 text-xs text-white/55">
|
||||||
@ -2566,9 +2600,9 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
<div className="min-h-0 border-b border-white/10 p-4 xl:border-b-0 xl:border-r">
|
<div className="min-h-0 border-b border-white/10 p-4 xl:border-b-0 xl:border-r">
|
||||||
<div className="mb-3 flex items-center justify-between">
|
<div className="mb-3 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-semibold text-white">SKU 参考图</div>
|
<div className="text-sm font-semibold text-white">{skuReferenceSourceLabel}参考图</div>
|
||||||
<div className="text-xs text-white/40">
|
<div className="text-xs text-white/40">
|
||||||
支持按 SKU 或 SPU 查询任务-详情整套候选,并切换对应的 SKU 参考图。
|
支持按 SKU 或 SPU 查询任务整套候选,并切换对应的{skuReferenceSourceLabel}参考图。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-full border border-white/10 bg-white/[0.03] px-3 py-1 text-xs text-white/55">
|
<div className="rounded-full border border-white/10 bg-white/[0.03] px-3 py-1 text-xs text-white/55">
|
||||||
@ -2613,7 +2647,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
query: e.target.value,
|
query: e.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
placeholder={`输入${skuReplacementQueryType === "spu" ? "SPU" : "SKU"},查询任务-详情整套中的 SKU 参考图`}
|
placeholder={`输入${skuReplacementQueryType === "spu" ? "SPU" : "SKU"},查询任务整套中的${skuReferenceSourceLabel}参考图`}
|
||||||
className="min-h-[68px] resize-none rounded-[22px] border-white/10 bg-black/20 text-sm text-white placeholder:text-white/22 focus:border-emerald-400/30"
|
className="min-h-[68px] resize-none rounded-[22px] border-white/10 bg-black/20 text-sm text-white placeholder:text-white/22 focus:border-emerald-400/30"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@ -2628,7 +2662,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
查询中
|
查询中
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"查询 SKU 图"
|
`查询${skuReferenceSourceLabel}`
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
{skuReplacementReferenceCards.length > 0 ? (
|
{skuReplacementReferenceCards.length > 0 ? (
|
||||||
@ -2650,7 +2684,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
{skuReplacementSearchState.lastQuery ||
|
{skuReplacementSearchState.lastQuery ||
|
||||||
skuReplacementQueryInput.trim().toUpperCase()}
|
skuReplacementQueryInput.trim().toUpperCase()}
|
||||||
<span className="ml-2 text-emerald-200/70">
|
<span className="ml-2 text-emerald-200/70">
|
||||||
已接入真实 SKU 参考图接口
|
{`已接入真实${skuReferenceSourceLabel}参考图接口`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
@ -2668,7 +2702,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
<div className="agent-dark-scrollbar flex gap-2 overflow-x-auto pb-1">
|
<div className="agent-dark-scrollbar flex gap-2 overflow-x-auto pb-1">
|
||||||
{skuReplacementCandidates.map((candidate) => {
|
{skuReplacementCandidates.map((candidate) => {
|
||||||
const isSelected = skuReplacementSelectedCandidateId === candidate.id;
|
const isSelected = skuReplacementSelectedCandidateId === candidate.id;
|
||||||
const previewUrl = getCandidatePreviewUrl(candidate, "sku");
|
const previewUrl = getCandidatePreviewUrl(candidate, skuReferenceTarget);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={candidate.id}
|
key={candidate.id}
|
||||||
@ -2690,7 +2724,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="absolute left-3 top-3 rounded-full border border-white/10 bg-black/45 px-2.5 py-1 text-[11px] text-white/78">
|
<div className="absolute left-3 top-3 rounded-full border border-white/10 bg-black/45 px-2.5 py-1 text-[11px] text-white/78">
|
||||||
{candidate.skuImageUrls.length} 张SKU图
|
{getCandidateTargetCount(candidate, skuReferenceTarget)} 张{skuReferenceSourceLabel}
|
||||||
</div>
|
</div>
|
||||||
{isSelected ? (
|
{isSelected ? (
|
||||||
<div className="absolute right-3 top-3 rounded-full border border-emerald-300/35 bg-emerald-500/[0.14] px-2.5 py-1 text-[11px] text-emerald-100">
|
<div className="absolute right-3 top-3 rounded-full border border-emerald-300/35 bg-emerald-500/[0.14] px-2.5 py-1 text-[11px] text-emerald-100">
|
||||||
@ -2703,7 +2737,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
{candidate.sceneName || candidate.taskNo || candidate.productModel || "候选整套"}
|
{candidate.sceneName || candidate.taskNo || candidate.productModel || "候选整套"}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 line-clamp-2 text-[11px] text-white/42">
|
<div className="mt-1 line-clamp-2 text-[11px] text-white/42">
|
||||||
{formatCandidateMeta(candidate, "sku") || "点击切换这套 SKU 参考图"}
|
{formatCandidateMeta(candidate, skuReferenceTarget) || `点击切换这套${skuReferenceSourceLabel}参考图`}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-1.5 text-[11px] text-white/48">
|
<div className="flex flex-wrap gap-1.5 text-[11px] text-white/48">
|
||||||
{candidate.shopName ? (
|
{candidate.shopName ? (
|
||||||
@ -2776,7 +2810,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/85 via-black/35 to-transparent px-4 py-3">
|
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/85 via-black/35 to-transparent px-4 py-3">
|
||||||
<div className="text-sm font-medium text-white/88">{card.label}</div>
|
<div className="text-sm font-medium text-white/88">{card.label}</div>
|
||||||
<div className="text-xs text-white/45">
|
<div className="text-xs text-white/45">
|
||||||
第 {index + 1} 张 SKU 参考图
|
第 {index + 1} 张{skuReferenceSourceLabel}参考图
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -2821,9 +2855,9 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="flex min-h-[220px] flex-col items-center justify-center rounded-[22px] border border-dashed border-white/12 bg-black/20 px-5 text-center">
|
<div className="flex min-h-[220px] flex-col items-center justify-center rounded-[22px] border border-dashed border-white/12 bg-black/20 px-5 text-center">
|
||||||
<div className="text-sm font-medium text-white/72">等待 SKU 参考图</div>
|
<div className="text-sm font-medium text-white/72">等待{skuReferenceSourceLabel}参考图</div>
|
||||||
<div className="mt-2 text-xs leading-6 text-white/40">
|
<div className="mt-2 text-xs leading-6 text-white/40">
|
||||||
输入 SKU 或 SPU 后,这里会展示任务-详情整套里的真实 SKU 参考图。
|
输入 SKU 或 SPU 后,这里会展示任务整套里的真实{skuReferenceSourceLabel}参考图。
|
||||||
选择多少张图,就会按相同数量进入替换流程。
|
选择多少张图,就会按相同数量进入替换流程。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -2836,7 +2870,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-semibold text-white">替换结果</div>
|
<div className="text-sm font-semibold text-white">替换结果</div>
|
||||||
<div className="text-xs text-white/40">
|
<div className="text-xs text-white/40">
|
||||||
每张 SKU 参考图会对应一张结果图,形成一对一批量替换结果。
|
每张{skuReferenceSourceLabel}参考图会对应一张结果图,形成一对一批量替换结果。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-full border border-white/10 bg-white/[0.03] px-3 py-1 text-xs text-emerald-200/80">
|
<div className="rounded-full border border-white/10 bg-white/[0.03] px-3 py-1 text-xs text-emerald-200/80">
|
||||||
@ -2907,7 +2941,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
结果图 {card.index + 1}
|
结果图 {card.index + 1}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-white/35">
|
<div className="text-xs text-white/35">
|
||||||
将按“输入图 + 第 {card.index + 1} 张 SKU 参考图”生成
|
将按“输入图 + 第 {card.index + 1} 张{skuReferenceSourceLabel}参考图”生成
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -2930,7 +2964,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
|||||||
<div className="flex min-h-[220px] flex-col items-center justify-center rounded-[22px] border border-dashed border-white/12 bg-black/20 px-5 text-center">
|
<div className="flex min-h-[220px] flex-col items-center justify-center rounded-[22px] border border-dashed border-white/12 bg-black/20 px-5 text-center">
|
||||||
<div className="text-sm font-medium text-white/72">等待替换结果</div>
|
<div className="text-sm font-medium text-white/72">等待替换结果</div>
|
||||||
<div className="mt-2 text-xs leading-6 text-white/40">
|
<div className="mt-2 text-xs leading-6 text-white/40">
|
||||||
SKU 参考图加载后,右侧会按相同顺序生成一对一替换结果,并支持批量下载。
|
{skuReferenceSourceLabel}参考图加载后,右侧会按相同顺序生成一对一替换结果,并支持批量下载。
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -44,6 +44,7 @@ import type {
|
|||||||
AgentCreatePayload,
|
AgentCreatePayload,
|
||||||
AgentPresetModeType,
|
AgentPresetModeType,
|
||||||
AgentPresetModeConfig,
|
AgentPresetModeConfig,
|
||||||
|
AgentTaskDetailReferenceSource,
|
||||||
AgentPresetType,
|
AgentPresetType,
|
||||||
AgentPreset,
|
AgentPreset,
|
||||||
AgentPresetSlot,
|
AgentPresetSlot,
|
||||||
@ -102,6 +103,28 @@ const IMAGE_MODE_OPTIONS: Array<{
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const TASK_DETAIL_REFERENCE_SOURCE_OPTIONS: Array<{
|
||||||
|
value: AgentTaskDetailReferenceSource;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
value: "main",
|
||||||
|
label: "主图",
|
||||||
|
description: "获取任务整套里的主图作为参考图来源。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "sku",
|
||||||
|
label: "SKU图",
|
||||||
|
description: "获取任务整套里的 SKU 图作为参考图来源。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "detail",
|
||||||
|
label: "详情图",
|
||||||
|
description: "获取任务整套里的详情图作为参考图来源。",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export function AgentPresetDialog({
|
export function AgentPresetDialog({
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
@ -1209,6 +1232,69 @@ export function AgentPresetDialog({
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className={cn("text-sm font-medium", isDark ? "text-white/88" : "")}>
|
||||||
|
参考图来源
|
||||||
|
</div>
|
||||||
|
<Select
|
||||||
|
value={
|
||||||
|
isDetailReplacementModeBuilder
|
||||||
|
? modeConfig.detail.referenceImageSource
|
||||||
|
: modeConfig.sku.referenceImageSource
|
||||||
|
}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setModeConfig((prev) => ({
|
||||||
|
...prev,
|
||||||
|
modeType: isDetailReplacementModeBuilder
|
||||||
|
? "detail_replacement"
|
||||||
|
: "sku_replacement",
|
||||||
|
detail: {
|
||||||
|
...prev.detail,
|
||||||
|
referenceImageSource: isDetailReplacementModeBuilder
|
||||||
|
? (value as AgentTaskDetailReferenceSource)
|
||||||
|
: prev.detail.referenceImageSource,
|
||||||
|
},
|
||||||
|
sku: {
|
||||||
|
...prev.sku,
|
||||||
|
referenceImageSource: isSkuReplacementModeBuilder
|
||||||
|
? (value as AgentTaskDetailReferenceSource)
|
||||||
|
: prev.sku.referenceImageSource,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger
|
||||||
|
className={cn(
|
||||||
|
isDark
|
||||||
|
? "border-white/10 bg-black/30 text-white focus:ring-emerald-400/35"
|
||||||
|
: ""
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent className={darkSelectContentClass}>
|
||||||
|
{TASK_DETAIL_REFERENCE_SOURCE_OPTIONS.map((option) => (
|
||||||
|
<SelectItem
|
||||||
|
key={option.value}
|
||||||
|
value={option.value}
|
||||||
|
className={cn(isDark ? "text-white focus:bg-white/10 focus:text-white" : "")}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<div className={cn("text-xs leading-5", isDark ? "text-white/42" : "text-muted-foreground")}>
|
||||||
|
{TASK_DETAIL_REFERENCE_SOURCE_OPTIONS.find(
|
||||||
|
(option) =>
|
||||||
|
option.value ===
|
||||||
|
(isDetailReplacementModeBuilder
|
||||||
|
? modeConfig.detail.referenceImageSource
|
||||||
|
: modeConfig.sku.referenceImageSource)
|
||||||
|
)?.description || "根据模式配置自动获取对应参考图"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between rounded-xl border border-white/10 bg-white/[0.02] px-3 py-2.5">
|
<div className="flex items-center justify-between rounded-xl border border-white/10 bg-white/[0.02] px-3 py-2.5">
|
||||||
<div>
|
<div>
|
||||||
<div className={cn("text-sm font-medium", isDark ? "text-white/88" : "")}>模特图槽位</div>
|
<div className={cn("text-sm font-medium", isDark ? "text-white/88" : "")}>模特图槽位</div>
|
||||||
@ -1284,8 +1370,8 @@ export function AgentPresetDialog({
|
|||||||
|
|
||||||
<div className={cn("rounded-xl border border-dashed px-3 py-3 text-xs", isDark ? "border-white/10 text-white/45" : "text-muted-foreground")}>
|
<div className={cn("rounded-xl border border-dashed px-3 py-3 text-xs", isDark ? "border-white/10 text-white/45" : "text-muted-foreground")}>
|
||||||
{isDetailReplacementModeBuilder
|
{isDetailReplacementModeBuilder
|
||||||
? "详情替换模式不需要上传模板参考图,第二步会通过 SPU 获取详情参考图,再把输入图逐张替换到详情参考图中。"
|
? `详情替换模式不需要上传模板参考图,第二步会通过 SPU 获取${TASK_DETAIL_REFERENCE_SOURCE_OPTIONS.find((option) => option.value === modeConfig.detail.referenceImageSource)?.label || "详情图"},再把输入图逐张替换到对应参考图中。`
|
||||||
: "SKU替换模式会把用户选中的白底图与参考图建立一对一关系,后续在第二步专用工作台中执行替换。"}
|
: `SKU替换模式会把用户选中的白底图与${TASK_DETAIL_REFERENCE_SOURCE_OPTIONS.find((option) => option.value === modeConfig.sku.referenceImageSource)?.label || "SKU图"}建立一对一关系,后续在第二步专用工作台中执行替换。`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
AgentPresetModeConfig,
|
AgentPresetModeConfig,
|
||||||
AgentPresetModeType,
|
AgentPresetModeType,
|
||||||
|
AgentTaskDetailReferenceSource,
|
||||||
} from "@/types/agent";
|
} from "@/types/agent";
|
||||||
|
|
||||||
export const defaultAgentSceneGenerationModeConfig = {
|
export const defaultAgentSceneGenerationModeConfig = {
|
||||||
@ -14,12 +15,14 @@ export const defaultAgentDetailReplacementModeConfig = {
|
|||||||
enableTextEditing: true,
|
enableTextEditing: true,
|
||||||
productImageSlotCount: 2,
|
productImageSlotCount: 2,
|
||||||
includeModelSlot: true,
|
includeModelSlot: true,
|
||||||
|
referenceImageSource: "detail",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const defaultAgentSkuReplacementModeConfig = {
|
export const defaultAgentSkuReplacementModeConfig = {
|
||||||
enableTextEditing: true,
|
enableTextEditing: true,
|
||||||
productImageSlotCount: 2,
|
productImageSlotCount: 2,
|
||||||
includeModelSlot: true,
|
includeModelSlot: true,
|
||||||
|
referenceImageSource: "sku",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const defaultAgentPresetModeConfig: AgentPresetModeConfig = {
|
export const defaultAgentPresetModeConfig: AgentPresetModeConfig = {
|
||||||
@ -39,6 +42,13 @@ function normalizeInteger(value: unknown, fallback: number, min: number, max: nu
|
|||||||
return Math.min(max, Math.max(min, Math.round(normalized)));
|
return Math.min(max, Math.max(min, Math.round(normalized)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeReferenceSource(
|
||||||
|
value: unknown,
|
||||||
|
fallback: AgentTaskDetailReferenceSource
|
||||||
|
): AgentTaskDetailReferenceSource {
|
||||||
|
return value === "main" || value === "sku" || value === "detail" ? value : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
export function normalizeAgentPresetModeConfig(value: unknown): AgentPresetModeConfig {
|
export function normalizeAgentPresetModeConfig(value: unknown): AgentPresetModeConfig {
|
||||||
const raw =
|
const raw =
|
||||||
value && typeof value === "object" && !Array.isArray(value)
|
value && typeof value === "object" && !Array.isArray(value)
|
||||||
@ -108,6 +118,10 @@ export function normalizeAgentPresetModeConfig(value: unknown): AgentPresetModeC
|
|||||||
detailRaw.includeModelSlot,
|
detailRaw.includeModelSlot,
|
||||||
defaultAgentDetailReplacementModeConfig.includeModelSlot
|
defaultAgentDetailReplacementModeConfig.includeModelSlot
|
||||||
),
|
),
|
||||||
|
referenceImageSource: normalizeReferenceSource(
|
||||||
|
detailRaw.referenceImageSource,
|
||||||
|
defaultAgentDetailReplacementModeConfig.referenceImageSource
|
||||||
|
),
|
||||||
},
|
},
|
||||||
sku: {
|
sku: {
|
||||||
enableTextEditing: normalizeBoolean(
|
enableTextEditing: normalizeBoolean(
|
||||||
@ -124,6 +138,10 @@ export function normalizeAgentPresetModeConfig(value: unknown): AgentPresetModeC
|
|||||||
skuRaw.includeModelSlot,
|
skuRaw.includeModelSlot,
|
||||||
defaultAgentSkuReplacementModeConfig.includeModelSlot
|
defaultAgentSkuReplacementModeConfig.includeModelSlot
|
||||||
),
|
),
|
||||||
|
referenceImageSource: normalizeReferenceSource(
|
||||||
|
skuRaw.referenceImageSource,
|
||||||
|
defaultAgentSkuReplacementModeConfig.referenceImageSource
|
||||||
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,8 @@ export type AgentPresetModeType =
|
|||||||
| "detail_replacement"
|
| "detail_replacement"
|
||||||
| "sku_replacement";
|
| "sku_replacement";
|
||||||
|
|
||||||
|
export type AgentTaskDetailReferenceSource = "main" | "sku" | "detail";
|
||||||
|
|
||||||
export interface AgentSceneGenerationModeConfig {
|
export interface AgentSceneGenerationModeConfig {
|
||||||
productImageSlotCount: number;
|
productImageSlotCount: number;
|
||||||
includeModelSlot: boolean;
|
includeModelSlot: boolean;
|
||||||
@ -19,12 +21,14 @@ export interface AgentDetailReplacementModeConfig {
|
|||||||
enableTextEditing: boolean;
|
enableTextEditing: boolean;
|
||||||
productImageSlotCount: number;
|
productImageSlotCount: number;
|
||||||
includeModelSlot: boolean;
|
includeModelSlot: boolean;
|
||||||
|
referenceImageSource: AgentTaskDetailReferenceSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentSkuReplacementModeConfig {
|
export interface AgentSkuReplacementModeConfig {
|
||||||
enableTextEditing: boolean;
|
enableTextEditing: boolean;
|
||||||
productImageSlotCount: number;
|
productImageSlotCount: number;
|
||||||
includeModelSlot: boolean;
|
includeModelSlot: boolean;
|
||||||
|
referenceImageSource: AgentTaskDetailReferenceSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentPresetModeConfig {
|
export interface AgentPresetModeConfig {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user