feat: extend workflow brand hierarchy to five levels
This commit is contained in:
parent
7eda4720d3
commit
ea6a9a9d76
@ -92,11 +92,13 @@ import type {
|
||||
AgentPresetLibrarySection,
|
||||
AgentWorkflowBrandModelConfig,
|
||||
AgentWorkflowBrandStoreEntry,
|
||||
AgentWorkflowFourthCategoryEntry,
|
||||
AgentWorkflowGoodsInfoApiConfig,
|
||||
AgentWorkflowMaterialApiConfig,
|
||||
AgentWorkflowOptionUiConfig,
|
||||
AgentPsdTextColorConfig,
|
||||
AgentWorkflowStoreEntry,
|
||||
AgentWorkflowThirdCategoryEntry,
|
||||
AgentWorkflowShowcaseConfig,
|
||||
AgentWorkflowStepUiConfig,
|
||||
AgentWorkflowUiConfig,
|
||||
@ -465,15 +467,86 @@ function buildWorkflowInputOptionsFromBrandEntries(
|
||||
return;
|
||||
}
|
||||
|
||||
const uniqueThirdCategoryEntries = new Map<string, AgentWorkflowThirdCategoryEntry>();
|
||||
|
||||
(storeEntry.thirdCategoryEntries || []).forEach((thirdEntry, thirdIndex) => {
|
||||
const categoryName = String(thirdEntry.categoryName || "").trim();
|
||||
if (!categoryName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uniqueFourthCategoryEntries = new Map<string, AgentWorkflowFourthCategoryEntry>();
|
||||
(thirdEntry.fourthCategoryEntries || []).forEach((fourthEntry, fourthIndex) => {
|
||||
const fourthCategoryName = String(fourthEntry.categoryName || "").trim();
|
||||
if (!fourthCategoryName) {
|
||||
return;
|
||||
}
|
||||
|
||||
uniqueFourthCategoryEntries.set(fourthCategoryName.toLowerCase(), {
|
||||
categoryName: fourthCategoryName,
|
||||
fifthCategories: Array.from(
|
||||
new Set(
|
||||
(fourthEntry.fifthCategories || [])
|
||||
.map((fifthCategory) => fifthCategory.trim())
|
||||
.filter(Boolean)
|
||||
)
|
||||
),
|
||||
sortOrder: fourthIndex,
|
||||
});
|
||||
});
|
||||
|
||||
const fourthCategoryEntries = Array.from(uniqueFourthCategoryEntries.values()).map(
|
||||
(fourthEntry, fourthIndex) => ({
|
||||
...fourthEntry,
|
||||
sortOrder: fourthIndex,
|
||||
})
|
||||
);
|
||||
|
||||
uniqueThirdCategoryEntries.set(categoryName.toLowerCase(), {
|
||||
categoryName,
|
||||
fourthCategories:
|
||||
fourthCategoryEntries.length > 0
|
||||
? fourthCategoryEntries.map((fourthEntry) => fourthEntry.categoryName)
|
||||
: Array.from(
|
||||
new Set(
|
||||
(thirdEntry.fourthCategories || [])
|
||||
.map((fourthCategory) => fourthCategory.trim())
|
||||
.filter(Boolean)
|
||||
)
|
||||
),
|
||||
fourthCategoryEntries,
|
||||
sortOrder: thirdIndex,
|
||||
});
|
||||
});
|
||||
|
||||
const thirdCategoryEntries = Array.from(uniqueThirdCategoryEntries.values()).map((thirdEntry, thirdIndex) => ({
|
||||
...thirdEntry,
|
||||
fourthCategories:
|
||||
(thirdEntry.fourthCategoryEntries || []).length > 0
|
||||
? (thirdEntry.fourthCategoryEntries || []).map((fourthEntry) => fourthEntry.categoryName)
|
||||
: Array.from(
|
||||
new Set(
|
||||
(thirdEntry.fourthCategories || [])
|
||||
.map((fourthCategory) => fourthCategory.trim())
|
||||
.filter(Boolean)
|
||||
)
|
||||
),
|
||||
sortOrder: thirdIndex,
|
||||
}));
|
||||
|
||||
uniqueStoreEntries.set(storeName.toLowerCase(), {
|
||||
storeName,
|
||||
thirdCategories: Array.from(
|
||||
new Set(
|
||||
(storeEntry.thirdCategories || [])
|
||||
.map((thirdCategory) => thirdCategory.trim())
|
||||
.filter(Boolean)
|
||||
)
|
||||
),
|
||||
thirdCategories:
|
||||
thirdCategoryEntries.length > 0
|
||||
? thirdCategoryEntries.map((thirdEntry) => thirdEntry.categoryName)
|
||||
: Array.from(
|
||||
new Set(
|
||||
(storeEntry.thirdCategories || [])
|
||||
.map((thirdCategory) => thirdCategory.trim())
|
||||
.filter(Boolean)
|
||||
)
|
||||
),
|
||||
thirdCategoryEntries,
|
||||
sortOrder: storeIndex,
|
||||
});
|
||||
});
|
||||
@ -513,10 +586,51 @@ function buildWorkflowInputOptionsFromBrandEntries(
|
||||
)
|
||||
)
|
||||
),
|
||||
fourthCategories: Array.from(
|
||||
new Set(
|
||||
normalizedEntries.flatMap((entry) =>
|
||||
entry.storeEntries.flatMap((storeEntry) =>
|
||||
(storeEntry.thirdCategoryEntries || []).flatMap(
|
||||
(thirdEntry) => thirdEntry.fourthCategories || []
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
fifthCategories: Array.from(
|
||||
new Set(
|
||||
normalizedEntries.flatMap((entry) =>
|
||||
entry.storeEntries.flatMap((storeEntry) =>
|
||||
(storeEntry.thirdCategoryEntries || []).flatMap((thirdEntry) =>
|
||||
(thirdEntry.fourthCategoryEntries || []).flatMap(
|
||||
(fourthEntry) => fourthEntry.fifthCategories || []
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
priceBands: normalizedPriceBands,
|
||||
};
|
||||
}
|
||||
|
||||
function createStoreDraftKey(brandName: string, storeName: string) {
|
||||
return `${brandName}::${storeName}`;
|
||||
}
|
||||
|
||||
function createThirdCategoryDraftKey(brandName: string, storeName: string, thirdCategory: string) {
|
||||
return `${brandName}::${storeName}::${thirdCategory}`;
|
||||
}
|
||||
|
||||
function createFourthCategoryDraftKey(
|
||||
brandName: string,
|
||||
storeName: string,
|
||||
thirdCategory: string,
|
||||
fourthCategory: string
|
||||
) {
|
||||
return `${brandName}::${storeName}::${thirdCategory}::${fourthCategory}`;
|
||||
}
|
||||
|
||||
interface AgentWorkflowSettingsClientProps {
|
||||
backHref?: string;
|
||||
backLabel?: string;
|
||||
@ -573,6 +687,8 @@ export function AgentWorkflowSettingsClient({
|
||||
const [priceBandDraft, setPriceBandDraft] = useState("");
|
||||
const [storeDrafts, setStoreDrafts] = useState<Record<string, string>>({});
|
||||
const [thirdCategoryDrafts, setThirdCategoryDrafts] = useState<Record<string, string>>({});
|
||||
const [fourthCategoryDrafts, setFourthCategoryDrafts] = useState<Record<string, string>>({});
|
||||
const [fifthCategoryDrafts, setFifthCategoryDrafts] = useState<Record<string, string>>({});
|
||||
const [blockedOptionSearches, setBlockedOptionSearches] = useState<Record<string, string>>({});
|
||||
const fileInputRefs = useRef<Record<string, HTMLInputElement | null>>({});
|
||||
|
||||
@ -717,6 +833,24 @@ export function AgentWorkflowSettingsClient({
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setFourthCategoryDrafts((prev) => {
|
||||
const next = { ...prev };
|
||||
Object.keys(next).forEach((key) => {
|
||||
if (key.startsWith(`${brandName}::`)) {
|
||||
delete next[key];
|
||||
}
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setFifthCategoryDrafts((prev) => {
|
||||
const next = { ...prev };
|
||||
Object.keys(next).forEach((key) => {
|
||||
if (key.startsWith(`${brandName}::`)) {
|
||||
delete next[key];
|
||||
}
|
||||
});
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddStoreToBrand = (brandName: string) => {
|
||||
@ -752,6 +886,7 @@ export function AgentWorkflowSettingsClient({
|
||||
{
|
||||
storeName: nextValue,
|
||||
thirdCategories: [],
|
||||
thirdCategoryEntries: [],
|
||||
sortOrder: (entry.storeEntries || []).length,
|
||||
},
|
||||
],
|
||||
@ -780,13 +915,35 @@ export function AgentWorkflowSettingsClient({
|
||||
);
|
||||
setThirdCategoryDrafts((prev) => {
|
||||
const next = { ...prev };
|
||||
delete next[`${brandName}::${storeName}`];
|
||||
Object.keys(next).forEach((key) => {
|
||||
if (key.startsWith(`${brandName}::${storeName}`)) {
|
||||
delete next[key];
|
||||
}
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setFourthCategoryDrafts((prev) => {
|
||||
const next = { ...prev };
|
||||
Object.keys(next).forEach((key) => {
|
||||
if (key.startsWith(`${brandName}::${storeName}`)) {
|
||||
delete next[key];
|
||||
}
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setFifthCategoryDrafts((prev) => {
|
||||
const next = { ...prev };
|
||||
Object.keys(next).forEach((key) => {
|
||||
if (key.startsWith(`${brandName}::${storeName}`)) {
|
||||
delete next[key];
|
||||
}
|
||||
});
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddThirdCategoryToStore = (brandName: string, storeName: string) => {
|
||||
const draftKey = `${brandName}::${storeName}`;
|
||||
const draftKey = createStoreDraftKey(brandName, storeName);
|
||||
const nextValue = normalizeInputOptionValue(thirdCategoryDrafts[draftKey] || "");
|
||||
if (!nextValue) {
|
||||
toast.error("请输入三级分类名称");
|
||||
@ -798,6 +955,9 @@ export function AgentWorkflowSettingsClient({
|
||||
(storeEntry) => storeEntry.storeName === storeName
|
||||
);
|
||||
if (
|
||||
(targetStoreEntry?.thirdCategoryEntries || []).some(
|
||||
(thirdEntry) => thirdEntry.categoryName.toLowerCase() === nextValue.toLowerCase()
|
||||
) ||
|
||||
targetStoreEntry?.thirdCategories.some(
|
||||
(thirdCategory) => thirdCategory.toLowerCase() === nextValue.toLowerCase()
|
||||
)
|
||||
@ -816,6 +976,18 @@ export function AgentWorkflowSettingsClient({
|
||||
? {
|
||||
...storeEntry,
|
||||
thirdCategories: [...(storeEntry.thirdCategories || []), nextValue],
|
||||
thirdCategoryEntries: [
|
||||
...((storeEntry.thirdCategoryEntries || []).map((thirdEntry, thirdIndex) => ({
|
||||
...thirdEntry,
|
||||
sortOrder: thirdIndex,
|
||||
})) || []),
|
||||
{
|
||||
categoryName: nextValue,
|
||||
fourthCategories: [],
|
||||
fourthCategoryEntries: [],
|
||||
sortOrder: (storeEntry.thirdCategoryEntries || []).length,
|
||||
},
|
||||
],
|
||||
}
|
||||
: storeEntry
|
||||
),
|
||||
@ -845,6 +1017,259 @@ export function AgentWorkflowSettingsClient({
|
||||
thirdCategories: (storeEntry.thirdCategories || []).filter(
|
||||
(item) => item !== thirdCategory
|
||||
),
|
||||
thirdCategoryEntries: (storeEntry.thirdCategoryEntries || []).filter(
|
||||
(item) => item.categoryName !== thirdCategory
|
||||
),
|
||||
}
|
||||
: storeEntry
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
setFourthCategoryDrafts((prev) => {
|
||||
const next = { ...prev };
|
||||
Object.keys(next).forEach((key) => {
|
||||
if (key.startsWith(`${brandName}::${storeName}::${thirdCategory}`)) {
|
||||
delete next[key];
|
||||
}
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setFifthCategoryDrafts((prev) => {
|
||||
const next = { ...prev };
|
||||
Object.keys(next).forEach((key) => {
|
||||
if (key.startsWith(`${brandName}::${storeName}::${thirdCategory}`)) {
|
||||
delete next[key];
|
||||
}
|
||||
});
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddFourthCategoryToThirdCategory = (
|
||||
brandName: string,
|
||||
storeName: string,
|
||||
thirdCategory: string
|
||||
) => {
|
||||
const draftKey = createThirdCategoryDraftKey(brandName, storeName, thirdCategory);
|
||||
const nextValue = normalizeInputOptionValue(fourthCategoryDrafts[draftKey] || "");
|
||||
if (!nextValue) {
|
||||
toast.error("请输入四级分类名称");
|
||||
return;
|
||||
}
|
||||
|
||||
const targetEntry = brandStoreEntries.find((entry) => entry.brandName === brandName);
|
||||
const targetStoreEntry = targetEntry?.storeEntries?.find(
|
||||
(storeEntry) => storeEntry.storeName === storeName
|
||||
);
|
||||
const targetThirdEntry = targetStoreEntry?.thirdCategoryEntries?.find(
|
||||
(thirdEntry) => thirdEntry.categoryName === thirdCategory
|
||||
);
|
||||
if (
|
||||
(targetThirdEntry?.fourthCategoryEntries || []).some(
|
||||
(fourthEntry) => fourthEntry.categoryName.toLowerCase() === nextValue.toLowerCase()
|
||||
) ||
|
||||
(targetThirdEntry?.fourthCategories || []).some(
|
||||
(fourthCategory) => fourthCategory.toLowerCase() === nextValue.toLowerCase()
|
||||
)
|
||||
) {
|
||||
toast.error("该四级分类已存在");
|
||||
return;
|
||||
}
|
||||
|
||||
updateInputOptions(
|
||||
brandStoreEntries.map((entry) => {
|
||||
if (entry.brandName !== brandName) return entry;
|
||||
return {
|
||||
...entry,
|
||||
storeEntries: (entry.storeEntries || []).map((storeEntry) =>
|
||||
storeEntry.storeName === storeName
|
||||
? {
|
||||
...storeEntry,
|
||||
thirdCategoryEntries: (storeEntry.thirdCategoryEntries || []).map((thirdEntry) =>
|
||||
thirdEntry.categoryName === thirdCategory
|
||||
? {
|
||||
...thirdEntry,
|
||||
fourthCategories: [...(thirdEntry.fourthCategories || []), nextValue],
|
||||
fourthCategoryEntries: [
|
||||
...((thirdEntry.fourthCategoryEntries || []).map((fourthEntry, fourthIndex) => ({
|
||||
...fourthEntry,
|
||||
sortOrder: fourthIndex,
|
||||
})) || []),
|
||||
{
|
||||
categoryName: nextValue,
|
||||
fifthCategories: [],
|
||||
sortOrder: (thirdEntry.fourthCategoryEntries || []).length,
|
||||
},
|
||||
],
|
||||
}
|
||||
: thirdEntry
|
||||
),
|
||||
}
|
||||
: storeEntry
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
setFourthCategoryDrafts((prev) => ({
|
||||
...prev,
|
||||
[draftKey]: "",
|
||||
}));
|
||||
};
|
||||
|
||||
const handleRemoveFourthCategoryFromThirdCategory = (
|
||||
brandName: string,
|
||||
storeName: string,
|
||||
thirdCategory: string,
|
||||
fourthCategory: string
|
||||
) => {
|
||||
updateInputOptions(
|
||||
brandStoreEntries.map((entry) => {
|
||||
if (entry.brandName !== brandName) return entry;
|
||||
return {
|
||||
...entry,
|
||||
storeEntries: (entry.storeEntries || []).map((storeEntry) =>
|
||||
storeEntry.storeName === storeName
|
||||
? {
|
||||
...storeEntry,
|
||||
thirdCategoryEntries: (storeEntry.thirdCategoryEntries || []).map((thirdEntry) =>
|
||||
thirdEntry.categoryName === thirdCategory
|
||||
? {
|
||||
...thirdEntry,
|
||||
fourthCategories: (thirdEntry.fourthCategories || []).filter(
|
||||
(item) => item !== fourthCategory
|
||||
),
|
||||
fourthCategoryEntries: (thirdEntry.fourthCategoryEntries || []).filter(
|
||||
(item) => item.categoryName !== fourthCategory
|
||||
),
|
||||
}
|
||||
: thirdEntry
|
||||
),
|
||||
}
|
||||
: storeEntry
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
setFifthCategoryDrafts((prev) => {
|
||||
const next = { ...prev };
|
||||
Object.keys(next).forEach((key) => {
|
||||
if (key.startsWith(`${brandName}::${storeName}::${thirdCategory}::${fourthCategory}`)) {
|
||||
delete next[key];
|
||||
}
|
||||
});
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddFifthCategoryToFourthCategory = (
|
||||
brandName: string,
|
||||
storeName: string,
|
||||
thirdCategory: string,
|
||||
fourthCategory: string
|
||||
) => {
|
||||
const draftKey = createFourthCategoryDraftKey(
|
||||
brandName,
|
||||
storeName,
|
||||
thirdCategory,
|
||||
fourthCategory
|
||||
);
|
||||
const nextValue = normalizeInputOptionValue(fifthCategoryDrafts[draftKey] || "");
|
||||
if (!nextValue) {
|
||||
toast.error("请输入五级分类名称");
|
||||
return;
|
||||
}
|
||||
|
||||
const targetEntry = brandStoreEntries.find((entry) => entry.brandName === brandName);
|
||||
const targetStoreEntry = targetEntry?.storeEntries?.find(
|
||||
(storeEntry) => storeEntry.storeName === storeName
|
||||
);
|
||||
const targetThirdEntry = targetStoreEntry?.thirdCategoryEntries?.find(
|
||||
(thirdEntry) => thirdEntry.categoryName === thirdCategory
|
||||
);
|
||||
const targetFourthEntry = targetThirdEntry?.fourthCategoryEntries?.find(
|
||||
(fourthEntry) => fourthEntry.categoryName === fourthCategory
|
||||
);
|
||||
if (
|
||||
targetFourthEntry?.fifthCategories.some(
|
||||
(fifthCategory) => fifthCategory.toLowerCase() === nextValue.toLowerCase()
|
||||
)
|
||||
) {
|
||||
toast.error("该五级分类已存在");
|
||||
return;
|
||||
}
|
||||
|
||||
updateInputOptions(
|
||||
brandStoreEntries.map((entry) => {
|
||||
if (entry.brandName !== brandName) return entry;
|
||||
return {
|
||||
...entry,
|
||||
storeEntries: (entry.storeEntries || []).map((storeEntry) =>
|
||||
storeEntry.storeName === storeName
|
||||
? {
|
||||
...storeEntry,
|
||||
thirdCategoryEntries: (storeEntry.thirdCategoryEntries || []).map((thirdEntry) =>
|
||||
thirdEntry.categoryName === thirdCategory
|
||||
? {
|
||||
...thirdEntry,
|
||||
fourthCategoryEntries: (thirdEntry.fourthCategoryEntries || []).map(
|
||||
(fourthEntry) =>
|
||||
fourthEntry.categoryName === fourthCategory
|
||||
? {
|
||||
...fourthEntry,
|
||||
fifthCategories: [...(fourthEntry.fifthCategories || []), nextValue],
|
||||
}
|
||||
: fourthEntry
|
||||
),
|
||||
}
|
||||
: thirdEntry
|
||||
),
|
||||
}
|
||||
: storeEntry
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
setFifthCategoryDrafts((prev) => ({
|
||||
...prev,
|
||||
[draftKey]: "",
|
||||
}));
|
||||
};
|
||||
|
||||
const handleRemoveFifthCategoryFromFourthCategory = (
|
||||
brandName: string,
|
||||
storeName: string,
|
||||
thirdCategory: string,
|
||||
fourthCategory: string,
|
||||
fifthCategory: string
|
||||
) => {
|
||||
updateInputOptions(
|
||||
brandStoreEntries.map((entry) => {
|
||||
if (entry.brandName !== brandName) return entry;
|
||||
return {
|
||||
...entry,
|
||||
storeEntries: (entry.storeEntries || []).map((storeEntry) =>
|
||||
storeEntry.storeName === storeName
|
||||
? {
|
||||
...storeEntry,
|
||||
thirdCategoryEntries: (storeEntry.thirdCategoryEntries || []).map((thirdEntry) =>
|
||||
thirdEntry.categoryName === thirdCategory
|
||||
? {
|
||||
...thirdEntry,
|
||||
fourthCategoryEntries: (thirdEntry.fourthCategoryEntries || []).map(
|
||||
(fourthEntry) =>
|
||||
fourthEntry.categoryName === fourthCategory
|
||||
? {
|
||||
...fourthEntry,
|
||||
fifthCategories: (fourthEntry.fifthCategories || []).filter(
|
||||
(item) => item !== fifthCategory
|
||||
),
|
||||
}
|
||||
: fourthEntry
|
||||
),
|
||||
}
|
||||
: thirdEntry
|
||||
),
|
||||
}
|
||||
: storeEntry
|
||||
),
|
||||
@ -2726,7 +3151,7 @@ export function AgentWorkflowSettingsClient({
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-base">基础下拉选项</CardTitle>
|
||||
<CardDescription>配置品牌、店铺、三级分类的层级关系,以及第一页可选的价格带区间</CardDescription>
|
||||
<CardDescription>配置品牌、店铺到五级分类的层级关系,以及第一页可选的价格带区间</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
@ -2841,7 +3266,7 @@ export function AgentWorkflowSettingsClient({
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{(storeEntry.thirdCategories || []).length} 个三级分类
|
||||
{(storeEntry.thirdCategoryEntries || []).length} 个三级分类
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -2883,32 +3308,214 @@ export function AgentWorkflowSettingsClient({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{(storeEntry.thirdCategories || []).length > 0 ? (
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{(storeEntry.thirdCategories || []).map(
|
||||
(thirdCategory) => (
|
||||
{(storeEntry.thirdCategoryEntries || []).length > 0 ? (
|
||||
<div className="mt-3 space-y-3">
|
||||
{(storeEntry.thirdCategoryEntries || []).map((thirdEntry) => {
|
||||
const fourthDraftKey = createThirdCategoryDraftKey(
|
||||
entry.brandName,
|
||||
storeEntry.storeName,
|
||||
thirdEntry.categoryName
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={`${entry.brandName}-${storeEntry.storeName}-${thirdCategory}`}
|
||||
className="inline-flex items-center gap-2 rounded-full border bg-background px-3 py-1.5 text-sm"
|
||||
key={`${entry.brandName}-${storeEntry.storeName}-${thirdEntry.categoryName}`}
|
||||
className="rounded-xl border bg-background/80 p-3"
|
||||
>
|
||||
<span>{thirdCategory}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleRemoveThirdCategoryFromStore(
|
||||
entry.brandName,
|
||||
storeEntry.storeName,
|
||||
thirdCategory
|
||||
)
|
||||
}
|
||||
className="rounded-full p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-destructive"
|
||||
aria-label={`删除三级分类 ${thirdCategory}`}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="inline-flex items-center gap-2 rounded-full border bg-background px-3 py-1.5 text-sm font-medium">
|
||||
<span>{thirdEntry.categoryName}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleRemoveThirdCategoryFromStore(
|
||||
entry.brandName,
|
||||
storeEntry.storeName,
|
||||
thirdEntry.categoryName
|
||||
)
|
||||
}
|
||||
className="rounded-full p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-destructive"
|
||||
aria-label={`删除三级分类 ${thirdEntry.categoryName}`}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{(thirdEntry.fourthCategoryEntries || []).length} 个四级分类
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 rounded-xl border bg-muted/20 p-3">
|
||||
<div className="flex flex-col gap-2 sm:flex-row">
|
||||
<Input
|
||||
value={fourthCategoryDrafts[fourthDraftKey] || ""}
|
||||
onChange={(e) =>
|
||||
setFourthCategoryDrafts((prev) => ({
|
||||
...prev,
|
||||
[fourthDraftKey]: e.target.value,
|
||||
}))
|
||||
}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
handleAddFourthCategoryToThirdCategory(
|
||||
entry.brandName,
|
||||
storeEntry.storeName,
|
||||
thirdEntry.categoryName
|
||||
);
|
||||
}
|
||||
}}
|
||||
placeholder={`给三级分类“${thirdEntry.categoryName}”添加四级分类`}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleAddFourthCategoryToThirdCategory(
|
||||
entry.brandName,
|
||||
storeEntry.storeName,
|
||||
thirdEntry.categoryName
|
||||
)
|
||||
}
|
||||
className="shrink-0"
|
||||
variant="outline"
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
添加四级分类
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{(thirdEntry.fourthCategoryEntries || []).length > 0 ? (
|
||||
<div className="mt-3 space-y-3">
|
||||
{(thirdEntry.fourthCategoryEntries || []).map(
|
||||
(fourthEntry) => {
|
||||
const fifthDraftKey = createFourthCategoryDraftKey(
|
||||
entry.brandName,
|
||||
storeEntry.storeName,
|
||||
thirdEntry.categoryName,
|
||||
fourthEntry.categoryName
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={`${entry.brandName}-${storeEntry.storeName}-${thirdEntry.categoryName}-${fourthEntry.categoryName}`}
|
||||
className="rounded-xl border bg-background/80 p-3"
|
||||
>
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="inline-flex items-center gap-2 rounded-full border bg-background px-3 py-1.5 text-sm font-medium">
|
||||
<span>{fourthEntry.categoryName}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleRemoveFourthCategoryFromThirdCategory(
|
||||
entry.brandName,
|
||||
storeEntry.storeName,
|
||||
thirdEntry.categoryName,
|
||||
fourthEntry.categoryName
|
||||
)
|
||||
}
|
||||
className="rounded-full p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-destructive"
|
||||
aria-label={`删除四级分类 ${fourthEntry.categoryName}`}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{(fourthEntry.fifthCategories || []).length} 个五级分类
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 rounded-xl border bg-muted/20 p-3">
|
||||
<div className="flex flex-col gap-2 sm:flex-row">
|
||||
<Input
|
||||
value={fifthCategoryDrafts[fifthDraftKey] || ""}
|
||||
onChange={(e) =>
|
||||
setFifthCategoryDrafts((prev) => ({
|
||||
...prev,
|
||||
[fifthDraftKey]: e.target.value,
|
||||
}))
|
||||
}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
handleAddFifthCategoryToFourthCategory(
|
||||
entry.brandName,
|
||||
storeEntry.storeName,
|
||||
thirdEntry.categoryName,
|
||||
fourthEntry.categoryName
|
||||
);
|
||||
}
|
||||
}}
|
||||
placeholder={`给四级分类“${fourthEntry.categoryName}”添加五级分类`}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleAddFifthCategoryToFourthCategory(
|
||||
entry.brandName,
|
||||
storeEntry.storeName,
|
||||
thirdEntry.categoryName,
|
||||
fourthEntry.categoryName
|
||||
)
|
||||
}
|
||||
className="shrink-0"
|
||||
variant="outline"
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
添加五级分类
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{(fourthEntry.fifthCategories || []).length > 0 ? (
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{(fourthEntry.fifthCategories || []).map(
|
||||
(fifthCategory) => (
|
||||
<div
|
||||
key={`${entry.brandName}-${storeEntry.storeName}-${thirdEntry.categoryName}-${fourthEntry.categoryName}-${fifthCategory}`}
|
||||
className="inline-flex items-center gap-2 rounded-full border bg-background px-3 py-1.5 text-sm"
|
||||
>
|
||||
<span>{fifthCategory}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleRemoveFifthCategoryFromFourthCategory(
|
||||
entry.brandName,
|
||||
storeEntry.storeName,
|
||||
thirdEntry.categoryName,
|
||||
fourthEntry.categoryName,
|
||||
fifthCategory
|
||||
)
|
||||
}
|
||||
className="rounded-full p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-destructive"
|
||||
aria-label={`删除五级分类 ${fifthCategory}`}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-3 rounded-lg border border-dashed bg-background/70 px-3 py-4 text-sm text-muted-foreground">
|
||||
这个四级分类下还没有添加任何五级分类。
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-3 rounded-lg border border-dashed bg-background/70 px-3 py-4 text-sm text-muted-foreground">
|
||||
这个三级分类下还没有添加任何四级分类。
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-3 rounded-lg border border-dashed bg-background/70 px-3 py-4 text-sm text-muted-foreground">
|
||||
@ -2931,7 +3538,7 @@ export function AgentWorkflowSettingsClient({
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-3 rounded-lg border border-dashed bg-background/70 px-3 py-4 text-sm text-muted-foreground">
|
||||
还没有添加品牌。先添加品牌,再在每个品牌下继续添加店铺和三级分类。
|
||||
还没有添加品牌。先添加品牌,再在每个品牌下继续添加店铺以及三级到五级分类。
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -731,6 +731,8 @@ const updateSchema = z.object({
|
||||
brands: z.array(z.string().trim().min(1).max(64)).max(300).optional(),
|
||||
stores: z.array(z.string().trim().min(1).max(64)).max(300).optional(),
|
||||
thirdCategories: z.array(z.string().trim().min(1).max(64)).max(300).optional(),
|
||||
fourthCategories: z.array(z.string().trim().min(1).max(64)).max(300).optional(),
|
||||
fifthCategories: z.array(z.string().trim().min(1).max(64)).max(300).optional(),
|
||||
priceBands: z.array(z.string().trim().min(1).max(64)).max(300).optional(),
|
||||
brandStoreEntries: z
|
||||
.array(
|
||||
@ -745,6 +747,32 @@ const updateSchema = z.object({
|
||||
.array(z.string().trim().min(1).max(64))
|
||||
.max(300)
|
||||
.optional(),
|
||||
thirdCategoryEntries: z
|
||||
.array(
|
||||
z.object({
|
||||
categoryName: z.string().trim().min(1).max(64),
|
||||
fourthCategories: z
|
||||
.array(z.string().trim().min(1).max(64))
|
||||
.max(300)
|
||||
.optional(),
|
||||
fourthCategoryEntries: z
|
||||
.array(
|
||||
z.object({
|
||||
categoryName: z.string().trim().min(1).max(64),
|
||||
fifthCategories: z
|
||||
.array(z.string().trim().min(1).max(64))
|
||||
.max(300)
|
||||
.optional(),
|
||||
sortOrder: z.number().min(0).max(999).optional(),
|
||||
})
|
||||
)
|
||||
.max(300)
|
||||
.optional(),
|
||||
sortOrder: z.number().min(0).max(999).optional(),
|
||||
})
|
||||
)
|
||||
.max(300)
|
||||
.optional(),
|
||||
sortOrder: z.number().min(0).max(999).optional(),
|
||||
})
|
||||
)
|
||||
|
||||
@ -153,6 +153,8 @@ function hasMeaningfulWorkflowState(
|
||||
String(inputs?.brand || "").trim() ||
|
||||
String(inputs?.store || "").trim() ||
|
||||
String(inputs?.thirdCategory || "").trim() ||
|
||||
String(inputs?.fourthCategory || "").trim() ||
|
||||
String(inputs?.fifthCategory || "").trim() ||
|
||||
String(inputs?.priceBand || "").trim()
|
||||
);
|
||||
|
||||
@ -322,6 +324,8 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
brand: "",
|
||||
store: "",
|
||||
thirdCategory: "",
|
||||
fourthCategory: "",
|
||||
fifthCategory: "",
|
||||
priceBand: "",
|
||||
});
|
||||
const [workflowMaterials, setWorkflowMaterials] = useState<AgentWorkflowMaterialSelections>(
|
||||
@ -489,6 +493,8 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
brand: workflowInputs.brand.trim(),
|
||||
store: workflowInputs.store.trim(),
|
||||
thirdCategory: workflowInputs.thirdCategory.trim(),
|
||||
fourthCategory: workflowInputs.fourthCategory.trim(),
|
||||
fifthCategory: workflowInputs.fifthCategory.trim(),
|
||||
priceBand: workflowInputs.priceBand.trim(),
|
||||
},
|
||||
materials: workflowMaterials,
|
||||
@ -535,6 +541,8 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
brand: "",
|
||||
store: "",
|
||||
thirdCategory: "",
|
||||
fourthCategory: "",
|
||||
fifthCategory: "",
|
||||
priceBand: "",
|
||||
});
|
||||
setWorkflowMaterials(createEmptyAgentWorkflowMaterialSelections());
|
||||
@ -567,6 +575,8 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
brand: String(persistedWorkflow.inputs?.brand || "").trim(),
|
||||
store: String(persistedWorkflow.inputs?.store || "").trim(),
|
||||
thirdCategory: String(persistedWorkflow.inputs?.thirdCategory || "").trim(),
|
||||
fourthCategory: String(persistedWorkflow.inputs?.fourthCategory || "").trim(),
|
||||
fifthCategory: String(persistedWorkflow.inputs?.fifthCategory || "").trim(),
|
||||
priceBand: String(persistedWorkflow.inputs?.priceBand || "").trim(),
|
||||
});
|
||||
setWorkflowMaterials(
|
||||
@ -676,6 +686,8 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
brand: workflowInputs.brand.trim(),
|
||||
store: workflowInputs.store.trim(),
|
||||
thirdCategory: workflowInputs.thirdCategory.trim(),
|
||||
fourthCategory: workflowInputs.fourthCategory.trim(),
|
||||
fifthCategory: workflowInputs.fifthCategory.trim(),
|
||||
priceBand: workflowInputs.priceBand.trim(),
|
||||
},
|
||||
materials: workflowMaterials,
|
||||
@ -763,6 +775,8 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
brand: String(parsed.inputs?.brand || "").trim(),
|
||||
store: String(parsed.inputs?.store || "").trim(),
|
||||
thirdCategory: String(parsed.inputs?.thirdCategory || "").trim(),
|
||||
fourthCategory: String(parsed.inputs?.fourthCategory || "").trim(),
|
||||
fifthCategory: String(parsed.inputs?.fifthCategory || "").trim(),
|
||||
priceBand: String(parsed.inputs?.priceBand || "").trim(),
|
||||
});
|
||||
setWorkflowMaterials(normalizeAgentWorkflowMaterialSelections(parsed.materials));
|
||||
@ -890,6 +904,8 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
brand: String(persistedWorkflow.inputs?.brand || "").trim(),
|
||||
store: String(persistedWorkflow.inputs?.store || "").trim(),
|
||||
thirdCategory: String(persistedWorkflow.inputs?.thirdCategory || "").trim(),
|
||||
fourthCategory: String(persistedWorkflow.inputs?.fourthCategory || "").trim(),
|
||||
fifthCategory: String(persistedWorkflow.inputs?.fifthCategory || "").trim(),
|
||||
priceBand: String(persistedWorkflow.inputs?.priceBand || "").trim(),
|
||||
});
|
||||
setWorkflowMaterials(normalizeAgentWorkflowMaterialSelections(persistedWorkflow.materials));
|
||||
|
||||
@ -9,10 +9,10 @@ import {
|
||||
createEmptyAgentWorkflowMaterialSelections,
|
||||
normalizeAgentWorkflowMaterialSelections,
|
||||
} from "@/lib/agents/workflow-material-config";
|
||||
import { buildAgentWorkflowComposerContext } from "@/lib/agents/workflow-composer";
|
||||
import type {
|
||||
AgentWorkflowMaterialSelections,
|
||||
AgentWorkflowStepKey,
|
||||
AgentWorkflowComposerContext,
|
||||
} from "@/types/agent";
|
||||
|
||||
interface AgentPageDarkProps {
|
||||
@ -38,6 +38,8 @@ export function AgentPageDark({
|
||||
brand: "",
|
||||
store: "",
|
||||
thirdCategory: "",
|
||||
fourthCategory: "",
|
||||
fifthCategory: "",
|
||||
priceBand: "",
|
||||
});
|
||||
const [workflowMaterials, setWorkflowMaterials] = useState<AgentWorkflowMaterialSelections>(
|
||||
@ -56,24 +58,12 @@ export function AgentPageDark({
|
||||
};
|
||||
|
||||
// Build workflow context
|
||||
const workflowContext: AgentWorkflowComposerContext | null = {
|
||||
const workflowContext = buildAgentWorkflowComposerContext({
|
||||
config: workflowConfig,
|
||||
inputs: workflowInputs,
|
||||
materials: workflowMaterials,
|
||||
selections: Object.entries(workflowSelections).map(([stepKey, optionKey]) => {
|
||||
const step = workflowConfig?.steps?.find((s: any) => s.key === stepKey);
|
||||
const option = step?.options?.find((o: any) => o.key === optionKey);
|
||||
return {
|
||||
stepKey,
|
||||
stepTitle: step?.title || "",
|
||||
optionKey: optionKey || "",
|
||||
optionName: option?.buttonName || "",
|
||||
promptFragment: option?.promptFragment || "",
|
||||
};
|
||||
}),
|
||||
referenceImages: [],
|
||||
summaryText: "",
|
||||
promptText: "",
|
||||
};
|
||||
selections: workflowSelections,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="bg-black min-h-screen">
|
||||
|
||||
@ -11,6 +11,8 @@ import { cn } from "@/lib/utils";
|
||||
import {
|
||||
defaultAgentWorkflowUiConfig,
|
||||
getAgentWorkflowBrandStoreEntries,
|
||||
getAgentWorkflowFifthCategoryOptionsByBrandStoreThirdFourth,
|
||||
getAgentWorkflowFourthCategoryOptionsByBrandStoreThirdCategory,
|
||||
getAgentWorkflowPriceBandOptions,
|
||||
getAgentWorkflowThirdCategoryOptionsByBrandStore,
|
||||
getAgentWorkflowStoreOptionsByBrand,
|
||||
@ -33,6 +35,8 @@ export interface AgentWorkflowInputs {
|
||||
brand: string;
|
||||
store: string;
|
||||
thirdCategory: string;
|
||||
fourthCategory: string;
|
||||
fifthCategory: string;
|
||||
priceBand: string;
|
||||
}
|
||||
|
||||
@ -109,6 +113,27 @@ export function AgentWorkflowPanel({
|
||||
}),
|
||||
[config, inputs.brand, inputs.store]
|
||||
);
|
||||
const fourthCategoryOptions = useMemo(
|
||||
() =>
|
||||
getAgentWorkflowFourthCategoryOptionsByBrandStoreThirdCategory({
|
||||
config,
|
||||
brand: inputs.brand,
|
||||
store: inputs.store,
|
||||
thirdCategory: inputs.thirdCategory,
|
||||
}),
|
||||
[config, inputs.brand, inputs.store, inputs.thirdCategory]
|
||||
);
|
||||
const fifthCategoryOptions = useMemo(
|
||||
() =>
|
||||
getAgentWorkflowFifthCategoryOptionsByBrandStoreThirdFourth({
|
||||
config,
|
||||
brand: inputs.brand,
|
||||
store: inputs.store,
|
||||
thirdCategory: inputs.thirdCategory,
|
||||
fourthCategory: inputs.fourthCategory,
|
||||
}),
|
||||
[config, inputs.brand, inputs.store, inputs.thirdCategory, inputs.fourthCategory]
|
||||
);
|
||||
const priceBandOptions = useMemo(
|
||||
() => getAgentWorkflowPriceBandOptions(config),
|
||||
[config]
|
||||
@ -198,6 +223,8 @@ export function AgentWorkflowPanel({
|
||||
brand: "",
|
||||
store: "",
|
||||
thirdCategory: "",
|
||||
fourthCategory: "",
|
||||
fifthCategory: "",
|
||||
priceBand: "",
|
||||
});
|
||||
onMaterialsChange({
|
||||
@ -217,7 +244,7 @@ export function AgentWorkflowPanel({
|
||||
<div className="space-y-5 p-4">
|
||||
<section className="space-y-3">
|
||||
<div className="text-sm font-medium">基础配置</div>
|
||||
<div className="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-4">
|
||||
<div className="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-6">
|
||||
<SearchableSelect
|
||||
value={inputs.brand}
|
||||
options={brandOptions}
|
||||
@ -236,6 +263,8 @@ export function AgentWorkflowPanel({
|
||||
value.trim().toLowerCase() === inputs.brand.trim().toLowerCase()
|
||||
? inputs.thirdCategory
|
||||
: "",
|
||||
fourthCategory: "",
|
||||
fifthCategory: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -254,6 +283,8 @@ export function AgentWorkflowPanel({
|
||||
value.trim().toLowerCase() === inputs.store.trim().toLowerCase()
|
||||
? inputs.thirdCategory
|
||||
: "",
|
||||
fourthCategory: "",
|
||||
fifthCategory: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -264,7 +295,44 @@ export function AgentWorkflowPanel({
|
||||
searchPlaceholder="输入三级分类筛选"
|
||||
emptyText={inputs.store ? "当前店铺下暂无三级分类" : "请先选择店铺"}
|
||||
disabled={!inputs.store || thirdCategoryOptions.length === 0}
|
||||
onValueChange={(value) => onInputsChange({ ...inputs, thirdCategory: value })}
|
||||
onValueChange={(value) =>
|
||||
onInputsChange({
|
||||
...inputs,
|
||||
thirdCategory: value,
|
||||
fourthCategory:
|
||||
value.trim().toLowerCase() === inputs.thirdCategory.trim().toLowerCase()
|
||||
? inputs.fourthCategory
|
||||
: "",
|
||||
fifthCategory: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
<SearchableSelect
|
||||
value={inputs.fourthCategory}
|
||||
options={fourthCategoryOptions}
|
||||
placeholder="选择四级分类"
|
||||
searchPlaceholder="输入四级分类筛选"
|
||||
emptyText={inputs.thirdCategory ? "当前三级分类下暂无四级分类" : "请先选择三级分类"}
|
||||
disabled={!inputs.thirdCategory || fourthCategoryOptions.length === 0}
|
||||
onValueChange={(value) =>
|
||||
onInputsChange({
|
||||
...inputs,
|
||||
fourthCategory: value,
|
||||
fifthCategory:
|
||||
value.trim().toLowerCase() === inputs.fourthCategory.trim().toLowerCase()
|
||||
? inputs.fifthCategory
|
||||
: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
<SearchableSelect
|
||||
value={inputs.fifthCategory}
|
||||
options={fifthCategoryOptions}
|
||||
placeholder="选择五级分类"
|
||||
searchPlaceholder="输入五级分类筛选"
|
||||
emptyText={inputs.fourthCategory ? "当前四级分类下暂无五级分类" : "请先选择四级分类"}
|
||||
disabled={!inputs.fourthCategory || fifthCategoryOptions.length === 0}
|
||||
onValueChange={(value) => onInputsChange({ ...inputs, fifthCategory: value })}
|
||||
/>
|
||||
<SearchableSelect
|
||||
value={inputs.priceBand}
|
||||
@ -357,6 +425,8 @@ export function AgentWorkflowPanel({
|
||||
inputs.brand ||
|
||||
inputs.store ||
|
||||
inputs.thirdCategory ||
|
||||
inputs.fourthCategory ||
|
||||
inputs.fifthCategory ||
|
||||
inputs.priceBand ||
|
||||
materials.primary.selectedImageIds.length > 0 ||
|
||||
materials.secondary.selectedImageIds.length > 0 ? (
|
||||
@ -376,6 +446,12 @@ export function AgentWorkflowPanel({
|
||||
{inputs.thirdCategory ? (
|
||||
<span className="rounded-full bg-background px-2 py-1">三级分类:{inputs.thirdCategory}</span>
|
||||
) : null}
|
||||
{inputs.fourthCategory ? (
|
||||
<span className="rounded-full bg-background px-2 py-1">四级分类:{inputs.fourthCategory}</span>
|
||||
) : null}
|
||||
{inputs.fifthCategory ? (
|
||||
<span className="rounded-full bg-background px-2 py-1">五级分类:{inputs.fifthCategory}</span>
|
||||
) : null}
|
||||
{inputs.priceBand ? (
|
||||
<span className="rounded-full bg-background px-2 py-1">价格带:{inputs.priceBand}</span>
|
||||
) : null}
|
||||
|
||||
@ -12,6 +12,8 @@ import { cn } from "@/lib/utils";
|
||||
import {
|
||||
defaultAgentWorkflowUiConfig,
|
||||
getAgentWorkflowBrandStoreEntries,
|
||||
getAgentWorkflowFifthCategoryOptionsByBrandStoreThirdFourth,
|
||||
getAgentWorkflowFourthCategoryOptionsByBrandStoreThirdCategory,
|
||||
getAgentWorkflowPriceBandOptions,
|
||||
getAgentWorkflowThirdCategoryOptionsByBrandStore,
|
||||
getAgentWorkflowStoreOptionsByBrand,
|
||||
@ -34,6 +36,8 @@ export interface AgentWorkflowInputs {
|
||||
brand: string;
|
||||
store: string;
|
||||
thirdCategory: string;
|
||||
fourthCategory: string;
|
||||
fifthCategory: string;
|
||||
priceBand: string;
|
||||
}
|
||||
|
||||
@ -191,6 +195,27 @@ export function AgentWorkflowPanelDark({
|
||||
}),
|
||||
[config, inputs.brand, inputs.store]
|
||||
);
|
||||
const fourthCategoryOptions = useMemo(
|
||||
() =>
|
||||
getAgentWorkflowFourthCategoryOptionsByBrandStoreThirdCategory({
|
||||
config,
|
||||
brand: inputs.brand,
|
||||
store: inputs.store,
|
||||
thirdCategory: inputs.thirdCategory,
|
||||
}),
|
||||
[config, inputs.brand, inputs.store, inputs.thirdCategory]
|
||||
);
|
||||
const fifthCategoryOptions = useMemo(
|
||||
() =>
|
||||
getAgentWorkflowFifthCategoryOptionsByBrandStoreThirdFourth({
|
||||
config,
|
||||
brand: inputs.brand,
|
||||
store: inputs.store,
|
||||
thirdCategory: inputs.thirdCategory,
|
||||
fourthCategory: inputs.fourthCategory,
|
||||
}),
|
||||
[config, inputs.brand, inputs.store, inputs.thirdCategory, inputs.fourthCategory]
|
||||
);
|
||||
const priceBandOptions = useMemo(
|
||||
() => getAgentWorkflowPriceBandOptions(config),
|
||||
[config]
|
||||
@ -284,7 +309,7 @@ export function AgentWorkflowPanelDark({
|
||||
onReset();
|
||||
return;
|
||||
}
|
||||
onInputsChange({ primarySku: "", secondarySku: "", brand: "", store: "", thirdCategory: "", priceBand: "" });
|
||||
onInputsChange({ primarySku: "", secondarySku: "", brand: "", store: "", thirdCategory: "", fourthCategory: "", fifthCategory: "", priceBand: "" });
|
||||
onMaterialsChange({
|
||||
primary: createEmptyAgentWorkflowMaterialSelection(),
|
||||
secondary: createEmptyAgentWorkflowMaterialSelection(),
|
||||
@ -318,7 +343,7 @@ export function AgentWorkflowPanelDark({
|
||||
基础配置
|
||||
</motion.h2>
|
||||
<motion.div
|
||||
className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4"
|
||||
className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-6"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
@ -341,6 +366,8 @@ export function AgentWorkflowPanelDark({
|
||||
value.trim().toLowerCase() === inputs.brand.trim().toLowerCase()
|
||||
? inputs.thirdCategory
|
||||
: "",
|
||||
fourthCategory: "",
|
||||
fifthCategory: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -359,6 +386,8 @@ export function AgentWorkflowPanelDark({
|
||||
value.trim().toLowerCase() === inputs.store.trim().toLowerCase()
|
||||
? inputs.thirdCategory
|
||||
: "",
|
||||
fourthCategory: "",
|
||||
fifthCategory: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -369,7 +398,44 @@ export function AgentWorkflowPanelDark({
|
||||
searchPlaceholder="输入三级分类筛选"
|
||||
emptyText={inputs.store ? "当前店铺下暂无三级分类" : "请先选择店铺"}
|
||||
disabled={!inputs.store || thirdCategoryOptions.length === 0}
|
||||
onValueChange={(value) => onInputsChange({ ...inputs, thirdCategory: value })}
|
||||
onValueChange={(value) =>
|
||||
onInputsChange({
|
||||
...inputs,
|
||||
thirdCategory: value,
|
||||
fourthCategory:
|
||||
value.trim().toLowerCase() === inputs.thirdCategory.trim().toLowerCase()
|
||||
? inputs.fourthCategory
|
||||
: "",
|
||||
fifthCategory: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
<SearchableSelect
|
||||
value={inputs.fourthCategory}
|
||||
options={fourthCategoryOptions}
|
||||
placeholder="选择四级分类"
|
||||
searchPlaceholder="输入四级分类筛选"
|
||||
emptyText={inputs.thirdCategory ? "当前三级分类下暂无四级分类" : "请先选择三级分类"}
|
||||
disabled={!inputs.thirdCategory || fourthCategoryOptions.length === 0}
|
||||
onValueChange={(value) =>
|
||||
onInputsChange({
|
||||
...inputs,
|
||||
fourthCategory: value,
|
||||
fifthCategory:
|
||||
value.trim().toLowerCase() === inputs.fourthCategory.trim().toLowerCase()
|
||||
? inputs.fifthCategory
|
||||
: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
<SearchableSelect
|
||||
value={inputs.fifthCategory}
|
||||
options={fifthCategoryOptions}
|
||||
placeholder="选择五级分类"
|
||||
searchPlaceholder="输入五级分类筛选"
|
||||
emptyText={inputs.fourthCategory ? "当前四级分类下暂无五级分类" : "请先选择四级分类"}
|
||||
disabled={!inputs.fourthCategory || fifthCategoryOptions.length === 0}
|
||||
onValueChange={(value) => onInputsChange({ ...inputs, fifthCategory: value })}
|
||||
/>
|
||||
<SearchableSelect
|
||||
value={inputs.priceBand}
|
||||
|
||||
@ -14,6 +14,8 @@ import {
|
||||
flattenVisibleAgentWorkflowSteps,
|
||||
getAgentWorkflowBlockedOptionKeysForSelections,
|
||||
getAgentWorkflowBrandStoreEntries,
|
||||
getAgentWorkflowFifthCategoryOptionsByBrandStoreThirdFourth,
|
||||
getAgentWorkflowFourthCategoryOptionsByBrandStoreThirdCategory,
|
||||
getAgentWorkflowPriceBandOptions,
|
||||
getAgentWorkflowThirdCategoryOptionsByBrandStore,
|
||||
getAgentWorkflowSelectedOptionPath,
|
||||
@ -43,6 +45,8 @@ export interface AgentWorkflowInputs {
|
||||
brand: string;
|
||||
store: string;
|
||||
thirdCategory: string;
|
||||
fourthCategory: string;
|
||||
fifthCategory: string;
|
||||
priceBand: string;
|
||||
}
|
||||
|
||||
@ -274,7 +278,7 @@ export function AgentWorkflowBrandStorePanel({
|
||||
onInputsChange,
|
||||
theme = "dark",
|
||||
title = "品牌层级选择",
|
||||
description = "先选择品牌、店铺、三级分类和价格带,后续品牌模特与风格步骤会按这里的选择联动。",
|
||||
description = "先选择品牌、店铺到五级分类和价格带,后续品牌模特与风格步骤会按这里的选择联动。",
|
||||
}: AgentWorkflowBrandStorePanelProps) {
|
||||
const brandOptions = useMemo(
|
||||
() => config?.inputOptions?.brands || defaultAgentWorkflowUiConfig.inputOptions?.brands || [],
|
||||
@ -297,6 +301,27 @@ export function AgentWorkflowBrandStorePanel({
|
||||
}),
|
||||
[config, inputs.brand, inputs.store]
|
||||
);
|
||||
const fourthCategoryOptions = useMemo(
|
||||
() =>
|
||||
getAgentWorkflowFourthCategoryOptionsByBrandStoreThirdCategory({
|
||||
config,
|
||||
brand: inputs.brand,
|
||||
store: inputs.store,
|
||||
thirdCategory: inputs.thirdCategory,
|
||||
}),
|
||||
[config, inputs.brand, inputs.store, inputs.thirdCategory]
|
||||
);
|
||||
const fifthCategoryOptions = useMemo(
|
||||
() =>
|
||||
getAgentWorkflowFifthCategoryOptionsByBrandStoreThirdFourth({
|
||||
config,
|
||||
brand: inputs.brand,
|
||||
store: inputs.store,
|
||||
thirdCategory: inputs.thirdCategory,
|
||||
fourthCategory: inputs.fourthCategory,
|
||||
}),
|
||||
[config, inputs.brand, inputs.store, inputs.thirdCategory, inputs.fourthCategory]
|
||||
);
|
||||
const priceBandOptions = useMemo(
|
||||
() => getAgentWorkflowPriceBandOptions(config),
|
||||
[config]
|
||||
@ -318,7 +343,7 @@ export function AgentWorkflowBrandStorePanel({
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid max-w-6xl grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<div className="grid max-w-7xl grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-6">
|
||||
<SearchableSelect
|
||||
value={inputs.brand}
|
||||
options={brandOptions}
|
||||
@ -338,6 +363,8 @@ export function AgentWorkflowBrandStorePanel({
|
||||
value.trim().toLowerCase() === inputs.brand.trim().toLowerCase()
|
||||
? inputs.thirdCategory
|
||||
: "",
|
||||
fourthCategory: "",
|
||||
fifthCategory: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -357,6 +384,8 @@ export function AgentWorkflowBrandStorePanel({
|
||||
value.trim().toLowerCase() === inputs.store.trim().toLowerCase()
|
||||
? inputs.thirdCategory
|
||||
: "",
|
||||
fourthCategory: "",
|
||||
fifthCategory: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -368,7 +397,46 @@ export function AgentWorkflowBrandStorePanel({
|
||||
emptyText={inputs.store ? "当前店铺下暂无三级分类" : "请先选择店铺"}
|
||||
theme={theme}
|
||||
disabled={!inputs.store || thirdCategoryOptions.length === 0}
|
||||
onValueChange={(value) => onInputsChange({ ...inputs, thirdCategory: value })}
|
||||
onValueChange={(value) =>
|
||||
onInputsChange({
|
||||
...inputs,
|
||||
thirdCategory: value,
|
||||
fourthCategory:
|
||||
value.trim().toLowerCase() === inputs.thirdCategory.trim().toLowerCase()
|
||||
? inputs.fourthCategory
|
||||
: "",
|
||||
fifthCategory: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
<SearchableSelect
|
||||
value={inputs.fourthCategory}
|
||||
options={fourthCategoryOptions}
|
||||
placeholder="选择四级分类"
|
||||
searchPlaceholder="输入四级分类筛选"
|
||||
emptyText={inputs.thirdCategory ? "当前三级分类下暂无四级分类" : "请先选择三级分类"}
|
||||
theme={theme}
|
||||
disabled={!inputs.thirdCategory || fourthCategoryOptions.length === 0}
|
||||
onValueChange={(value) =>
|
||||
onInputsChange({
|
||||
...inputs,
|
||||
fourthCategory: value,
|
||||
fifthCategory:
|
||||
value.trim().toLowerCase() === inputs.fourthCategory.trim().toLowerCase()
|
||||
? inputs.fifthCategory
|
||||
: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
<SearchableSelect
|
||||
value={inputs.fifthCategory}
|
||||
options={fifthCategoryOptions}
|
||||
placeholder="选择五级分类"
|
||||
searchPlaceholder="输入五级分类筛选"
|
||||
emptyText={inputs.fourthCategory ? "当前四级分类下暂无五级分类" : "请先选择四级分类"}
|
||||
theme={theme}
|
||||
disabled={!inputs.fourthCategory || fifthCategoryOptions.length === 0}
|
||||
onValueChange={(value) => onInputsChange({ ...inputs, fifthCategory: value })}
|
||||
/>
|
||||
<SearchableSelect
|
||||
value={inputs.priceBand}
|
||||
|
||||
@ -12,6 +12,8 @@ import { cn } from "@/lib/utils";
|
||||
import {
|
||||
defaultAgentWorkflowUiConfig,
|
||||
getAgentWorkflowBrandStoreEntries,
|
||||
getAgentWorkflowFifthCategoryOptionsByBrandStoreThirdFourth,
|
||||
getAgentWorkflowFourthCategoryOptionsByBrandStoreThirdCategory,
|
||||
getAgentWorkflowPriceBandOptions,
|
||||
getAgentWorkflowThirdCategoryOptionsByBrandStore,
|
||||
getAgentWorkflowStoreOptionsByBrand,
|
||||
@ -34,6 +36,8 @@ export interface AgentWorkflowInputs {
|
||||
brand: string;
|
||||
store: string;
|
||||
thirdCategory: string;
|
||||
fourthCategory: string;
|
||||
fifthCategory: string;
|
||||
priceBand: string;
|
||||
}
|
||||
|
||||
@ -203,6 +207,27 @@ export function AgentWorkflowPanelNew({
|
||||
}),
|
||||
[config, inputs.brand, inputs.store]
|
||||
);
|
||||
const fourthCategoryOptions = useMemo(
|
||||
() =>
|
||||
getAgentWorkflowFourthCategoryOptionsByBrandStoreThirdCategory({
|
||||
config,
|
||||
brand: inputs.brand,
|
||||
store: inputs.store,
|
||||
thirdCategory: inputs.thirdCategory,
|
||||
}),
|
||||
[config, inputs.brand, inputs.store, inputs.thirdCategory]
|
||||
);
|
||||
const fifthCategoryOptions = useMemo(
|
||||
() =>
|
||||
getAgentWorkflowFifthCategoryOptionsByBrandStoreThirdFourth({
|
||||
config,
|
||||
brand: inputs.brand,
|
||||
store: inputs.store,
|
||||
thirdCategory: inputs.thirdCategory,
|
||||
fourthCategory: inputs.fourthCategory,
|
||||
}),
|
||||
[config, inputs.brand, inputs.store, inputs.thirdCategory, inputs.fourthCategory]
|
||||
);
|
||||
const priceBandOptions = useMemo(
|
||||
() => getAgentWorkflowPriceBandOptions(config),
|
||||
[config]
|
||||
@ -305,7 +330,7 @@ export function AgentWorkflowPanelNew({
|
||||
onReset();
|
||||
return;
|
||||
}
|
||||
onInputsChange({ primarySku: "", secondarySku: "", brand: "", store: "", thirdCategory: "", priceBand: "" });
|
||||
onInputsChange({ primarySku: "", secondarySku: "", brand: "", store: "", thirdCategory: "", fourthCategory: "", fifthCategory: "", priceBand: "" });
|
||||
onMaterialsChange({
|
||||
primary: createEmptyAgentWorkflowMaterialSelection(),
|
||||
secondary: createEmptyAgentWorkflowMaterialSelection(),
|
||||
@ -357,7 +382,7 @@ export function AgentWorkflowPanelNew({
|
||||
</span>
|
||||
基础配置
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-4">
|
||||
<div className="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-6">
|
||||
<SearchableSelect
|
||||
value={inputs.brand}
|
||||
options={brandOptions}
|
||||
@ -376,6 +401,8 @@ export function AgentWorkflowPanelNew({
|
||||
value.trim().toLowerCase() === inputs.brand.trim().toLowerCase()
|
||||
? inputs.thirdCategory
|
||||
: "",
|
||||
fourthCategory: "",
|
||||
fifthCategory: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -394,6 +421,8 @@ export function AgentWorkflowPanelNew({
|
||||
value.trim().toLowerCase() === inputs.store.trim().toLowerCase()
|
||||
? inputs.thirdCategory
|
||||
: "",
|
||||
fourthCategory: "",
|
||||
fifthCategory: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -404,7 +433,44 @@ export function AgentWorkflowPanelNew({
|
||||
searchPlaceholder="输入三级分类筛选"
|
||||
emptyText={inputs.store ? "当前店铺下暂无三级分类" : "请先选择店铺"}
|
||||
disabled={!inputs.store || thirdCategoryOptions.length === 0}
|
||||
onValueChange={(value) => onInputsChange({ ...inputs, thirdCategory: value })}
|
||||
onValueChange={(value) =>
|
||||
onInputsChange({
|
||||
...inputs,
|
||||
thirdCategory: value,
|
||||
fourthCategory:
|
||||
value.trim().toLowerCase() === inputs.thirdCategory.trim().toLowerCase()
|
||||
? inputs.fourthCategory
|
||||
: "",
|
||||
fifthCategory: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
<SearchableSelect
|
||||
value={inputs.fourthCategory}
|
||||
options={fourthCategoryOptions}
|
||||
placeholder="选择四级分类"
|
||||
searchPlaceholder="输入四级分类筛选"
|
||||
emptyText={inputs.thirdCategory ? "当前三级分类下暂无四级分类" : "请先选择三级分类"}
|
||||
disabled={!inputs.thirdCategory || fourthCategoryOptions.length === 0}
|
||||
onValueChange={(value) =>
|
||||
onInputsChange({
|
||||
...inputs,
|
||||
fourthCategory: value,
|
||||
fifthCategory:
|
||||
value.trim().toLowerCase() === inputs.fourthCategory.trim().toLowerCase()
|
||||
? inputs.fifthCategory
|
||||
: "",
|
||||
})
|
||||
}
|
||||
/>
|
||||
<SearchableSelect
|
||||
value={inputs.fifthCategory}
|
||||
options={fifthCategoryOptions}
|
||||
placeholder="选择五级分类"
|
||||
searchPlaceholder="输入五级分类筛选"
|
||||
emptyText={inputs.fourthCategory ? "当前四级分类下暂无五级分类" : "请先选择四级分类"}
|
||||
disabled={!inputs.fourthCategory || fifthCategoryOptions.length === 0}
|
||||
onValueChange={(value) => onInputsChange({ ...inputs, fifthCategory: value })}
|
||||
/>
|
||||
<SearchableSelect
|
||||
value={inputs.priceBand}
|
||||
|
||||
@ -35,6 +35,8 @@ export function createEmptyAgentWorkflowPersistedState(): AgentWorkflowPersisted
|
||||
brand: "",
|
||||
store: "",
|
||||
thirdCategory: "",
|
||||
fourthCategory: "",
|
||||
fifthCategory: "",
|
||||
priceBand: "",
|
||||
},
|
||||
materials: createEmptyAgentWorkflowMaterialSelections(),
|
||||
@ -231,6 +233,8 @@ function normalizeWorkflowState(value: unknown): AgentWorkflowPersistedState {
|
||||
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(),
|
||||
thirdCategory: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record<string, unknown>).thirdCategory || "" : "").trim(),
|
||||
fourthCategory: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record<string, unknown>).fourthCategory || "" : "").trim(),
|
||||
fifthCategory: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record<string, unknown>).fifthCategory || "" : "").trim(),
|
||||
priceBand: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record<string, unknown>).priceBand || "" : "").trim(),
|
||||
},
|
||||
materials: normalizeAgentWorkflowMaterialSelections(raw.materials),
|
||||
|
||||
@ -21,6 +21,8 @@ export interface AgentWorkflowInputsValue {
|
||||
brand: string;
|
||||
store: string;
|
||||
thirdCategory: string;
|
||||
fourthCategory: string;
|
||||
fifthCategory: string;
|
||||
priceBand: string;
|
||||
}
|
||||
|
||||
@ -216,6 +218,12 @@ export function buildAgentWorkflowComposerContext(params: {
|
||||
if (params.inputs.thirdCategory.trim()) {
|
||||
summaryParts.push(`三级分类:${params.inputs.thirdCategory.trim()}`);
|
||||
}
|
||||
if (params.inputs.fourthCategory.trim()) {
|
||||
summaryParts.push(`四级分类:${params.inputs.fourthCategory.trim()}`);
|
||||
}
|
||||
if (params.inputs.fifthCategory.trim()) {
|
||||
summaryParts.push(`五级分类:${params.inputs.fifthCategory.trim()}`);
|
||||
}
|
||||
if (params.inputs.priceBand.trim()) {
|
||||
summaryParts.push(`价格带:${params.inputs.priceBand.trim()}`);
|
||||
}
|
||||
@ -276,6 +284,12 @@ export function buildAgentWorkflowComposerContext(params: {
|
||||
if (params.inputs.thirdCategory.trim()) {
|
||||
promptLines.push(`三级分类:${params.inputs.thirdCategory.trim()}`);
|
||||
}
|
||||
if (params.inputs.fourthCategory.trim()) {
|
||||
promptLines.push(`四级分类:${params.inputs.fourthCategory.trim()}`);
|
||||
}
|
||||
if (params.inputs.fifthCategory.trim()) {
|
||||
promptLines.push(`五级分类:${params.inputs.fifthCategory.trim()}`);
|
||||
}
|
||||
if (params.inputs.priceBand.trim()) {
|
||||
promptLines.push(`价格带:${params.inputs.priceBand.trim()}`);
|
||||
}
|
||||
@ -362,6 +376,8 @@ export function buildAgentWorkflowComposerContext(params: {
|
||||
brand: params.inputs.brand.trim() || undefined,
|
||||
store: params.inputs.store.trim() || undefined,
|
||||
thirdCategory: params.inputs.thirdCategory.trim() || undefined,
|
||||
fourthCategory: params.inputs.fourthCategory.trim() || undefined,
|
||||
fifthCategory: params.inputs.fifthCategory.trim() || undefined,
|
||||
priceBand: params.inputs.priceBand.trim() || undefined,
|
||||
selections,
|
||||
referenceImages,
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import type {
|
||||
AgentWorkflowBrandStoreEntry,
|
||||
AgentWorkflowFourthCategoryEntry,
|
||||
AgentWorkflowOptionUiConfig,
|
||||
AgentWorkflowStoreEntry,
|
||||
AgentWorkflowThirdCategoryEntry,
|
||||
AgentWorkflowStepKey,
|
||||
AgentWorkflowStepUiConfig,
|
||||
AgentWorkflowUiConfig,
|
||||
@ -141,6 +143,8 @@ export const defaultAgentWorkflowUiConfig: AgentWorkflowUiConfig = {
|
||||
brands: [],
|
||||
stores: [],
|
||||
thirdCategories: [],
|
||||
fourthCategories: [],
|
||||
fifthCategories: [],
|
||||
priceBands: [],
|
||||
brandStoreEntries: [],
|
||||
},
|
||||
@ -434,6 +438,143 @@ export function normalizeAgentWorkflowUiConfig(value: unknown): AgentWorkflowUiC
|
||||
const normalizeBrandStoreEntries = (input: unknown) => {
|
||||
if (!Array.isArray(input)) return [] as AgentWorkflowBrandStoreEntry[];
|
||||
|
||||
const normalizeFourthCategoryEntries = (
|
||||
rawFourthCategoryEntries: unknown,
|
||||
legacyFourthCategories: string[]
|
||||
): AgentWorkflowFourthCategoryEntry[] => {
|
||||
const mappedEntries: Array<AgentWorkflowFourthCategoryEntry | null> = Array.isArray(
|
||||
rawFourthCategoryEntries
|
||||
)
|
||||
? rawFourthCategoryEntries.map((fourthItem, fourthIndex) => {
|
||||
if (typeof fourthItem === "string") {
|
||||
const categoryName = normalizeText(fourthItem, 64);
|
||||
if (!categoryName) return null;
|
||||
return {
|
||||
categoryName,
|
||||
fifthCategories: [],
|
||||
sortOrder: fourthIndex,
|
||||
};
|
||||
}
|
||||
|
||||
const fourthRecord =
|
||||
fourthItem && typeof fourthItem === "object" && !Array.isArray(fourthItem)
|
||||
? (fourthItem as Record<string, unknown>)
|
||||
: {};
|
||||
const categoryName = normalizeText(
|
||||
fourthRecord.categoryName ?? fourthRecord.fourthCategoryName,
|
||||
64
|
||||
);
|
||||
if (!categoryName) return null;
|
||||
return {
|
||||
categoryName,
|
||||
fifthCategories: normalizeStringList(fourthRecord.fifthCategories).slice(0, 300),
|
||||
sortOrder: normalizeNumber(fourthRecord.sortOrder, fourthIndex),
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
||||
const normalizedFromEntries = mappedEntries
|
||||
.filter((item): item is AgentWorkflowFourthCategoryEntry => item !== null)
|
||||
.sort((left, right) => (left.sortOrder || 0) - (right.sortOrder || 0))
|
||||
.map((entry, index) => ({
|
||||
...entry,
|
||||
fifthCategories: Array.from(
|
||||
new Set((entry.fifthCategories || []).map((item) => item.trim()).filter(Boolean))
|
||||
).slice(0, 300),
|
||||
sortOrder: index,
|
||||
}));
|
||||
|
||||
if (normalizedFromEntries.length > 0) {
|
||||
return normalizedFromEntries;
|
||||
}
|
||||
|
||||
return legacyFourthCategories.map((categoryName, index) => ({
|
||||
categoryName,
|
||||
fifthCategories: [],
|
||||
sortOrder: index,
|
||||
}));
|
||||
};
|
||||
|
||||
const normalizeThirdCategoryEntries = (
|
||||
rawThirdCategoryEntries: unknown,
|
||||
legacyThirdCategories: string[]
|
||||
): AgentWorkflowThirdCategoryEntry[] => {
|
||||
const mappedEntries: Array<AgentWorkflowThirdCategoryEntry | null> = Array.isArray(
|
||||
rawThirdCategoryEntries
|
||||
)
|
||||
? rawThirdCategoryEntries.map((thirdItem, thirdIndex) => {
|
||||
if (typeof thirdItem === "string") {
|
||||
const categoryName = normalizeText(thirdItem, 64);
|
||||
if (!categoryName) return null;
|
||||
return {
|
||||
categoryName,
|
||||
fourthCategories: [],
|
||||
fourthCategoryEntries: [],
|
||||
sortOrder: thirdIndex,
|
||||
};
|
||||
}
|
||||
|
||||
const thirdRecord =
|
||||
thirdItem && typeof thirdItem === "object" && !Array.isArray(thirdItem)
|
||||
? (thirdItem as Record<string, unknown>)
|
||||
: {};
|
||||
const categoryName = normalizeText(
|
||||
thirdRecord.categoryName ?? thirdRecord.thirdCategoryName,
|
||||
64
|
||||
);
|
||||
if (!categoryName) return null;
|
||||
|
||||
const legacyFourthCategories = normalizeStringList(thirdRecord.fourthCategories).slice(
|
||||
0,
|
||||
300
|
||||
);
|
||||
const fourthCategoryEntries = normalizeFourthCategoryEntries(
|
||||
thirdRecord.fourthCategoryEntries,
|
||||
legacyFourthCategories
|
||||
);
|
||||
|
||||
return {
|
||||
categoryName,
|
||||
fourthCategories:
|
||||
fourthCategoryEntries.length > 0
|
||||
? fourthCategoryEntries.map((entry) => entry.categoryName)
|
||||
: legacyFourthCategories,
|
||||
fourthCategoryEntries,
|
||||
sortOrder: normalizeNumber(thirdRecord.sortOrder, thirdIndex),
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
||||
const normalizedFromEntries = mappedEntries
|
||||
.filter((item): item is AgentWorkflowThirdCategoryEntry => item !== null)
|
||||
.sort((left, right) => (left.sortOrder || 0) - (right.sortOrder || 0))
|
||||
.map((entry, index) => ({
|
||||
...entry,
|
||||
fourthCategories: Array.from(
|
||||
new Set((entry.fourthCategories || []).map((item) => item.trim()).filter(Boolean))
|
||||
).slice(0, 300),
|
||||
fourthCategoryEntries: (entry.fourthCategoryEntries || []).map((fourthEntry, fourthIndex) => ({
|
||||
...fourthEntry,
|
||||
fifthCategories: Array.from(
|
||||
new Set((fourthEntry.fifthCategories || []).map((item) => item.trim()).filter(Boolean))
|
||||
).slice(0, 300),
|
||||
sortOrder: fourthIndex,
|
||||
})),
|
||||
sortOrder: index,
|
||||
}));
|
||||
|
||||
if (normalizedFromEntries.length > 0) {
|
||||
return normalizedFromEntries;
|
||||
}
|
||||
|
||||
return legacyThirdCategories.map((categoryName, index) => ({
|
||||
categoryName,
|
||||
fourthCategories: [],
|
||||
fourthCategoryEntries: [],
|
||||
sortOrder: index,
|
||||
}));
|
||||
};
|
||||
|
||||
const normalizeStoreEntries = (
|
||||
rawStoreEntries: unknown,
|
||||
legacyStores: string[]
|
||||
@ -456,9 +597,21 @@ export function normalizeAgentWorkflowUiConfig(value: unknown): AgentWorkflowUiC
|
||||
: {};
|
||||
const storeName = normalizeText(storeRecord.storeName, 64);
|
||||
if (!storeName) return null;
|
||||
const legacyThirdCategories = normalizeStringList(storeRecord.thirdCategories).slice(
|
||||
0,
|
||||
300
|
||||
);
|
||||
const thirdCategoryEntries = normalizeThirdCategoryEntries(
|
||||
storeRecord.thirdCategoryEntries,
|
||||
legacyThirdCategories
|
||||
);
|
||||
return {
|
||||
storeName,
|
||||
thirdCategories: normalizeStringList(storeRecord.thirdCategories).slice(0, 300),
|
||||
thirdCategories:
|
||||
thirdCategoryEntries.length > 0
|
||||
? thirdCategoryEntries.map((entry) => entry.categoryName)
|
||||
: legacyThirdCategories,
|
||||
thirdCategoryEntries,
|
||||
sortOrder: normalizeNumber(storeRecord.sortOrder, storeIndex),
|
||||
};
|
||||
})
|
||||
@ -472,6 +625,22 @@ export function normalizeAgentWorkflowUiConfig(value: unknown): AgentWorkflowUiC
|
||||
thirdCategories: Array.from(
|
||||
new Set((entry.thirdCategories || []).map((item) => item.trim()).filter(Boolean))
|
||||
).slice(0, 300),
|
||||
thirdCategoryEntries: (entry.thirdCategoryEntries || []).map((thirdEntry, thirdIndex) => ({
|
||||
...thirdEntry,
|
||||
fourthCategories: Array.from(
|
||||
new Set((thirdEntry.fourthCategories || []).map((item) => item.trim()).filter(Boolean))
|
||||
).slice(0, 300),
|
||||
fourthCategoryEntries: (thirdEntry.fourthCategoryEntries || []).map(
|
||||
(fourthEntry, fourthIndex) => ({
|
||||
...fourthEntry,
|
||||
fifthCategories: Array.from(
|
||||
new Set((fourthEntry.fifthCategories || []).map((item) => item.trim()).filter(Boolean))
|
||||
).slice(0, 300),
|
||||
sortOrder: fourthIndex,
|
||||
})
|
||||
),
|
||||
sortOrder: thirdIndex,
|
||||
})),
|
||||
sortOrder: index,
|
||||
}));
|
||||
|
||||
@ -482,6 +651,7 @@ export function normalizeAgentWorkflowUiConfig(value: unknown): AgentWorkflowUiC
|
||||
return legacyStores.map((storeName, index) => ({
|
||||
storeName,
|
||||
thirdCategories: [],
|
||||
thirdCategoryEntries: [],
|
||||
sortOrder: index,
|
||||
}));
|
||||
};
|
||||
@ -519,6 +689,8 @@ export function normalizeAgentWorkflowUiConfig(value: unknown): AgentWorkflowUiC
|
||||
const normalizedBrands = normalizeStringList(rawInputOptions.brands);
|
||||
const normalizedStores = normalizeStringList(rawInputOptions.stores);
|
||||
const normalizedThirdCategories = normalizeStringList(rawInputOptions.thirdCategories);
|
||||
const normalizedFourthCategories = normalizeStringList(rawInputOptions.fourthCategories);
|
||||
const normalizedFifthCategories = normalizeStringList(rawInputOptions.fifthCategories);
|
||||
const normalizedPriceBands = normalizeStringList(rawInputOptions.priceBands);
|
||||
const fallbackBrandStoreEntries: AgentWorkflowBrandStoreEntry[] =
|
||||
normalizedBrandStoreEntries.length > 0
|
||||
@ -531,6 +703,7 @@ export function normalizeAgentWorkflowUiConfig(value: unknown): AgentWorkflowUiC
|
||||
? normalizedStores.map((storeName, storeIndex) => ({
|
||||
storeName,
|
||||
thirdCategories: [],
|
||||
thirdCategoryEntries: [],
|
||||
sortOrder: storeIndex,
|
||||
}))
|
||||
: [],
|
||||
@ -718,6 +891,36 @@ export function normalizeAgentWorkflowUiConfig(value: unknown): AgentWorkflowUiC
|
||||
)
|
||||
)
|
||||
: normalizedThirdCategories,
|
||||
fourthCategories:
|
||||
fallbackBrandStoreEntries.length > 0
|
||||
? Array.from(
|
||||
new Set(
|
||||
fallbackBrandStoreEntries.flatMap((entry) =>
|
||||
(entry.storeEntries || []).flatMap((storeEntry) =>
|
||||
(storeEntry.thirdCategoryEntries || []).flatMap(
|
||||
(thirdEntry) => thirdEntry.fourthCategories || []
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
: normalizedFourthCategories,
|
||||
fifthCategories:
|
||||
fallbackBrandStoreEntries.length > 0
|
||||
? Array.from(
|
||||
new Set(
|
||||
fallbackBrandStoreEntries.flatMap((entry) =>
|
||||
(entry.storeEntries || []).flatMap((storeEntry) =>
|
||||
(storeEntry.thirdCategoryEntries || []).flatMap((thirdEntry) =>
|
||||
(thirdEntry.fourthCategoryEntries || []).flatMap(
|
||||
(fourthEntry) => fourthEntry.fifthCategories || []
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
: normalizedFifthCategories,
|
||||
priceBands: normalizedPriceBands,
|
||||
brandStoreEntries: fallbackBrandStoreEntries,
|
||||
},
|
||||
@ -773,6 +976,13 @@ export function getAgentWorkflowThirdCategoryOptionsByBrandStore(params: {
|
||||
const matchedStoreEntry = matchedEntry?.storeEntries?.find(
|
||||
(storeEntry) => storeEntry.storeName.trim().toLowerCase() === normalizedStore
|
||||
);
|
||||
const thirdCategoryOptions =
|
||||
matchedStoreEntry?.thirdCategoryEntries && matchedStoreEntry.thirdCategoryEntries.length > 0
|
||||
? matchedStoreEntry.thirdCategoryEntries.map((entry) => entry.categoryName).filter(Boolean)
|
||||
: matchedStoreEntry?.thirdCategories || [];
|
||||
if (thirdCategoryOptions.length > 0) {
|
||||
return thirdCategoryOptions;
|
||||
}
|
||||
if (matchedStoreEntry && matchedStoreEntry.thirdCategories.length > 0) {
|
||||
return matchedStoreEntry.thirdCategories;
|
||||
}
|
||||
@ -780,6 +990,71 @@ export function getAgentWorkflowThirdCategoryOptionsByBrandStore(params: {
|
||||
return params.config?.inputOptions?.thirdCategories || defaultAgentWorkflowUiConfig.inputOptions?.thirdCategories || [];
|
||||
}
|
||||
|
||||
export function getAgentWorkflowFourthCategoryOptionsByBrandStoreThirdCategory(params: {
|
||||
config?: AgentWorkflowUiConfig | null;
|
||||
brand?: string | null;
|
||||
store?: string | null;
|
||||
thirdCategory?: string | null;
|
||||
}) {
|
||||
const entries = getAgentWorkflowBrandStoreEntries(params.config);
|
||||
const normalizedBrand = String(params.brand || "").trim().toLowerCase();
|
||||
const normalizedStore = String(params.store || "").trim().toLowerCase();
|
||||
const normalizedThirdCategory = String(params.thirdCategory || "").trim().toLowerCase();
|
||||
if (!normalizedBrand || !normalizedStore || !normalizedThirdCategory) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const matchedEntry = entries.find(
|
||||
(entry) => entry.brandName.trim().toLowerCase() === normalizedBrand
|
||||
);
|
||||
const matchedStoreEntry = matchedEntry?.storeEntries?.find(
|
||||
(storeEntry) => storeEntry.storeName.trim().toLowerCase() === normalizedStore
|
||||
);
|
||||
const matchedThirdEntry = matchedStoreEntry?.thirdCategoryEntries?.find(
|
||||
(thirdEntry) => thirdEntry.categoryName.trim().toLowerCase() === normalizedThirdCategory
|
||||
);
|
||||
if (matchedThirdEntry && matchedThirdEntry.fourthCategories.length > 0) {
|
||||
return matchedThirdEntry.fourthCategories;
|
||||
}
|
||||
|
||||
return params.config?.inputOptions?.fourthCategories || defaultAgentWorkflowUiConfig.inputOptions?.fourthCategories || [];
|
||||
}
|
||||
|
||||
export function getAgentWorkflowFifthCategoryOptionsByBrandStoreThirdFourth(params: {
|
||||
config?: AgentWorkflowUiConfig | null;
|
||||
brand?: string | null;
|
||||
store?: string | null;
|
||||
thirdCategory?: string | null;
|
||||
fourthCategory?: string | null;
|
||||
}) {
|
||||
const entries = getAgentWorkflowBrandStoreEntries(params.config);
|
||||
const normalizedBrand = String(params.brand || "").trim().toLowerCase();
|
||||
const normalizedStore = String(params.store || "").trim().toLowerCase();
|
||||
const normalizedThirdCategory = String(params.thirdCategory || "").trim().toLowerCase();
|
||||
const normalizedFourthCategory = String(params.fourthCategory || "").trim().toLowerCase();
|
||||
if (!normalizedBrand || !normalizedStore || !normalizedThirdCategory || !normalizedFourthCategory) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const matchedEntry = entries.find(
|
||||
(entry) => entry.brandName.trim().toLowerCase() === normalizedBrand
|
||||
);
|
||||
const matchedStoreEntry = matchedEntry?.storeEntries?.find(
|
||||
(storeEntry) => storeEntry.storeName.trim().toLowerCase() === normalizedStore
|
||||
);
|
||||
const matchedThirdEntry = matchedStoreEntry?.thirdCategoryEntries?.find(
|
||||
(thirdEntry) => thirdEntry.categoryName.trim().toLowerCase() === normalizedThirdCategory
|
||||
);
|
||||
const matchedFourthEntry = matchedThirdEntry?.fourthCategoryEntries?.find(
|
||||
(fourthEntry) => fourthEntry.categoryName.trim().toLowerCase() === normalizedFourthCategory
|
||||
);
|
||||
if (matchedFourthEntry && matchedFourthEntry.fifthCategories.length > 0) {
|
||||
return matchedFourthEntry.fifthCategories;
|
||||
}
|
||||
|
||||
return params.config?.inputOptions?.fifthCategories || defaultAgentWorkflowUiConfig.inputOptions?.fifthCategories || [];
|
||||
}
|
||||
|
||||
export function getAgentWorkflowPriceBandOptions(config?: AgentWorkflowUiConfig | null) {
|
||||
return config?.inputOptions?.priceBands || defaultAgentWorkflowUiConfig.inputOptions?.priceBands || [];
|
||||
}
|
||||
|
||||
@ -618,6 +618,8 @@ export const canvasProject = pgTable("CanvasProject", {
|
||||
brand?: string;
|
||||
store?: string;
|
||||
thirdCategory?: string;
|
||||
fourthCategory?: string;
|
||||
fifthCategory?: string;
|
||||
priceBand?: string;
|
||||
};
|
||||
materials?: {
|
||||
|
||||
@ -106,15 +106,31 @@ export interface AgentWorkflowUiConfig {
|
||||
brands: string[];
|
||||
stores: string[];
|
||||
thirdCategories?: string[];
|
||||
fourthCategories?: string[];
|
||||
fifthCategories?: string[];
|
||||
priceBands?: string[];
|
||||
brandStoreEntries?: AgentWorkflowBrandStoreEntry[];
|
||||
};
|
||||
steps: AgentWorkflowStepUiConfig[];
|
||||
}
|
||||
|
||||
export interface AgentWorkflowFourthCategoryEntry {
|
||||
categoryName: string;
|
||||
fifthCategories: string[];
|
||||
sortOrder?: number;
|
||||
}
|
||||
|
||||
export interface AgentWorkflowThirdCategoryEntry {
|
||||
categoryName: string;
|
||||
fourthCategories: string[];
|
||||
fourthCategoryEntries?: AgentWorkflowFourthCategoryEntry[];
|
||||
sortOrder?: number;
|
||||
}
|
||||
|
||||
export interface AgentWorkflowStoreEntry {
|
||||
storeName: string;
|
||||
thirdCategories: string[];
|
||||
thirdCategoryEntries?: AgentWorkflowThirdCategoryEntry[];
|
||||
sortOrder?: number;
|
||||
}
|
||||
|
||||
@ -258,6 +274,8 @@ export interface AgentWorkflowComposerContext {
|
||||
brand?: string;
|
||||
store?: string;
|
||||
thirdCategory?: string;
|
||||
fourthCategory?: string;
|
||||
fifthCategory?: string;
|
||||
priceBand?: string;
|
||||
selections: AgentWorkflowComposerSelection[];
|
||||
referenceImages: AgentWorkflowReferenceImage[];
|
||||
@ -392,6 +410,8 @@ export interface AgentWorkflowPersistedState {
|
||||
brand: string;
|
||||
store: string;
|
||||
thirdCategory: string;
|
||||
fourthCategory: string;
|
||||
fifthCategory: string;
|
||||
priceBand: string;
|
||||
};
|
||||
materials?: AgentWorkflowMaterialSelections;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user