From e9608532bdcf22a3f4580fa4afa22362aec84049 Mon Sep 17 00:00:00 2001 From: yyh Date: Fri, 30 Jan 2026 15:43:37 +0800 Subject: [PATCH] feat(skill): guard template cards against duplicate skill addition Add useExistingSkillNames hook that derives root folder names from the cached asset tree via TanStack Query select, then use it to show an "Added" state on hover for already-present skills and block re-upload. --- .../skill/hooks/use-skill-asset-tree.ts | 20 +++++++++ .../start-tab/skill-templates-section.tsx | 8 +++- .../skill/start-tab/template-card.tsx | 41 +++++++++++++------ web/i18n/en-US/workflow.json | 1 + web/i18n/zh-Hans/workflow.json | 1 + 5 files changed, 57 insertions(+), 14 deletions(-) diff --git a/web/app/components/workflow/skill/hooks/use-skill-asset-tree.ts b/web/app/components/workflow/skill/hooks/use-skill-asset-tree.ts index 65da8d5e3b..32a5687705 100644 --- a/web/app/components/workflow/skill/hooks/use-skill-asset-tree.ts +++ b/web/app/components/workflow/skill/hooks/use-skill-asset-tree.ts @@ -35,3 +35,23 @@ export function useSkillAssetNodeMap() { }, }) } + +/** + * Hook to get the set of root-level folder names in the skill asset tree. + * Useful for checking whether a skill template has already been added. + */ +export function useExistingSkillNames() { + const appId = useSkillAppId() + return useGetAppAssetTree(appId, { + select: (data: AppAssetTreeResponse): Set => { + if (!data?.children) + return new Set() + const names = new Set() + for (const node of data.children) { + if (node.node_type === 'folder') + names.add(node.name) + } + return names + }, + }) +} diff --git a/web/app/components/workflow/skill/start-tab/skill-templates-section.tsx b/web/app/components/workflow/skill/start-tab/skill-templates-section.tsx index cf176d5183..033a14485c 100644 --- a/web/app/components/workflow/skill/start-tab/skill-templates-section.tsx +++ b/web/app/components/workflow/skill/start-tab/skill-templates-section.tsx @@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' import { useWorkflowStore } from '@/app/components/workflow/store' import { useBatchUpload } from '@/service/use-app-asset' +import { useExistingSkillNames } from '../hooks/use-skill-asset-tree' import { useSkillTreeUpdateEmitter } from '../hooks/use-skill-tree-collaboration' import CategoryTabs from './category-tabs' import SectionHeader from './section-header' @@ -30,9 +31,13 @@ const SkillTemplatesSection = () => { const emitTreeUpdateRef = useRef(emitTreeUpdate) emitTreeUpdateRef.current = emitTreeUpdate + const { data: existingNames } = useExistingSkillNames() + const existingNamesRef = useRef(existingNames) + existingNamesRef.current = existingNames + const handleUse = useCallback(async (summary: SkillTemplateSummary) => { const entry = SKILL_TEMPLATES.find(e => e.id === summary.id) - if (!entry || !appId) + if (!entry || !appId || existingNamesRef.current?.has(summary.name)) return setLoadingId(summary.id) @@ -94,6 +99,7 @@ const SkillTemplatesSection = () => { void } -const TemplateCard = ({ template, disabled, loading, onUse }: TemplateCardProps) => { +const TemplateCard = ({ template, added, disabled, loading, onUse }: TemplateCardProps) => { const { t } = useTranslation('workflow') return ( @@ -51,17 +52,31 @@ const TemplateCard = ({ template, disabled, loading, onUse }: TemplateCardProps) ) :
}
- + {added + ? ( + + ) + : ( + + )}
diff --git a/web/i18n/en-US/workflow.json b/web/i18n/en-US/workflow.json index 0ba19d86db..723027fc9d 100644 --- a/web/i18n/en-US/workflow.json +++ b/web/i18n/en-US/workflow.json @@ -1078,6 +1078,7 @@ "skill.startTab.importSkill": "Import Skill", "skill.startTab.importSkillDesc": "Import skill from skill.zip file", "skill.startTab.searchPlaceholder": "Search…", + "skill.startTab.skillAdded": "Added", "skill.startTab.templatesComingSoon": "Templates coming soon…", "skill.startTab.templatesDesc": "Choose a template to bootstrap your agent's capabilities", "skill.startTab.templatesTitle": "Skill Templates", diff --git a/web/i18n/zh-Hans/workflow.json b/web/i18n/zh-Hans/workflow.json index 0b2f03ccaa..8371beed46 100644 --- a/web/i18n/zh-Hans/workflow.json +++ b/web/i18n/zh-Hans/workflow.json @@ -1070,6 +1070,7 @@ "skill.startTab.importSkill": "导入 Skill", "skill.startTab.importSkillDesc": "从 skill.zip 文件导入", "skill.startTab.searchPlaceholder": "搜索…", + "skill.startTab.skillAdded": "已添加", "skill.startTab.templatesComingSoon": "模板即将推出…", "skill.startTab.templatesDesc": "选择模板来快速构建你的 Agent 能力", "skill.startTab.templatesTitle": "Skill 模板",