refactor: simplify agent preset setup
This commit is contained in:
parent
4533af1495
commit
afc9e187b6
@ -5443,185 +5443,12 @@ export function AdminSettingsClient() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{siteSettings.agentEnabled && (
|
||||
<div className="rounded-lg border p-4 space-y-4">
|
||||
<div>
|
||||
<div className="font-medium">智能体树形分类配置</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
固定四级分类:产品品类 / 人群标签 / 使用场景 / 品牌。用户创建和筛选智能体时统一从这里选择,不再支持自定义创建分类。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label>一级分类:产品品类</Label>
|
||||
<Textarea
|
||||
value={siteSettings.agentCategoryTreeConfig.productCategories.join("\n")}
|
||||
onChange={(event) =>
|
||||
setSiteSettings({
|
||||
...siteSettings,
|
||||
agentCategoryTreeConfig: {
|
||||
...siteSettings.agentCategoryTreeConfig,
|
||||
productCategories: event.target.value
|
||||
.split("\n")
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean),
|
||||
},
|
||||
})
|
||||
}
|
||||
rows={6}
|
||||
placeholder={"例如:\n沙发\n床垫\n护肤品\n咖啡机"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>二级分类:人群标签</Label>
|
||||
<Textarea
|
||||
value={siteSettings.agentCategoryTreeConfig.audienceTags.join("\n")}
|
||||
onChange={(event) =>
|
||||
setSiteSettings({
|
||||
...siteSettings,
|
||||
agentCategoryTreeConfig: {
|
||||
...siteSettings.agentCategoryTreeConfig,
|
||||
audienceTags: event.target.value
|
||||
.split("\n")
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean),
|
||||
},
|
||||
})
|
||||
}
|
||||
rows={6}
|
||||
placeholder={"例如:\n宝妈\n年轻白领\n学生\n户外人群"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>三级分类:使用场景</Label>
|
||||
<Textarea
|
||||
value={siteSettings.agentCategoryTreeConfig.usageScenarios.join("\n")}
|
||||
onChange={(event) =>
|
||||
setSiteSettings({
|
||||
...siteSettings,
|
||||
agentCategoryTreeConfig: {
|
||||
...siteSettings.agentCategoryTreeConfig,
|
||||
usageScenarios: event.target.value
|
||||
.split("\n")
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean),
|
||||
},
|
||||
})
|
||||
}
|
||||
rows={6}
|
||||
placeholder={"例如:\n客厅\n卧室\n办公室\n户外露营"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>四级分类:品牌</Label>
|
||||
<Textarea
|
||||
value={siteSettings.agentCategoryTreeConfig.brands.join("\n")}
|
||||
onChange={(event) =>
|
||||
setSiteSettings({
|
||||
...siteSettings,
|
||||
agentCategoryTreeConfig: {
|
||||
...siteSettings.agentCategoryTreeConfig,
|
||||
brands: event.target.value
|
||||
.split("\n")
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean),
|
||||
},
|
||||
})
|
||||
}
|
||||
rows={6}
|
||||
placeholder={"例如:\n宜家\n华为\n兰蔻\n戴森"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{siteSettings.agentEnabled && (
|
||||
<div className="rounded-lg border p-4 space-y-4">
|
||||
<div>
|
||||
<div className="font-medium">智能体二级类型配置</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
用户创建智能体时,“二级类型”下拉从这里读取。按主类型分别维护:生图 / 视频 / 生文。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
<div className="space-y-2">
|
||||
<Label>生图二级类型</Label>
|
||||
<Textarea
|
||||
value={siteSettings.agentSecondaryTypeConfig.image.join("\n")}
|
||||
onChange={(event) =>
|
||||
setSiteSettings({
|
||||
...siteSettings,
|
||||
agentSecondaryTypeConfig: {
|
||||
...siteSettings.agentSecondaryTypeConfig,
|
||||
image: event.target.value
|
||||
.split("\n")
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean),
|
||||
},
|
||||
})
|
||||
}
|
||||
rows={6}
|
||||
placeholder={"例如:\n产品图优化\n场景图生成\n海报图生成"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>视频二级类型</Label>
|
||||
<Textarea
|
||||
value={siteSettings.agentSecondaryTypeConfig.video.join("\n")}
|
||||
onChange={(event) =>
|
||||
setSiteSettings({
|
||||
...siteSettings,
|
||||
agentSecondaryTypeConfig: {
|
||||
...siteSettings.agentSecondaryTypeConfig,
|
||||
video: event.target.value
|
||||
.split("\n")
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean),
|
||||
},
|
||||
})
|
||||
}
|
||||
rows={6}
|
||||
placeholder={"例如:\n口播视频\n商品演示\n分镜脚本"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>生文二级类型</Label>
|
||||
<Textarea
|
||||
value={siteSettings.agentSecondaryTypeConfig.text.join("\n")}
|
||||
onChange={(event) =>
|
||||
setSiteSettings({
|
||||
...siteSettings,
|
||||
agentSecondaryTypeConfig: {
|
||||
...siteSettings.agentSecondaryTypeConfig,
|
||||
text: event.target.value
|
||||
.split("\n")
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean),
|
||||
},
|
||||
})
|
||||
}
|
||||
rows={6}
|
||||
placeholder={"例如:\n文案分析\n提示词拆解\n图片分析报告"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{siteSettings.agentEnabled && (
|
||||
<div className="rounded-lg border p-4 space-y-4">
|
||||
<div>
|
||||
<div className="font-medium">智能体工作流配置</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
智能体页面的功能按钮名称、Prompt 片段和预览图已迁移到独立子页面,避免网站设置页过于臃肿。
|
||||
智能体页面的功能按钮名称、Prompt 片段和预览图已迁移到独立子页面,避免网站设置页过于臃肿。原先重复的分类和二级类型配置已不再在这里维护。
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-xl border bg-muted/20 p-4">
|
||||
|
||||
@ -40,10 +40,6 @@ export function AgentPresetCard({
|
||||
day: "2-digit",
|
||||
});
|
||||
})();
|
||||
const categoryBadges = [
|
||||
preset.categoryPath?.productCategory,
|
||||
preset.categoryPath?.audienceTag,
|
||||
].filter(Boolean) as string[];
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -79,16 +75,6 @@ export function AgentPresetCard({
|
||||
<Badge variant="secondary" className="bg-white/90 text-black shadow-sm">
|
||||
{preset.type === "video" ? "视频" : preset.type === "text" ? "生文" : "生图"}
|
||||
</Badge>
|
||||
{preset.secondaryType ? (
|
||||
<Badge key={preset.secondaryType} variant="secondary" className="bg-white/90 text-black shadow-sm">
|
||||
{preset.secondaryType}
|
||||
</Badge>
|
||||
) : null}
|
||||
{categoryBadges.map((item) => (
|
||||
<Badge key={item} variant="secondary" className="bg-white/90 text-black shadow-sm">
|
||||
{item}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
<div className="absolute right-2 top-2 flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
{preset.visibility === "private" && onEdit ? (
|
||||
|
||||
@ -27,10 +27,11 @@ import { fetcher } from "@/lib/utils";
|
||||
import { imageGenerationService } from "@/services/canvas";
|
||||
import { agentService } from "@/services/agents";
|
||||
import type {
|
||||
AgentCreatePayload,
|
||||
AgentPresetType,
|
||||
AgentCategoryTreeConfig,
|
||||
AgentPreset,
|
||||
AgentPresetSlot,
|
||||
AgentUpdatePayload,
|
||||
} from "@/types/agent";
|
||||
|
||||
type DialogSlotState = AgentPresetSlot & {
|
||||
@ -44,12 +45,6 @@ type DialogSlotState = AgentPresetSlot & {
|
||||
interface AgentPresetDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
categoryTreeConfig: AgentCategoryTreeConfig;
|
||||
secondaryTypeConfig: {
|
||||
image: string[];
|
||||
video: string[];
|
||||
text: string[];
|
||||
};
|
||||
initialPreset?: AgentPreset | null;
|
||||
onSaved?: (preset: AgentPreset) => void;
|
||||
}
|
||||
@ -57,8 +52,6 @@ interface AgentPresetDialogProps {
|
||||
export function AgentPresetDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
categoryTreeConfig,
|
||||
secondaryTypeConfig,
|
||||
initialPreset,
|
||||
onSaved,
|
||||
}: AgentPresetDialogProps) {
|
||||
@ -74,19 +67,7 @@ export function AgentPresetDialog({
|
||||
|
||||
const [name, setName] = useState("");
|
||||
const [type, setType] = useState<"image" | "video" | "text">("image");
|
||||
const [secondaryType, setSecondaryType] = useState("");
|
||||
const [visibility, setVisibility] = useState<"public" | "private">("private");
|
||||
const [categoryPath, setCategoryPath] = useState<{
|
||||
productCategory: string;
|
||||
audienceTag: string;
|
||||
usageScenario: string;
|
||||
brand: string;
|
||||
}>({
|
||||
productCategory: "",
|
||||
audienceTag: "",
|
||||
usageScenario: "",
|
||||
brand: "",
|
||||
});
|
||||
const [defaultModelId, setDefaultModelId] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [avatarAssetId, setAvatarAssetId] = useState<string>("");
|
||||
@ -130,10 +111,6 @@ export function AgentPresetDialog({
|
||||
model.supportsImageGen && !videoProviderTypes.has(String(model.providerType || ""))
|
||||
);
|
||||
}, [modelsData?.models, type, videoProviderTypes]);
|
||||
const secondaryTypeOptions = useMemo(
|
||||
() => secondaryTypeConfig[type] || [],
|
||||
[secondaryTypeConfig, type]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
@ -161,14 +138,7 @@ export function AgentPresetDialog({
|
||||
const resetState = () => {
|
||||
setName("");
|
||||
setType("image");
|
||||
setSecondaryType("");
|
||||
setVisibility("private");
|
||||
setCategoryPath({
|
||||
productCategory: "",
|
||||
audienceTag: "",
|
||||
usageScenario: "",
|
||||
brand: "",
|
||||
});
|
||||
setDefaultModelId("");
|
||||
setDescription("");
|
||||
setAvatarAssetId("");
|
||||
@ -197,14 +167,7 @@ export function AgentPresetDialog({
|
||||
|
||||
setName(initialPreset.name || "");
|
||||
setType(initialPreset.type);
|
||||
setSecondaryType(String(initialPreset.secondaryType || ""));
|
||||
setVisibility(initialPreset.visibility);
|
||||
setCategoryPath({
|
||||
productCategory: String(initialPreset.categoryPath?.productCategory || ""),
|
||||
audienceTag: String(initialPreset.categoryPath?.audienceTag || ""),
|
||||
usageScenario: String(initialPreset.categoryPath?.usageScenario || ""),
|
||||
brand: String(initialPreset.categoryPath?.brand || ""),
|
||||
});
|
||||
setDefaultModelId(initialPreset.defaultModelId || "");
|
||||
setDescription(initialPreset.description || "");
|
||||
setAvatarAssetId(initialPreset.avatarAssetId || "");
|
||||
@ -349,17 +312,10 @@ export function AgentPresetDialog({
|
||||
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
const payload = {
|
||||
const basePayload: AgentCreatePayload = {
|
||||
name: name.trim(),
|
||||
type,
|
||||
secondaryType: secondaryType || undefined,
|
||||
visibility,
|
||||
categoryPath: {
|
||||
productCategory: categoryPath.productCategory || undefined,
|
||||
audienceTag: categoryPath.audienceTag || undefined,
|
||||
usageScenario: categoryPath.usageScenario || undefined,
|
||||
brand: categoryPath.brand || undefined,
|
||||
},
|
||||
description: description.trim() || undefined,
|
||||
defaultModelId,
|
||||
slots: slots.map((slot, index) => ({
|
||||
@ -372,11 +328,15 @@ export function AgentPresetDialog({
|
||||
referenceAssetIds: slot.referenceAssetIds || [],
|
||||
})),
|
||||
avatarAssetId: avatarAssetId || undefined,
|
||||
} as const;
|
||||
};
|
||||
|
||||
const result = initialPreset
|
||||
? await agentService.updatePreset(initialPreset.id, payload)
|
||||
: await agentService.createPreset(payload);
|
||||
? await agentService.updatePreset(initialPreset.id, {
|
||||
...basePayload,
|
||||
secondaryType: null,
|
||||
categoryPath: null,
|
||||
} satisfies AgentUpdatePayload)
|
||||
: await agentService.createPreset(basePayload);
|
||||
|
||||
if (!result.success || !result.data) {
|
||||
throw new Error(result.error || (initialPreset ? "更新智能体失败" : "创建智能体失败"));
|
||||
@ -404,7 +364,7 @@ export function AgentPresetDialog({
|
||||
<DialogContent className="max-w-3xl border-slate-200 bg-white opacity-100 shadow-2xl dark:border-slate-800 dark:bg-zinc-950">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{initialPreset ? "编辑智能体" : "新建智能体"}</DialogTitle>
|
||||
<DialogDescription>配置模型、槽位提示词和参考图。</DialogDescription>
|
||||
<DialogDescription>配置模式名称、预览图、模型和槽位提示词。</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<ScrollArea className="max-h-[70vh] pr-3">
|
||||
@ -480,7 +440,6 @@ export function AgentPresetDialog({
|
||||
value={type}
|
||||
onValueChange={(value: AgentPresetType) => {
|
||||
setType(value);
|
||||
setSecondaryType("");
|
||||
setDefaultModelId("");
|
||||
}}
|
||||
>
|
||||
@ -494,52 +453,6 @@ export function AgentPresetDialog({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">二级类型</div>
|
||||
<Select
|
||||
value={secondaryType || "__none__"}
|
||||
onValueChange={(value) => setSecondaryType(value === "__none__" ? "" : value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择二级类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">不选择</SelectItem>
|
||||
{secondaryTypeOptions.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
二级类型由管理员在系统设置中统一配置。
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">产品品类</div>
|
||||
<Select
|
||||
value={categoryPath.productCategory || "__none__"}
|
||||
onValueChange={(value) =>
|
||||
setCategoryPath((prev) => ({
|
||||
...prev,
|
||||
productCategory: value === "__none__" ? "" : value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择产品品类" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">不选择</SelectItem>
|
||||
{categoryTreeConfig.productCategories.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">对应模型</div>
|
||||
<Select value={defaultModelId} onValueChange={setDefaultModelId}>
|
||||
@ -557,81 +470,6 @@ export function AgentPresetDialog({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-3 md:grid-cols-3">
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">人群标签</div>
|
||||
<Select
|
||||
value={categoryPath.audienceTag || "__none__"}
|
||||
onValueChange={(value) =>
|
||||
setCategoryPath((prev) => ({
|
||||
...prev,
|
||||
audienceTag: value === "__none__" ? "" : value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择人群标签" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">不选择</SelectItem>
|
||||
{categoryTreeConfig.audienceTags.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">使用场景</div>
|
||||
<Select
|
||||
value={categoryPath.usageScenario || "__none__"}
|
||||
onValueChange={(value) =>
|
||||
setCategoryPath((prev) => ({
|
||||
...prev,
|
||||
usageScenario: value === "__none__" ? "" : value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择使用场景" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">不选择</SelectItem>
|
||||
{categoryTreeConfig.usageScenarios.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">品牌</div>
|
||||
<Select
|
||||
value={categoryPath.brand || "__none__"}
|
||||
onValueChange={(value) =>
|
||||
setCategoryPath((prev) => ({
|
||||
...prev,
|
||||
brand: value === "__none__" ? "" : value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择品牌" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">不选择</SelectItem>
|
||||
{categoryTreeConfig.brands.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">描述</div>
|
||||
<Textarea
|
||||
|
||||
@ -1,21 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import { Loader2, Plus, Sparkles } from "lucide-react";
|
||||
import useSWR from "swr";
|
||||
import { toast } from "sonner";
|
||||
import { fetcher } from "@/lib/utils";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { agentService } from "@/services/agents";
|
||||
import type { AgentPreset } from "@/types/agent";
|
||||
import { AgentPresetCard } from "@/components/agents/AgentPresetCard";
|
||||
@ -39,33 +31,14 @@ export function AgentPresetPanel({
|
||||
embedded = false,
|
||||
layout = "fill",
|
||||
title = "智能体",
|
||||
description = "选择智能体后,右侧对话框会按其要求显示上传槽位",
|
||||
description = "选择模式后,工作台会自动加载对应槽位和生成能力",
|
||||
searchPlaceholder = "搜索智能体",
|
||||
publicTitle = "公共智能体",
|
||||
privateTitle = "我的智能体",
|
||||
}: AgentPresetPanelProps) {
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [categoryFilters, setCategoryFilters] = useState({
|
||||
productCategory: "",
|
||||
audienceTag: "",
|
||||
usageScenario: "",
|
||||
brand: "",
|
||||
});
|
||||
const [showCreateDialog, setShowCreateDialog] = useState(false);
|
||||
const [editingPreset, setEditingPreset] = useState<AgentPreset | null>(null);
|
||||
const { data: siteSettingsData } = useSWR<{
|
||||
agentCategoryTreeConfig?: {
|
||||
productCategories?: string[];
|
||||
audienceTags?: string[];
|
||||
usageScenarios?: string[];
|
||||
brands?: string[];
|
||||
};
|
||||
agentSecondaryTypeConfig?: {
|
||||
image?: string[];
|
||||
video?: string[];
|
||||
text?: string[];
|
||||
};
|
||||
}>("/api/site-settings", fetcher);
|
||||
const { data, isLoading, mutate } = useSWR(
|
||||
["agent-presets"],
|
||||
async () => {
|
||||
@ -80,56 +53,28 @@ export function AgentPresetPanel({
|
||||
}
|
||||
);
|
||||
|
||||
const categoryTreeConfig = useMemo(
|
||||
() => ({
|
||||
productCategories: siteSettingsData?.agentCategoryTreeConfig?.productCategories || [],
|
||||
audienceTags: siteSettingsData?.agentCategoryTreeConfig?.audienceTags || [],
|
||||
usageScenarios: siteSettingsData?.agentCategoryTreeConfig?.usageScenarios || [],
|
||||
brands: siteSettingsData?.agentCategoryTreeConfig?.brands || [],
|
||||
}),
|
||||
[siteSettingsData?.agentCategoryTreeConfig]
|
||||
);
|
||||
const secondaryTypeConfig = useMemo(
|
||||
() => ({
|
||||
image: siteSettingsData?.agentSecondaryTypeConfig?.image || [],
|
||||
video: siteSettingsData?.agentSecondaryTypeConfig?.video || [],
|
||||
text: siteSettingsData?.agentSecondaryTypeConfig?.text || [],
|
||||
}),
|
||||
[siteSettingsData?.agentSecondaryTypeConfig]
|
||||
);
|
||||
const filteredPublicPresets = useMemo(() => {
|
||||
return (data?.public || []).filter((preset) => {
|
||||
const path = preset.categoryPath || {};
|
||||
const matchedKeyword =
|
||||
!keyword.trim() ||
|
||||
preset.name.toLowerCase().includes(keyword.trim().toLowerCase()) ||
|
||||
[
|
||||
path.productCategory,
|
||||
path.audienceTag,
|
||||
path.usageScenario,
|
||||
path.brand,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
.toLowerCase()
|
||||
.includes(keyword.trim().toLowerCase());
|
||||
const matchedCategory =
|
||||
(!categoryFilters.productCategory || path.productCategory === categoryFilters.productCategory) &&
|
||||
(!categoryFilters.audienceTag || path.audienceTag === categoryFilters.audienceTag) &&
|
||||
(!categoryFilters.usageScenario || path.usageScenario === categoryFilters.usageScenario) &&
|
||||
(!categoryFilters.brand || path.brand === categoryFilters.brand);
|
||||
return matchedCategory && matchedKeyword;
|
||||
const normalizedKeyword = keyword.trim().toLowerCase();
|
||||
if (!normalizedKeyword) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return [preset.name, preset.description || "", preset.type]
|
||||
.filter(Boolean)
|
||||
.some((value) => String(value).toLowerCase().includes(normalizedKeyword));
|
||||
});
|
||||
}, [categoryFilters, data?.public, keyword]);
|
||||
}, [data?.public, keyword]);
|
||||
const filteredPrivatePresets = useMemo(() => {
|
||||
return (data?.private || []).filter((preset) => {
|
||||
const matchedKeyword =
|
||||
!keyword.trim() ||
|
||||
preset.name.toLowerCase().includes(keyword.trim().toLowerCase()) ||
|
||||
String(preset.category?.name || "")
|
||||
.toLowerCase()
|
||||
.includes(keyword.trim().toLowerCase());
|
||||
return matchedKeyword;
|
||||
const normalizedKeyword = keyword.trim().toLowerCase();
|
||||
if (!normalizedKeyword) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return [preset.name, preset.description || "", preset.type]
|
||||
.filter(Boolean)
|
||||
.some((value) => String(value).toLowerCase().includes(normalizedKeyword));
|
||||
});
|
||||
}, [data?.private, keyword]);
|
||||
|
||||
@ -198,95 +143,7 @@ export function AgentPresetPanel({
|
||||
<ScrollArea className="flex-1">
|
||||
<div className="space-y-5 p-4">
|
||||
<section className="space-y-3">
|
||||
<div className="space-y-3">
|
||||
<div className="text-sm font-medium">{publicTitle}</div>
|
||||
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
|
||||
<Select
|
||||
value={categoryFilters.productCategory || "__none__"}
|
||||
onValueChange={(value) =>
|
||||
setCategoryFilters((prev) => ({
|
||||
...prev,
|
||||
productCategory: value === "__none__" ? "" : value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="产品品类" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">全部产品品类</SelectItem>
|
||||
{categoryTreeConfig.productCategories.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select
|
||||
value={categoryFilters.audienceTag || "__none__"}
|
||||
onValueChange={(value) =>
|
||||
setCategoryFilters((prev) => ({
|
||||
...prev,
|
||||
audienceTag: value === "__none__" ? "" : value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="人群标签" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">全部人群标签</SelectItem>
|
||||
{categoryTreeConfig.audienceTags.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select
|
||||
value={categoryFilters.usageScenario || "__none__"}
|
||||
onValueChange={(value) =>
|
||||
setCategoryFilters((prev) => ({
|
||||
...prev,
|
||||
usageScenario: value === "__none__" ? "" : value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="使用场景" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">全部使用场景</SelectItem>
|
||||
{categoryTreeConfig.usageScenarios.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select
|
||||
value={categoryFilters.brand || "__none__"}
|
||||
onValueChange={(value) =>
|
||||
setCategoryFilters((prev) => ({
|
||||
...prev,
|
||||
brand: value === "__none__" ? "" : value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="品牌" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">全部品牌</SelectItem>
|
||||
{categoryTreeConfig.brands.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm font-medium">{publicTitle}</div>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
|
||||
@ -338,95 +195,7 @@ export function AgentPresetPanel({
|
||||
) : (
|
||||
<div className="space-y-5 p-4">
|
||||
<section className="space-y-3">
|
||||
<div className="space-y-3">
|
||||
<div className="text-sm font-medium">{publicTitle}</div>
|
||||
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
|
||||
<Select
|
||||
value={categoryFilters.productCategory || "__none__"}
|
||||
onValueChange={(value) =>
|
||||
setCategoryFilters((prev) => ({
|
||||
...prev,
|
||||
productCategory: value === "__none__" ? "" : value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="产品品类" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">全部产品品类</SelectItem>
|
||||
{categoryTreeConfig.productCategories.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select
|
||||
value={categoryFilters.audienceTag || "__none__"}
|
||||
onValueChange={(value) =>
|
||||
setCategoryFilters((prev) => ({
|
||||
...prev,
|
||||
audienceTag: value === "__none__" ? "" : value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="人群标签" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">全部人群标签</SelectItem>
|
||||
{categoryTreeConfig.audienceTags.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select
|
||||
value={categoryFilters.usageScenario || "__none__"}
|
||||
onValueChange={(value) =>
|
||||
setCategoryFilters((prev) => ({
|
||||
...prev,
|
||||
usageScenario: value === "__none__" ? "" : value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="使用场景" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">全部使用场景</SelectItem>
|
||||
{categoryTreeConfig.usageScenarios.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select
|
||||
value={categoryFilters.brand || "__none__"}
|
||||
onValueChange={(value) =>
|
||||
setCategoryFilters((prev) => ({
|
||||
...prev,
|
||||
brand: value === "__none__" ? "" : value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="品牌" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">全部品牌</SelectItem>
|
||||
{categoryTreeConfig.brands.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm font-medium">{publicTitle}</div>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
|
||||
@ -487,8 +256,6 @@ export function AgentPresetPanel({
|
||||
}
|
||||
setShowCreateDialog(true);
|
||||
}}
|
||||
categoryTreeConfig={categoryTreeConfig}
|
||||
secondaryTypeConfig={secondaryTypeConfig}
|
||||
initialPreset={editingPreset}
|
||||
onSaved={async (preset) => {
|
||||
await mutate();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user