mirror of
https://github.com/langgenius/dify.git
synced 2026-03-12 10:38:54 +08:00
refactor(skill): remove tags/icons/categories, use kebab-case folder names
Drop CategoryTabs component, SkillTemplateTag type, icon/tags fields, and UI_CONFIG from the fetch script. Upload folders now use the kebab-case skill id (e.g. "skill-creator") instead of the display name. Card shows the human-readable name from SKILL.md frontmatter while the created folder uses the id for consistent naming.
This commit is contained in:
@ -1,44 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { memo } from 'react'
|
||||
import TabItem from './tab-item'
|
||||
|
||||
export type TemplateCategory = {
|
||||
id: string
|
||||
label: string
|
||||
}
|
||||
const CATEGORIES: TemplateCategory[] = [
|
||||
{ id: 'all', label: 'All' },
|
||||
{ id: 'document', label: 'Document' },
|
||||
{ id: 'productivity', label: 'Productivity' },
|
||||
{ id: 'development', label: 'Development' },
|
||||
{ id: 'design', label: 'Design' },
|
||||
{ id: 'creative', label: 'Creative' },
|
||||
]
|
||||
|
||||
type CategoryTabsProps = {
|
||||
categories?: TemplateCategory[]
|
||||
activeCategory: string
|
||||
onCategoryChange: (categoryId: string) => void
|
||||
}
|
||||
|
||||
const CategoryTabs = ({
|
||||
categories = CATEGORIES,
|
||||
activeCategory,
|
||||
onCategoryChange,
|
||||
}: CategoryTabsProps) => {
|
||||
return (
|
||||
<div className="flex flex-1 items-center gap-1">
|
||||
{categories.map(category => (
|
||||
<TabItem
|
||||
key={category.id}
|
||||
label={category.label}
|
||||
isActive={activeCategory === category.id}
|
||||
onClick={() => onCategoryChange(category.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(CategoryTabs)
|
||||
@ -1,46 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { memo } from 'react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type TabItemProps = {
|
||||
label: string
|
||||
isActive: boolean
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
const TabItem = ({
|
||||
label,
|
||||
isActive,
|
||||
onClick,
|
||||
}: TabItemProps) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'grid shrink-0 rounded-lg px-3 py-2 transition-colors',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-components-input-border-active',
|
||||
isActive
|
||||
? 'bg-state-base-active'
|
||||
: 'hover:bg-state-base-hover',
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<span className="system-sm-semibold col-start-1 row-start-1 opacity-0" aria-hidden="true">
|
||||
{label}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
'col-start-1 row-start-1',
|
||||
isActive
|
||||
? 'system-sm-semibold text-text-primary'
|
||||
: 'system-sm-medium text-text-tertiary',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(TabItem)
|
||||
@ -8,7 +8,6 @@ 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'
|
||||
import TemplateCard from './template-card'
|
||||
import TemplateSearch from './template-search'
|
||||
@ -17,7 +16,6 @@ import { buildUploadDataFromTemplate } from './templates/template-to-upload'
|
||||
|
||||
const SkillTemplatesSection = () => {
|
||||
const { t } = useTranslation('workflow')
|
||||
const [activeCategory, setActiveCategory] = useState('all')
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [loadingId, setLoadingId] = useState<string | null>(null)
|
||||
|
||||
@ -37,7 +35,7 @@ const SkillTemplatesSection = () => {
|
||||
|
||||
const handleUse = useCallback(async (summary: SkillTemplateSummary) => {
|
||||
const entry = SKILL_TEMPLATES.find(e => e.id === summary.id)
|
||||
if (!entry || !appId || existingNamesRef.current?.has(summary.name))
|
||||
if (!entry || !appId || existingNamesRef.current?.has(summary.id))
|
||||
return
|
||||
|
||||
setLoadingId(summary.id)
|
||||
@ -46,7 +44,7 @@ const SkillTemplatesSection = () => {
|
||||
|
||||
try {
|
||||
const children = await entry.loadContent()
|
||||
const uploadData = await buildUploadDataFromTemplate(summary.name, children)
|
||||
const uploadData = await buildUploadDataFromTemplate(summary.id, children)
|
||||
|
||||
await batchUploadRef.current.mutateAsync({
|
||||
appId,
|
||||
@ -69,15 +67,14 @@ const SkillTemplatesSection = () => {
|
||||
}
|
||||
}, [appId, storeApi])
|
||||
|
||||
const filtered = useMemo(() => SKILL_TEMPLATES.filter((entry) => {
|
||||
if (searchQuery) {
|
||||
const q = searchQuery.toLowerCase()
|
||||
return entry.name.toLowerCase().includes(q) || entry.description.toLowerCase().includes(q)
|
||||
}
|
||||
if (activeCategory !== 'all')
|
||||
return entry.tags?.some(tag => tag.toLowerCase() === activeCategory.toLowerCase())
|
||||
return true
|
||||
}), [searchQuery, activeCategory])
|
||||
const filtered = useMemo(() => {
|
||||
if (!searchQuery)
|
||||
return SKILL_TEMPLATES
|
||||
const q = searchQuery.toLowerCase()
|
||||
return SKILL_TEMPLATES.filter(entry =>
|
||||
entry.name.toLowerCase().includes(q) || entry.description.toLowerCase().includes(q),
|
||||
)
|
||||
}, [searchQuery])
|
||||
|
||||
return (
|
||||
<section className="flex flex-col gap-3 px-6 py-2">
|
||||
@ -85,21 +82,13 @@ const SkillTemplatesSection = () => {
|
||||
title={t('skill.startTab.templatesTitle')}
|
||||
description={t('skill.startTab.templatesDesc')}
|
||||
/>
|
||||
<div className="flex w-full items-start gap-1">
|
||||
<CategoryTabs
|
||||
activeCategory={activeCategory}
|
||||
onCategoryChange={setActiveCategory}
|
||||
/>
|
||||
<TemplateSearch
|
||||
onChange={setSearchQuery}
|
||||
/>
|
||||
</div>
|
||||
<TemplateSearch onChange={setSearchQuery} />
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{filtered.map(entry => (
|
||||
<TemplateCard
|
||||
key={entry.id}
|
||||
template={entry}
|
||||
added={existingNames?.has(entry.name) ?? false}
|
||||
added={existingNames?.has(entry.id) ?? false}
|
||||
disabled={loadingId !== null}
|
||||
loading={loadingId === entry.id}
|
||||
onUse={handleUse}
|
||||
|
||||
@ -5,7 +5,6 @@ import { RiAddLine, RiCheckLine } from '@remixicon/react'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
type TemplateCardProps = {
|
||||
@ -24,7 +23,7 @@ const TemplateCard = ({ template, added, disabled, loading, onUse }: TemplateCar
|
||||
<div className="flex items-center gap-3 px-4 pb-2 pt-4">
|
||||
<AppIcon
|
||||
size="large"
|
||||
icon={template.icon || '📁'}
|
||||
icon="📙"
|
||||
className="!bg-components-icon-bg-violet-soft"
|
||||
/>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-0.5 py-px">
|
||||
@ -42,15 +41,7 @@ const TemplateCard = ({ template, added, disabled, loading, onUse }: TemplateCar
|
||||
</p>
|
||||
</div>
|
||||
<div className="relative px-4 pb-4">
|
||||
{template.tags?.length
|
||||
? (
|
||||
<div className="flex flex-wrap gap-1 transition-opacity group-hover:opacity-0">
|
||||
{template.tags.map(tag => (
|
||||
<Badge key={tag} className="badge-s" uppercase>{tag}</Badge>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
: <div className="h-[18px]" />}
|
||||
<div className="h-[18px]" />
|
||||
<div className="pointer-events-none absolute inset-0 flex items-end px-4 pb-4 opacity-0 transition-opacity group-hover:pointer-events-auto group-hover:opacity-100">
|
||||
{added
|
||||
? (
|
||||
|
||||
@ -8,8 +8,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'Algorithmic Art',
|
||||
description: 'Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists\' work to avoid copyright violations.',
|
||||
fileCount: 3,
|
||||
icon: '✨',
|
||||
tags: ['Creative', 'Development'],
|
||||
loadContent: () => import('./skills/algorithmic-art').then(m => m.default),
|
||||
},
|
||||
{
|
||||
@ -17,8 +15,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'Brand Guidelines',
|
||||
description: 'Applies Anthropic\'s official brand colors and typography to any sort of artifact that may benefit from having Anthropic\'s look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.',
|
||||
fileCount: 1,
|
||||
icon: '🏷️',
|
||||
tags: ['Design', 'Productivity'],
|
||||
loadContent: () => import('./skills/brand-guidelines').then(m => m.default),
|
||||
},
|
||||
{
|
||||
@ -26,8 +22,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'Canvas Design',
|
||||
description: 'Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists\' work to avoid copyright violations.',
|
||||
fileCount: 82,
|
||||
icon: '🖼️',
|
||||
tags: ['Design', 'Creative'],
|
||||
loadContent: () => import('./skills/canvas-design').then(m => m.default),
|
||||
},
|
||||
{
|
||||
@ -35,8 +29,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'Doc Co-authoring',
|
||||
description: 'Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content. This workflow helps users efficiently transfer context, refine content through iteration, and verify the doc works for readers. Trigger when user mentions writing docs, creating proposals, drafting specs, or similar documentation tasks.',
|
||||
fileCount: 1,
|
||||
icon: '📋',
|
||||
tags: ['Productivity'],
|
||||
loadContent: () => import('./skills/doc-coauthoring').then(m => m.default),
|
||||
},
|
||||
{
|
||||
@ -44,8 +36,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'DOCX',
|
||||
description: 'Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When Claude needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks',
|
||||
fileCount: 58,
|
||||
icon: '📝',
|
||||
tags: ['Document', 'Productivity'],
|
||||
loadContent: () => import('./skills/docx').then(m => m.default),
|
||||
},
|
||||
{
|
||||
@ -53,8 +43,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'Frontend Design',
|
||||
description: 'Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.',
|
||||
fileCount: 1,
|
||||
icon: '🎨',
|
||||
tags: ['Development', 'Design'],
|
||||
loadContent: () => import('./skills/frontend-design').then(m => m.default),
|
||||
},
|
||||
{
|
||||
@ -62,8 +50,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'Internal Comms',
|
||||
description: 'A set of resources to help me write all kinds of internal communications, using the formats that my company likes to use. Claude should use this skill whenever asked to write some sort of internal communications (status reports, leadership updates, 3P updates, company newsletters, FAQs, incident reports, project updates, etc.).',
|
||||
fileCount: 5,
|
||||
icon: '💬',
|
||||
tags: ['Productivity'],
|
||||
loadContent: () => import('./skills/internal-comms').then(m => m.default),
|
||||
},
|
||||
{
|
||||
@ -71,8 +57,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'MCP Builder',
|
||||
description: 'Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).',
|
||||
fileCount: 9,
|
||||
icon: '🔌',
|
||||
tags: ['Development'],
|
||||
loadContent: () => import('./skills/mcp-builder').then(m => m.default),
|
||||
},
|
||||
{
|
||||
@ -80,8 +64,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'PDF',
|
||||
description: 'Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale.',
|
||||
fileCount: 11,
|
||||
icon: '📄',
|
||||
tags: ['Document', 'Productivity'],
|
||||
loadContent: () => import('./skills/pdf').then(m => m.default),
|
||||
},
|
||||
{
|
||||
@ -89,8 +71,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'PPTX',
|
||||
description: 'Presentation creation, editing, and analysis. When Claude needs to work with presentations (.pptx files) for: (1) Creating new presentations, (2) Modifying or editing content, (3) Working with layouts, (4) Adding comments or speaker notes, or any other presentation tasks',
|
||||
fileCount: 55,
|
||||
icon: '📊',
|
||||
tags: ['Document', 'Productivity'],
|
||||
loadContent: () => import('./skills/pptx').then(m => m.default),
|
||||
},
|
||||
{
|
||||
@ -98,8 +78,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'Skill Creator',
|
||||
description: 'Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude\'s capabilities with specialized knowledge, workflows, or tool integrations.',
|
||||
fileCount: 6,
|
||||
icon: '🛠️',
|
||||
tags: ['Development'],
|
||||
loadContent: () => import('./skills/skill-creator').then(m => m.default),
|
||||
},
|
||||
{
|
||||
@ -107,8 +85,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'Slack GIF Creator',
|
||||
description: 'Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like "make me a GIF of X doing Y for Slack.',
|
||||
fileCount: 6,
|
||||
icon: '🎬',
|
||||
tags: ['Creative', 'Productivity'],
|
||||
loadContent: () => import('./skills/slack-gif-creator').then(m => m.default),
|
||||
},
|
||||
{
|
||||
@ -116,8 +92,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'Theme Factory',
|
||||
description: 'Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors/fonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.',
|
||||
fileCount: 12,
|
||||
icon: '🎭',
|
||||
tags: ['Design'],
|
||||
loadContent: () => import('./skills/theme-factory').then(m => m.default),
|
||||
},
|
||||
{
|
||||
@ -125,8 +99,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'Web Artifacts Builder',
|
||||
description: 'Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.',
|
||||
fileCount: 4,
|
||||
icon: '🌐',
|
||||
tags: ['Development', 'Design'],
|
||||
loadContent: () => import('./skills/web-artifacts-builder').then(m => m.default),
|
||||
},
|
||||
{
|
||||
@ -134,8 +106,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'Webapp Testing',
|
||||
description: 'Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.',
|
||||
fileCount: 5,
|
||||
icon: '🧪',
|
||||
tags: ['Development'],
|
||||
loadContent: () => import('./skills/webapp-testing').then(m => m.default),
|
||||
},
|
||||
{
|
||||
@ -143,8 +113,6 @@ export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
|
||||
name: 'XLSX',
|
||||
description: 'Comprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work with spreadsheets (.xlsx, .xlsm, .csv, .tsv, etc) for: (1) Creating new spreadsheets with formulas and formatting, (2) Reading or analyzing data, (3) Modify existing spreadsheets while preserving formulas, (4) Data analysis and visualization in spreadsheets, or (5) Recalculating formulas',
|
||||
fileCount: 2,
|
||||
icon: '📈',
|
||||
tags: ['Document', 'Productivity'],
|
||||
loadContent: () => import('./skills/xlsx').then(m => m.default),
|
||||
},
|
||||
]
|
||||
|
||||
@ -20,15 +20,8 @@ export type SkillTemplateSummary = {
|
||||
name: string
|
||||
description: string
|
||||
fileCount: number
|
||||
icon?: string
|
||||
tags?: string[]
|
||||
}
|
||||
|
||||
export type SkillTemplateEntry = SkillTemplateSummary & {
|
||||
loadContent: () => Promise<SkillTemplateNode[]>
|
||||
}
|
||||
|
||||
export type SkillTemplateTag = {
|
||||
id: string
|
||||
label: string
|
||||
}
|
||||
|
||||
@ -64,25 +64,6 @@ const TEXT_EXTENSIONS = new Set([
|
||||
|
||||
const SKIP_FILES = new Set(['LICENSE.txt'])
|
||||
|
||||
const UI_CONFIG: Record<string, { displayName: string, icon: string, tags: string[] }> = {
|
||||
'pdf': { displayName: 'PDF', icon: '📄', tags: ['Document', 'Productivity'] },
|
||||
'docx': { displayName: 'DOCX', icon: '📝', tags: ['Document', 'Productivity'] },
|
||||
'pptx': { displayName: 'PPTX', icon: '📊', tags: ['Document', 'Productivity'] },
|
||||
'xlsx': { displayName: 'XLSX', icon: '📈', tags: ['Document', 'Productivity'] },
|
||||
'frontend-design': { displayName: 'Frontend Design', icon: '🎨', tags: ['Development', 'Design'] },
|
||||
'canvas-design': { displayName: 'Canvas Design', icon: '🖼️', tags: ['Design', 'Creative'] },
|
||||
'algorithmic-art': { displayName: 'Algorithmic Art', icon: '✨', tags: ['Creative', 'Development'] },
|
||||
'mcp-builder': { displayName: 'MCP Builder', icon: '🔌', tags: ['Development'] },
|
||||
'web-artifacts-builder': { displayName: 'Web Artifacts Builder', icon: '🌐', tags: ['Development', 'Design'] },
|
||||
'doc-coauthoring': { displayName: 'Doc Co-authoring', icon: '📋', tags: ['Productivity'] },
|
||||
'skill-creator': { displayName: 'Skill Creator', icon: '🛠️', tags: ['Development'] },
|
||||
'webapp-testing': { displayName: 'Webapp Testing', icon: '🧪', tags: ['Development'] },
|
||||
'slack-gif-creator': { displayName: 'Slack GIF Creator', icon: '🎬', tags: ['Creative', 'Productivity'] },
|
||||
'theme-factory': { displayName: 'Theme Factory', icon: '🎭', tags: ['Design'] },
|
||||
'brand-guidelines': { displayName: 'Brand Guidelines', icon: '🏷️', tags: ['Design', 'Productivity'] },
|
||||
'internal-comms': { displayName: 'Internal Comms', icon: '💬', tags: ['Productivity'] },
|
||||
}
|
||||
|
||||
type FileEntry = {
|
||||
name: string
|
||||
node_type: 'file'
|
||||
@ -212,16 +193,11 @@ function generateRegistryFile(metas: SkillMeta[]): string {
|
||||
lines.push('export const SKILL_TEMPLATES: SkillTemplateEntry[] = [')
|
||||
|
||||
for (const meta of metas) {
|
||||
const config = UI_CONFIG[meta.id] || { displayName: '', icon: '📁', tags: [] }
|
||||
const displayName = config.displayName || meta.name
|
||||
const tagsStr = `[${config.tags.map(t => sq(t)).join(', ')}]`
|
||||
lines.push(' {')
|
||||
lines.push(` id: ${sq(meta.id)},`)
|
||||
lines.push(` name: ${sq(displayName)},`)
|
||||
lines.push(` name: ${sq(meta.name)},`)
|
||||
lines.push(` description: ${sq(meta.description)},`)
|
||||
lines.push(` fileCount: ${meta.fileCount},`)
|
||||
lines.push(` icon: ${sq(config.icon)},`)
|
||||
lines.push(` tags: ${tagsStr},`)
|
||||
lines.push(` loadContent: () => import(${sq(`./skills/${meta.id}`)}).then(m => m.default),`)
|
||||
lines.push(' },')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user