feat: 完善标签分类与领域场景归一化

This commit is contained in:
2026-02-22 12:03:11 +08:00
parent 285af9a039
commit ef527d69ce
5 changed files with 666 additions and 65 deletions
@@ -405,7 +405,8 @@ export async function upsertTags(tags: ProjectInput['tags']) {
})
const shouldUpdateNameEn = incomingTag.nameEn && !matchedTag.nameEn
const shouldUpdateCategory =
matchedTag.category === 'FREE_TAG' && inferredCategory !== 'FREE_TAG'
matchedTag.category !== inferredCategory &&
['FREE_TAG', 'RESOURCE_TYPE', 'PROTOCOL_INTERFACE'].includes(matchedTag.category)
if (shouldUpdateNameEn || shouldUpdateCategory) {
const updatedTag = await prisma.tag.update({
+157 -63
View File
@@ -23,6 +23,71 @@ export const FIXED_PROJECT_TYPE_TAGS = [
},
] as const
export const DOMAIN_SCENARIO_PRESET_TAGS = [
{
slug: 'code-dev',
name: '开发者工具/代码',
nameEn: 'Developer Tools & Coding',
},
{
slug: 'automation-workflow',
name: '自动化/工作流/RPA',
nameEn: 'Automation, Workflow & RPA',
},
{
slug: 'knowledge-rag',
name: '知识管理/检索/RAG',
nameEn: 'Knowledge Management, Retrieval & RAG',
},
{
slug: 'education-research',
name: '教育/研究资源',
nameEn: 'Education & Research Resources',
},
{
slug: 'model-inference',
name: '模型训练/推理',
nameEn: 'Model Training & Inference',
},
{
slug: 'api-integration',
name: '协议/API/集成',
nameEn: 'Protocol, API & Integration',
},
{
slug: 'vision-multimodal',
name: '计算机视觉/多模态',
nameEn: 'Computer Vision & Multimodal',
},
{
slug: 'data-bi',
name: '数据分析/BI/可视化',
nameEn: 'Data Analytics, BI & Visualization',
},
{
slug: 'security-privacy',
name: '安全/隐私',
nameEn: 'Security & Privacy',
},
{
slug: 'enterprise-office',
name: '企业应用/办公',
nameEn: 'Enterprise Applications & Office',
},
{
slug: 'finance',
name: '金融',
nameEn: 'Finance',
},
{
slug: 'medical-biomed',
name: '医疗/生物医药',
nameEn: 'Medical & Biomedicine',
},
] as const
export type DomainScenarioPresetSlug = (typeof DOMAIN_SCENARIO_PRESET_TAGS)[number]['slug']
export type FixedProjectTypeSlug = (typeof FIXED_PROJECT_TYPE_TAGS)[number]['slug']
export const FIXED_PROJECT_TYPE_SLUGS = new Set<FixedProjectTypeSlug>(
@@ -117,34 +182,34 @@ const TECH_STACK_SLUGS = new Set([
'bun',
'langchain',
'langgraph',
'pytorch',
'transformers',
'llamaindex',
'agent-framework',
'vllm',
'litellm',
'playwright',
'chromadb',
'gradio',
'streamlit',
'vuejs',
])
const AI_PARADIGM_SLUGS = new Set([
'llm',
'ai-agents',
'multi-agent-system',
'大语言模型',
'rag',
'transformers',
'pytorch',
'machine-learning',
'deep-learning',
'reinforcement-learning',
'multimodal',
'multimodal-ai',
'multi-agent-system',
'multi-agent',
'autonomous-agents',
'vllm',
'llamaindex',
'agent-framework',
'ai-agent-framework',
'ai-agents',
'natural-language-processing',
'model-context-protocol',
'sdk',
])
const PROTOCOL_INTERFACE_SLUGS = new Set([
'model-context-protocol',
'mcp-protocol',
'openai-api',
'sdk',
'api-gateway',
])
@@ -154,28 +219,39 @@ const PRODUCT_FORM_SLUGS = new Set([
'桌面应用',
'vs-code-extension',
'workflow-automation',
'browser-automation',
'knowledge-base',
'automation',
])
const DOMAIN_SCENARIO_SLUGS = new Set([
...DOMAIN_SCENARIO_PRESET_TAGS.map((item) => item.slug),
'data-analytics',
'数据分析',
'knowledge-management',
'知识管理',
'ai-security',
'natural-language-processing',
'nlp',
'knowledge-graph',
'computer-vision',
'visualization',
'enterprise-ai',
'chatbot',
])
const RESOURCE_TYPE_SLUGS = new Set([
'knowledge-base',
'markdown',
'ai-research',
'guide',
'tutorial',
'paper',
])
const RESOURCE_TYPE_SLUGS = new Set<string>([])
const TAG_SLUG_ALIAS_MAP: Record<string, string> = {
mcp: 'model-context-protocol',
'mcp-protocol': 'model-context-protocol',
llm: '大语言模型',
'llm-applications': '大语言模型',
'autonomous-agents': 'ai-agents',
'multi-agent': 'multi-agent-system',
'multimodal-ai': 'multimodal',
'ai-agent-framework': 'agent-framework',
'workflow-orchestration': 'workflow-automation',
'web-automation': 'browser-automation',
}
const TECH_STACK_KEYWORDS = [
'python',
@@ -210,13 +286,14 @@ const AI_PARADIGM_KEYWORDS = [
'multi agent',
'智能体',
'agent',
'mcp',
'sdk',
]
const PROTOCOL_INTERFACE_KEYWORDS = [
'mcp',
'api gateway',
'gateway',
'protocol',
'api',
'sdk',
'接口',
'协议',
]
@@ -235,6 +312,24 @@ const PRODUCT_FORM_KEYWORDS = [
]
const DOMAIN_SCENARIO_KEYWORDS = [
'domain',
'scenario',
'workflow',
'automation',
'rag',
'retrieval',
'developer',
'coding',
'medical',
'health',
'biomed',
'financial',
'trading',
'enterprise',
'office',
'api integration',
'visualization',
'dashboard',
'security',
'analysis',
'knowledge',
@@ -243,30 +338,33 @@ const DOMAIN_SCENARIO_KEYWORDS = [
'speech',
'robot',
'finance',
'integration',
'安全',
'分析',
'知识',
'检索',
'自动化',
'工作流',
'开发者',
'编程',
'代码',
'协议',
'集成',
'企业',
'办公',
'医疗',
'医药',
'医学',
'生物医学',
'金融',
'交易',
'量化',
'隐私',
'语音',
'视觉',
]
const RESOURCE_TYPE_KEYWORDS = [
'guide',
'tutorial',
'paper',
'list',
'awesome',
'docs',
'documentation',
'resource',
'指南',
'教程',
'论文',
'合集',
'文档',
'资源',
'课程',
]
const RESOURCE_TYPE_KEYWORDS: string[] = []
const PROJECT_DOC_KEYWORDS = [
...RESOURCE_TYPE_KEYWORDS,
@@ -301,22 +399,18 @@ const PROJECT_MODEL_TAG_SLUGS = new Set([
const PROJECT_DOC_TAG_SLUGS = new Set([
'knowledge-base',
'markdown',
'ai-research',
])
const PROJECT_TOOL_TAG_SLUGS = new Set([
'ai-development-tool',
'agent-framework',
'ai-agent-framework',
'ai-agents',
'mcp',
'model-context-protocol',
'mcp-protocol',
'cli',
'workflow-automation',
'browser-automation',
'automation',
'openai-api',
'sdk',
'web-application',
'桌面应用',
@@ -358,27 +452,29 @@ export function inferTagCategory(input: {
nameEn?: string | null
}): TagCategory {
const rawSlug = input.slug.trim().toLowerCase()
if (isFixedProjectTypeSlug(rawSlug)) {
const canonicalSlug = TAG_SLUG_ALIAS_MAP[rawSlug] ?? rawSlug
if (isFixedProjectTypeSlug(canonicalSlug)) {
return 'FIXED_PROJECT_TYPE'
}
const normalizedSlug = normalize(rawSlug)
const normalizedSlug = normalize(canonicalSlug)
if (TECH_STACK_SLUGS.has(input.slug)) {
if (TECH_STACK_SLUGS.has(canonicalSlug)) {
return 'TECH_STACK'
}
if (AI_PARADIGM_SLUGS.has(input.slug)) {
if (AI_PARADIGM_SLUGS.has(canonicalSlug)) {
return 'AI_PARADIGM'
}
if (PROTOCOL_INTERFACE_SLUGS.has(input.slug)) {
if (PROTOCOL_INTERFACE_SLUGS.has(canonicalSlug)) {
return 'PROTOCOL_INTERFACE'
}
if (PRODUCT_FORM_SLUGS.has(input.slug)) {
if (PRODUCT_FORM_SLUGS.has(canonicalSlug)) {
return 'PRODUCT_FORM'
}
if (DOMAIN_SCENARIO_SLUGS.has(input.slug)) {
if (DOMAIN_SCENARIO_SLUGS.has(canonicalSlug)) {
return 'DOMAIN_SCENARIO'
}
if (RESOURCE_TYPE_SLUGS.has(input.slug)) {
if (RESOURCE_TYPE_SLUGS.has(canonicalSlug)) {
return 'RESOURCE_TYPE'
}
@@ -386,9 +482,6 @@ export function inferTagCategory(input: {
const normalizedNameEn = normalize(input.nameEn || '')
const mergedText = `${normalizedSlug} ${normalizedName} ${normalizedNameEn}`
if (includesAny(mergedText, RESOURCE_TYPE_KEYWORDS)) {
return 'RESOURCE_TYPE'
}
if (includesAny(mergedText, TECH_STACK_KEYWORDS)) {
return 'TECH_STACK'
}
@@ -405,7 +498,8 @@ export function inferTagCategory(input: {
return 'DOMAIN_SCENARIO'
}
return 'FREE_TAG'
// Free tags are deprecated in this project; default to tech stack for unknown slugs.
return 'TECH_STACK'
}
export function inferProjectTypeSlug(input: {