diff --git a/apps/admin-web/src/i18n/messages/zh-CN.ts b/apps/admin-web/src/i18n/messages/zh-CN.ts index ca246d2..0cfe774 100644 --- a/apps/admin-web/src/i18n/messages/zh-CN.ts +++ b/apps/admin-web/src/i18n/messages/zh-CN.ts @@ -79,7 +79,7 @@ const zhCN = { media: "媒体管理", publishManagement: "发文管理", brandManagement: "品牌管理", - brands: "公司和词库", + brands: "品牌和词库", tracking: "数据追踪", trackingDetail: "数据详情", contentManagement: "内容管理", @@ -222,7 +222,7 @@ const zhCN = { description: "查看桌面端发布任务、外链、账号工作台入口和重发状态。", }, brands: { - title: "公司和词库", + title: "品牌和词库", description: "围绕品牌维护关键词库(搜索词)和竞品信息。", }, brandQuestionCreate: { @@ -1275,7 +1275,7 @@ const zhCN = { }, brands: { eyebrow: "Company & Lexicon", - title: "公司和词库", + title: "品牌和词库", description: "通过品牌名称和品牌相关信息维护关键词库(搜索词)和竞品资产。", railTitle: "品牌库", selectBrand: "选择品牌后即可继续维护关键词库(搜索词)。", diff --git a/apps/admin-web/src/views/BrandQuestionCreateView.vue b/apps/admin-web/src/views/BrandQuestionCreateView.vue index 8ed4fd4..f6c7709 100644 --- a/apps/admin-web/src/views/BrandQuestionCreateView.vue +++ b/apps/admin-web/src/views/BrandQuestionCreateView.vue @@ -506,12 +506,12 @@ function normalizeSearchTermText(value: string): string { .replace(/\s+/g, '') } -function questionLooksLikeQuestion(value: string): boolean { +function searchTermLooksValid(value: string): boolean { const text = normalizeSearchTermText(value) if (text.length < 4) { return false } - return /什么|如何|怎么|哪|为什么|是否|能不能|适合|区别|对比|推荐/.test(text) + return /[\p{L}\p{N}]/u.test(text) } function toCandidateRows(candidates: QuestionCandidate[]): CandidateRow[] { @@ -519,7 +519,7 @@ function toCandidateRows(candidates: QuestionCandidate[]): CandidateRow[] { .map((candidate, index) => { const text = normalizeSearchTermText(candidate.text) const tooShort = text.length < 4 - const invalid = !questionLooksLikeQuestion(text) + const invalid = !searchTermLooksValid(text) return { ...candidate, text, @@ -545,7 +545,7 @@ function buildLocalCandidates(texts: string[], source: QuestionSource): Candidat seen.add(key) const tooShort = text.length < 4 const duplicate = existingQuestionSet.value.has(key) || duplicateInBatch - const invalid = !questionLooksLikeQuestion(text) + const invalid = !searchTermLooksValid(text) rows.push({ id: `${Date.now()}-${rows.length}-${text}`, text, @@ -682,7 +682,7 @@ function refreshCandidate(row: CandidateRow): void { ) row.too_short = row.text.length < 4 row.duplicate = Boolean(key) && (existingQuestionSet.value.has(key) || duplicateInBatch) - row.suggest_skip = row.too_short || row.duplicate || !questionLooksLikeQuestion(row.text) + row.suggest_skip = row.too_short || row.duplicate || !searchTermLooksValid(row.text) } function refreshAllCandidates(): void { diff --git a/server/internal/tenant/app/question_metadata_classify.go b/server/internal/tenant/app/question_metadata_classify.go index 6d9ee5a..40eb21e 100644 --- a/server/internal/tenant/app/question_metadata_classify.go +++ b/server/internal/tenant/app/question_metadata_classify.go @@ -123,17 +123,14 @@ func questionLooksValid(text string) bool { if utf8.RuneCountInString(trimmed) < 4 { return false } - hasLetterOrNumber := false + hasSearchTermChar := false for _, r := range trimmed { if unicode.IsLetter(r) || unicode.IsNumber(r) { - hasLetterOrNumber = true + hasSearchTermChar = true break } } - if !hasLetterOrNumber { - return false - } - return containsAny(strings.ToLower(trimmed), []string{"?", "?", "什么", "如何", "怎么", "哪", "为什么", "是否", "能不能", "适合", "区别", "对比", "推荐"}) + return hasSearchTermChar } func containsAny(value string, needles []string) bool { diff --git a/server/internal/tenant/app/question_search_term_validation_test.go b/server/internal/tenant/app/question_search_term_validation_test.go new file mode 100644 index 0000000..594c484 --- /dev/null +++ b/server/internal/tenant/app/question_search_term_validation_test.go @@ -0,0 +1,48 @@ +package app + +import "testing" + +func TestQuestionLooksValidAcceptsGenericSearchTerms(t *testing.T) { + tests := []string{ + "全屋定制", + "GEO排名优化", + "AI搜索排名", + } + + for _, text := range tests { + if !questionLooksValid(text) { + t.Fatalf("questionLooksValid(%q) = false, want true", text) + } + } +} + +func TestBuildCandidateKeepsGenericSearchTermSavable(t *testing.T) { + candidate := buildCandidate( + "全屋定制", + QuestionSourceManual, + false, + map[string]struct{}{}, + &questionBrandContext{BrandName: "安徽海翔家居用品销售有限公司"}, + ) + + if candidate.TooShort { + t.Fatal("expected generic search term not to be marked too short") + } + if candidate.SuggestSkip { + t.Fatal("expected generic search term not to be suggested for skip") + } +} + +func TestQuestionLooksValidRejectsEmptyOrTooShortTerms(t *testing.T) { + tests := []string{ + "", + "!?。", + "AI", + } + + for _, text := range tests { + if questionLooksValid(text) { + t.Fatalf("questionLooksValid(%q) = true, want false", text) + } + } +}