feat: 完善标签分类与领域场景归一化
This commit is contained in:
@@ -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
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user