feat(keywords): rename brand-question terminology to search-keyword + add entity exclusion
- Rename UI/prompt labels: "品牌主问题" → "优化关键词", "搜索问题" → "搜索词" - Add brand/competitor entity exclusion filter in question expansion (AI distill path) - Refactor combination question text normalization, remove manual question-mark appending - Update prompts to emphasize commercial-intent search queries over editorial phrasing - Sync prompt changes across server/configs, deploy, and k3s configs - Update i18n, frontend views (BrandQuestionCreate, TemplateWizard), and test fixtures Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -191,15 +191,10 @@ func (s *QuestionExpansionService) GenerateByCombination(ctx context.Context, br
|
||||
for _, co := range core {
|
||||
for _, in := range industry {
|
||||
for _, sf := range suffix {
|
||||
text := strings.TrimSpace(strings.Join(nonEmptyParts(rg, pf, co, in, sf), ""))
|
||||
text := normalizeQuestionText(strings.Join(nonEmptyParts(rg, pf, co, in, sf), ""))
|
||||
if text == "" {
|
||||
continue
|
||||
}
|
||||
if sf != "" && !strings.HasSuffix(text, "?") && !strings.HasSuffix(text, "?") {
|
||||
if !strings.Contains(sf, "?") && !strings.Contains(sf, "?") {
|
||||
text += "?"
|
||||
}
|
||||
}
|
||||
key := normalizeQuestionKey(text)
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
@@ -312,6 +307,9 @@ func (s *QuestionExpansionService) GenerateByAIDistill(ctx context.Context, bran
|
||||
if text == "" {
|
||||
continue
|
||||
}
|
||||
if questionContainsExcludedEntity(text, brandCtx.BrandName, brandCtx.CompetitorNames) {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
@@ -754,11 +752,89 @@ func buildCandidate(text, source string, missingSuffix bool, existing map[string
|
||||
}
|
||||
}
|
||||
|
||||
func questionContainsExcludedEntity(text, brandName string, competitorNames []string) bool {
|
||||
normalized := normalizeQuestionKey(text)
|
||||
for _, entity := range append([]string{brandName}, competitorNames...) {
|
||||
for _, entityKey := range questionEntityExclusionKeys(entity) {
|
||||
if strings.Contains(normalized, entityKey) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func questionEntityExclusionKeys(entity string) []string {
|
||||
base := normalizeQuestionKey(entity)
|
||||
if utf8.RuneCountInString(base) < 2 {
|
||||
return nil
|
||||
}
|
||||
keys := make([]string, 0, 4)
|
||||
addKey := func(value string) {
|
||||
value = strings.TrimSpace(value)
|
||||
if utf8.RuneCountInString(value) < 2 {
|
||||
return
|
||||
}
|
||||
for _, key := range keys {
|
||||
if key == value {
|
||||
return
|
||||
}
|
||||
}
|
||||
keys = append(keys, value)
|
||||
}
|
||||
addKey(base)
|
||||
|
||||
companyName := base
|
||||
for _, suffix := range []string{
|
||||
"有限责任公司",
|
||||
"股份有限公司",
|
||||
"集团有限公司",
|
||||
"控股有限公司",
|
||||
"科技有限公司",
|
||||
"销售有限公司",
|
||||
"用品有限公司",
|
||||
"有限公司",
|
||||
"股份公司",
|
||||
"集团公司",
|
||||
"公司",
|
||||
} {
|
||||
if strings.HasSuffix(companyName, suffix) {
|
||||
companyName = strings.TrimSuffix(companyName, suffix)
|
||||
addKey(companyName)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
descriptorName := companyName
|
||||
for _, suffix := range []string{
|
||||
"用品销售",
|
||||
"产品销售",
|
||||
"销售",
|
||||
"用品",
|
||||
"产品",
|
||||
"服务",
|
||||
"科技",
|
||||
"商贸",
|
||||
"贸易",
|
||||
"网络",
|
||||
"信息",
|
||||
"文化",
|
||||
"传媒",
|
||||
} {
|
||||
if strings.HasSuffix(descriptorName, suffix) {
|
||||
descriptorName = strings.TrimSuffix(descriptorName, suffix)
|
||||
addKey(descriptorName)
|
||||
break
|
||||
}
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func normalizeQuestionParts(values []string) []string {
|
||||
result := make([]string, 0, len(values))
|
||||
seen := map[string]struct{}{}
|
||||
for _, value := range values {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
trimmed := normalizeQuestionText(value)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
@@ -790,27 +866,27 @@ func buildQuestionCombinationFillFallback(brandCtx *questionBrandContext, region
|
||||
region = compactQuestionCombinationTerms(req.Region, questionCombinationFillMax)
|
||||
}
|
||||
if len(region) == 0 {
|
||||
region = []string{"全国", "本地", "华东"}
|
||||
region = []string{"全国", "合肥", "华东"}
|
||||
}
|
||||
|
||||
prefix := compactQuestionCombinationTerms(req.Prefix, questionCombinationFillMax)
|
||||
if len(prefix) == 0 {
|
||||
prefix = []string{"好用的", "专业的", "靠谱的", "热门的"}
|
||||
prefix = []string{"性价比高的", "口碑好的", "专业的", "靠谱的"}
|
||||
}
|
||||
|
||||
core := compactQuestionCombinationTerms(req.Core, questionCombinationFillMax)
|
||||
if len(core) == 0 {
|
||||
core = questionCombinationCoreFallback(brandName, description)
|
||||
core = questionCombinationPrecisionFallback(brandName, description)
|
||||
}
|
||||
|
||||
industry := compactQuestionCombinationTerms(req.Industry, questionCombinationFillMax)
|
||||
if len(industry) == 0 {
|
||||
industry = []string{category, "平台", "工具", "服务商"}
|
||||
industry = questionCombinationIndustryFallback(category)
|
||||
}
|
||||
|
||||
suffix := compactQuestionCombinationTerms(req.Suffix, questionCombinationFillMax)
|
||||
if len(suffix) == 0 {
|
||||
suffix = []string{"哪家好", "怎么选", "推荐", "多少钱"}
|
||||
suffix = []string{"哪家好", "怎么选", "推荐", "避坑"}
|
||||
}
|
||||
|
||||
return QuestionCombinationFillResult{
|
||||
@@ -1017,21 +1093,40 @@ func chineseMacroRegion(province string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func questionCombinationCoreFallback(brandName, description string) []string {
|
||||
func questionCombinationPrecisionFallback(brandName, description string) []string {
|
||||
text := strings.ToLower(strings.TrimSpace(brandName + " " + description))
|
||||
if strings.Contains(text, "geo") || strings.Contains(text, "ai") || strings.Contains(text, "搜索") || strings.Contains(text, "排名") {
|
||||
return []string{"GEO", "AI搜索优化", "AI搜索排名", "品牌可见度"}
|
||||
return []string{"品牌词", "AI搜索", "获客", "中小企业"}
|
||||
}
|
||||
if strings.Contains(text, "教育") || strings.Contains(text, "培训") || strings.Contains(text, "课程") {
|
||||
return []string{"课程", "培训", "学习", "机构"}
|
||||
return []string{"成人", "少儿", "线上", "入门"}
|
||||
}
|
||||
if strings.Contains(text, "医疗") || strings.Contains(text, "健康") || strings.Contains(text, "诊所") {
|
||||
return []string{"医疗服务", "健康管理", "医生", "诊疗"}
|
||||
return []string{"本地", "复诊", "儿童", "上班族"}
|
||||
}
|
||||
if strings.Contains(text, "家居") || strings.Contains(text, "装修") || strings.Contains(text, "建材") {
|
||||
return []string{"装修", "家居", "建材", "设计"}
|
||||
return []string{"精装", "100平", "小户型", "旧房改造"}
|
||||
}
|
||||
return []string{"企业", "本地", "新手", "中小企业"}
|
||||
}
|
||||
|
||||
func questionCombinationIndustryFallback(category string) []string {
|
||||
switch category {
|
||||
case "营销":
|
||||
return []string{"GEO优化", "AI搜索优化", "品牌推广", "搜索营销"}
|
||||
case "教育":
|
||||
return []string{"培训机构", "在线课程", "教育机构", "学习平台"}
|
||||
case "健康":
|
||||
return []string{"健康管理", "医疗服务", "诊疗机构", "医生服务"}
|
||||
case "家居":
|
||||
return []string{"全屋定制", "家具定制", "家居定制", "定制家装"}
|
||||
case "餐饮":
|
||||
return []string{"餐饮店", "美食店", "连锁餐饮", "餐饮服务"}
|
||||
case "旅游":
|
||||
return []string{"旅游服务", "酒店预订", "旅行社", "旅游攻略"}
|
||||
default:
|
||||
return []string{"服务商", "解决方案", "平台", "公司"}
|
||||
}
|
||||
return []string{strings.TrimSpace(brandName), "解决方案", "服务", "平台"}
|
||||
}
|
||||
|
||||
func inferQuestionCombinationCategory(brandName, description string) string {
|
||||
|
||||
Reference in New Issue
Block a user