diff --git a/web/src/components/ai/ai-message-markdown.tsx b/web/src/components/ai/ai-message-markdown.tsx index 9e749c6bc..7e6f6bbc2 100644 --- a/web/src/components/ai/ai-message-markdown.tsx +++ b/web/src/components/ai/ai-message-markdown.tsx @@ -9,6 +9,8 @@ type AIMessageMarkdownProps = { isStreaming?: boolean; streamingAnimation?: "word" | "char" | "none"; className?: string; + /** 仅覆盖当前调用方需要定制的 Markdown 渲染器,默认渲染行为保持不变。 */ + components?: Components; }; function buildComponents(isStreaming: boolean): Components { @@ -37,7 +39,7 @@ function buildComponents(isStreaming: boolean): Components { const staticComponents = buildComponents(false); const streamingComponents = buildComponents(true); -export const AIMessageMarkdown = memo(function AIMessageMarkdown({ children, isStreaming = false, streamingAnimation = "word", className = "" }: AIMessageMarkdownProps) { +export const AIMessageMarkdown = memo(function AIMessageMarkdown({ children, isStreaming = false, streamingAnimation = "word", className = "", components }: AIMessageMarkdownProps) { if (!children.trim()) return null; return ( {children} diff --git a/web/src/components/canvas/canvas-agent-skill-library-modal.tsx b/web/src/components/canvas/canvas-agent-skill-library-modal.tsx index a2430222a..40777b05d 100644 --- a/web/src/components/canvas/canvas-agent-skill-library-modal.tsx +++ b/web/src/components/canvas/canvas-agent-skill-library-modal.tsx @@ -4,7 +4,7 @@ import { Check, LoaderCircle, Plus, Search, Sparkles, Users } from "lucide-react import { AppModal } from "@/components/ui/product/app-modal"; import type { CanvasTheme } from "@/lib/canvas-theme"; -import type { Skill, SkillCategory } from "@/services/api/skills"; +import type { Skill, SkillCategory, SkillLibraryCategory } from "@/services/api/skills"; type SkillLibraryTab = "enabled" | "installed" | "market"; @@ -15,6 +15,7 @@ type CanvasAgentSkillLibraryModalProps = { marketSkills: Skill[]; selectedSkillIds: string[]; categories: SkillCategory[]; + libraryCategories: SkillLibraryCategory[]; category: string; search: string; loading: boolean; @@ -34,6 +35,7 @@ export function CanvasAgentSkillLibraryModal({ marketSkills, selectedSkillIds, categories, + libraryCategories, category, search, loading, @@ -46,36 +48,53 @@ export function CanvasAgentSkillLibraryModal({ onLoadMore, }: CanvasAgentSkillLibraryModalProps) { const [tab, setTab] = useState("market"); + const [libraryCategoryId, setLibraryCategoryId] = useState("all"); const listRef = useRef(null); const loadMoreRef = useRef(null); const wasOpenRef = useRef(false); const selectedCount = selectedSkillIds.length; useEffect(() => { - if (open && !wasOpenRef.current) setTab("market"); + if (open && !wasOpenRef.current) { + setTab("market"); + setLibraryCategoryId("all"); + onCategoryChange("all"); + } wasOpenRef.current = open; - }, [open]); + }, [onCategoryChange, open]); useEffect(() => { listRef.current?.scrollTo({ top: 0 }); - }, [category, search, tab]); + }, [category, libraryCategoryId, search, tab]); + + const librarySource = useMemo(() => tab === "enabled" + ? installedSkills.filter((skill) => selectedSkillIds.includes(skill.skillId)) + : installedSkills, [installedSkills, selectedSkillIds, tab]); + + const libraryCategoryCounts = useMemo(() => { + const counts = new Map(); + librarySource.forEach((skill) => { + const categoryId = skill.libraryCategoryId || "__uncategorized__"; + counts.set(categoryId, (counts.get(categoryId) || 0) + 1); + }); + return counts; + }, [librarySource]); const visibleSkills = useMemo(() => { const keyword = search.trim().toLocaleLowerCase("zh-CN"); - const source = tab === "enabled" - ? installedSkills.filter((skill) => selectedSkillIds.includes(skill.skillId)) - : tab === "installed" - ? installedSkills - : marketSkills; - + const source = tab === "market" ? marketSkills : librarySource; return source.filter((skill) => { - if (category !== "all" && skill.tag !== category) return false; + if (tab === "market" && category !== "all" && skill.tag !== category) return false; + if (tab !== "market") { + if (libraryCategoryId === "__uncategorized__" && skill.libraryCategoryId) return false; + if (libraryCategoryId !== "all" && libraryCategoryId !== "__uncategorized__" && skill.libraryCategoryId !== libraryCategoryId) return false; + } if (!keyword) return true; - return `${skill.skillName} ${skill.description || ""} ${skill.effectiveUser?.name || ""}` + return `${skill.skillName} ${skill.description || ""} ${skill.instruction || ""} ${skill.extraInfo || ""} ${skill.effectiveUser?.name || ""} ${categories.find((item) => item.value === skill.tag)?.label || ""}` .toLocaleLowerCase("zh-CN") .includes(keyword); }); - }, [category, installedSkills, marketSkills, search, selectedSkillIds, tab]); + }, [categories, category, libraryCategoryId, librarySource, marketSkills, search, tab]); useEffect(() => { const target = loadMoreRef.current; @@ -95,14 +114,32 @@ export function CanvasAgentSkillLibraryModal({ return [...unique.values()]; }, [categories]); + const marketplaceCategoryTotal = useMemo( + () => categoryItems.reduce((total, item) => total + (item.count || 0), 0), + [categoryItems], + ); + const emptyText = tab === "enabled" - ? "本轮还没有启用技能,可在“我的技能”中选择" + ? selectedCount === 0 ? "本轮还没有启用技能,可在“我的技能”中选择" : "没有匹配的已启用技能" : tab === "installed" ? "还没有匹配的已加入技能,可前往“全部”添加" : "没有匹配的公开技能,换个关键词或分类试试"; const changeTab = (value: SkillLibraryTab) => { setTab(value); + setLibraryCategoryId("all"); + onCategoryChange("all"); + }; + + const selectMarketplaceCategory = (value: string) => { + setTab("market"); + setLibraryCategoryId("all"); + onCategoryChange(value); + }; + + const selectLibraryCategory = (value: string) => { + setTab((current) => current === "enabled" ? "enabled" : "installed"); + setLibraryCategoryId(value); onCategoryChange("all"); }; @@ -144,20 +181,34 @@ export function CanvasAgentSkillLibraryModal({ className="canvas-agent-skill-library-search" /> @@ -199,7 +250,7 @@ export function CanvasAgentSkillLibraryModal({