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 模板",