feat: improve agent brand and store option editor
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-03-18 16:15:56 +08:00
parent 4f5d999ade
commit eb56b7967d

View File

@ -183,6 +183,8 @@ export function AgentWorkflowSettingsClient({
const [uploadingKey, setUploadingKey] = useState<string | null>(null);
const [expandedSteps, setExpandedSteps] = useState<Set<string>>(new Set());
const [activeTab, setActiveTab] = useState("showcase");
const [brandDraft, setBrandDraft] = useState("");
const [storeDraft, setStoreDraft] = useState("");
const fileInputRefs = useRef<Record<string, HTMLInputElement | null>>({});
useEffect(() => {
@ -220,6 +222,60 @@ export function AgentWorkflowSettingsClient({
() => [...config.steps].sort((left, right) => left.sortOrder - right.sortOrder),
[config.steps]
);
const brandOptions = config.inputOptions?.brands || [];
const storeOptions = config.inputOptions?.stores || [];
const updateInputOptions = (updates: Partial<{ brands: string[]; stores: string[] }>) => {
setConfig((prev) => ({
...prev,
inputOptions: {
brands: updates.brands ?? prev.inputOptions?.brands ?? [],
stores: updates.stores ?? prev.inputOptions?.stores ?? [],
},
}));
};
const normalizeInputOptionValue = (value: string) =>
value
.replace(/\s+/g, " ")
.trim();
const handleAddInputOption = (kind: "brands" | "stores") => {
const draft = kind === "brands" ? brandDraft : storeDraft;
const nextValue = normalizeInputOptionValue(draft);
if (!nextValue) {
toast.error(kind === "brands" ? "请输入品牌名称" : "请输入店铺名称");
return;
}
const currentList = kind === "brands" ? brandOptions : storeOptions;
if (currentList.some((item) => item.toLowerCase() === nextValue.toLowerCase())) {
toast.error(kind === "brands" ? "该品牌已存在" : "该店铺已存在");
return;
}
if (kind === "brands") {
updateInputOptions({ brands: [...brandOptions, nextValue] });
setBrandDraft("");
return;
}
updateInputOptions({ stores: [...storeOptions, nextValue] });
setStoreDraft("");
};
const handleRemoveInputOption = (kind: "brands" | "stores", value: string) => {
if (kind === "brands") {
updateInputOptions({
brands: brandOptions.filter((item) => item !== value),
});
return;
}
updateInputOptions({
stores: storeOptions.filter((item) => item !== value),
});
};
const enabledStepsCount = useMemo(
() => sortedSteps.filter((s) => s.enabled).length,
@ -961,48 +1017,114 @@ export function AgentWorkflowSettingsClient({
<div className="flex items-center justify-between">
<Label className="text-xs">品牌列表</Label>
<span className="text-xs text-muted-foreground">
{(config.inputOptions?.brands || []).length} 个品牌
{brandOptions.length} 个品牌
</span>
</div>
<Textarea
value={(config.inputOptions?.brands || []).join("\n")}
onChange={(e) =>
setConfig((prev) => ({
...prev,
inputOptions: {
brands: e.target.value.split("\n").map((s) => s.trim()).filter(Boolean),
stores: prev.inputOptions?.stores || [],
},
}))
}
rows={8}
placeholder="宜家\n顾家家居\n林氏家居"
className="font-mono text-sm"
/>
<div className="rounded-xl border bg-muted/10 p-3">
<div className="flex flex-col gap-2 sm:flex-row">
<Input
value={brandDraft}
onChange={(e) => setBrandDraft(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
handleAddInputOption("brands");
}
}}
placeholder="输入一个品牌名称后点击添加"
/>
<Button
type="button"
onClick={() => handleAddInputOption("brands")}
className="shrink-0"
>
<Plus className="mr-2 h-4 w-4" />
添加品牌
</Button>
</div>
{brandOptions.length > 0 ? (
<div className="mt-3 flex flex-wrap gap-2">
{brandOptions.map((brand) => (
<div
key={brand}
className="inline-flex items-center gap-2 rounded-full border bg-background px-3 py-1.5 text-sm"
>
<span>{brand}</span>
<button
type="button"
onClick={() => handleRemoveInputOption("brands", brand)}
className="rounded-full p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-destructive"
aria-label={`删除品牌 ${brand}`}
>
<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>
<Separator />
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-xs">店铺列表</Label>
<span className="text-xs text-muted-foreground">
{(config.inputOptions?.stores || []).length} 个店铺
{storeOptions.length} 个店铺
</span>
</div>
<Textarea
value={(config.inputOptions?.stores || []).join("\n")}
onChange={(e) =>
setConfig((prev) => ({
...prev,
inputOptions: {
brands: prev.inputOptions?.brands || [],
stores: e.target.value.split("\n").map((s) => s.trim()).filter(Boolean),
},
}))
}
rows={8}
placeholder="天猫旗舰店\n京东自营店"
className="font-mono text-sm"
/>
<div className="rounded-xl border bg-muted/10 p-3">
<div className="flex flex-col gap-2 sm:flex-row">
<Input
value={storeDraft}
onChange={(e) => setStoreDraft(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
handleAddInputOption("stores");
}
}}
placeholder="输入一个店铺名称后点击添加"
/>
<Button
type="button"
onClick={() => handleAddInputOption("stores")}
className="shrink-0"
>
<Plus className="mr-2 h-4 w-4" />
添加店铺
</Button>
</div>
{storeOptions.length > 0 ? (
<div className="mt-3 flex flex-wrap gap-2">
{storeOptions.map((store) => (
<div
key={store}
className="inline-flex items-center gap-2 rounded-full border bg-background px-3 py-1.5 text-sm"
>
<span>{store}</span>
<button
type="button"
onClick={() => handleRemoveInputOption("stores", store)}
className="rounded-full p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-destructive"
aria-label={`删除店铺 ${store}`}
>
<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>
</CardContent>
</Card>
@ -1108,7 +1230,7 @@ export function AgentWorkflowSettingsClient({
>
<img
src={image.imageUrl}
alt={image.displayName}
alt={image.displayName || `${brandName} 模特图`}
className="h-full w-full object-cover"
/>
<div className="absolute inset-0 bg-black/0 transition-colors group-hover:bg-black/40" />