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:
2026-05-20 01:27:14 +08:00
parent 28633cf570
commit c89cad6643
19 changed files with 1211 additions and 668 deletions
+76 -69
View File
@@ -154,7 +154,7 @@ const enUS = {
template_outline_generate: 'AI Outline Generation',
kol_prompt_generate: 'KOL Prompt Generation',
kol_prompt_optimize: 'KOL Prompt Optimize',
question_distill: 'AI Candidate Question Generation',
question_distill: 'AI Candidate Search Term Generation',
question_combination_fill: 'Expansion Tool AI Fill',
},
aiUsageStatus: {
@@ -214,12 +214,13 @@ const enUS = {
'Review desktop publish tasks, external links, account workbench entry points, and retry state.',
},
brands: {
title: 'Brand Library',
description: 'Manage keywords, question sets, and competitors around each brand.',
title: 'Company & Lexicon',
description: 'Manage the keyword library (search terms) and competitors around each brand.',
},
brandQuestionCreate: {
title: 'New question',
description: 'Create brand questions with the expansion tool, AI expansion, or manual input.',
title: 'New search term',
description:
'Create brand search terms with the expansion tool, AI expansion, or manual input.',
},
tracking: {
title: 'Data Details',
@@ -459,11 +460,11 @@ const enUS = {
},
tracking: {
brandPlaceholder: 'Select brand',
keywordPlaceholder: 'Select question set',
allQuestionSets: 'All question sets',
keywordPlaceholder: 'Select keyword library (search terms)',
allQuestionSets: 'All keyword library (search terms)',
collectNow: 'Collect Now',
collectBrandRequired: 'Select a brand before collecting.',
collectKeywordRequired: 'Select a question set before collecting.',
collectKeywordRequired: 'Select a keyword library (search terms) before collecting.',
collectTodayOnly: "Collect Now only supports today's data. Switch back to today to continue.",
collectClientChecking: "Checking whether the current account's desktop client is online.",
collectClientOnlineRequired:
@@ -488,7 +489,7 @@ const enUS = {
hotQuestions: 'Hot Questions',
hotQuestionsTitle: 'Hot Questions',
hotQuestionsHint:
'Choose all question sets or one specific question. Selecting one question limits display and collection to that question.',
'Choose the full keyword library (search terms) or one specific search term. Selecting one search term limits display and collection to that term.',
questionSearchPlaceholder: 'Search questions in the selected scope',
questionDetailEyebrow: 'Question Detail',
questionDetailCollectedAt: 'Collected at {time}',
@@ -841,13 +842,13 @@ const enUS = {
language: 'Language',
brandInfo: 'Brand',
brandLibrary: 'Brand library',
brandName: 'Brand name',
brandName: 'Company name',
website: 'Official website',
brandSummary: 'AI brand summary',
keywords: 'Keywords',
brandQuestions: 'Brand questions',
primaryQuestion: 'Primary brand question',
supplementalQuestions: 'Supplemental questions',
brandQuestions: 'Keyword library (search terms)',
primaryQuestion: 'Optimization keyword',
supplementalQuestions: 'Supplemental keywords',
competitors: 'Competitors',
chooseTitle: 'Choose a title',
customTitle: 'Custom title',
@@ -864,17 +865,17 @@ const enUS = {
hints: {
brandContext: 'Optional. Selecting a brand pulls in keywords and competitor context.',
brandFlow:
'You can pick a brand from the library or type one manually. After you choose a brand question, AI will prepare the summary, competitors, and title options.',
'You can type a company name or choose one from the brand library. After you choose an optimization keyword, AI will prepare the summary, competitors, and title options.',
brandSummaryPlaceholder:
'After analysis, AI will summarize the brand angle, positioning, and content opportunities here.',
keywords:
'Type keywords directly or reuse brand-library keywords. AI results will be merged into this list.',
brandQuestions:
'The primary question controls the article spine; supplemental questions broaden nearby search intent.',
'The optimization keyword controls the article spine; supplemental keywords broaden nearby search intent.',
supplementalQuestions:
'Choose up to 3 so the article covers related intent without drifting.',
questionEmpty: 'No questions are available for this brand yet.',
selectBrandFirst: 'Choose a brand from the library first.',
selectBrandFirst: 'Choose a library brand from the company name dropdown first.',
competitors:
'Competitors stay editable. You can add rows manually; saving to the competitor library is only available after choosing a library brand.',
competitorEmptyEditable:
@@ -931,11 +932,11 @@ const enUS = {
},
placeholders: {
brandLibrary: 'Choose an existing brand from the library',
brandName: 'Enter a brand name, or refine the selected library brand',
brandName: 'Enter a company name, or choose from the brand library',
website: 'Official website, optional',
keywords: 'Type keywords and press Enter',
primaryQuestion: 'Choose one primary brand question',
supplementalQuestions: 'Optional, choose up to 3 supplemental questions',
primaryQuestion: 'Choose one optimization keyword',
supplementalQuestions: 'Optional, choose up to 3 supplemental keywords',
customTitle: 'Override the AI title here if needed',
customOutline: 'Add another section, for example “Buying advice”',
keyPoints: 'Add any extra instructions, key angles, or constraints for the article',
@@ -949,9 +950,9 @@ const enUS = {
aiBanner: {
title: 'One-click AI analysis',
defaultDescription:
'AI will use the brand question to complete the brand summary and extract competitor context.',
'AI will use the optimization keyword to complete the brand summary and extract competitor context.',
productReviewDescription:
'AI will use the brand question to organize the product context and suggest comparable alternatives.',
'AI will use the optimization keyword to organize the product context and suggest comparable alternatives.',
researchReportDescription:
'AI will use the brand question to organize the topic context and suggest reference targets.',
},
@@ -983,15 +984,18 @@ const enUS = {
},
messages: {
requiredField: 'Please fill in {field} first.',
missingBrand: 'Choose a brand from the library or enter a brand name first.',
selectBrandBeforeQuestion: 'Choose a brand from the library before selecting a question.',
missingBrand: 'Enter or choose a company name first.',
selectBrandBeforeQuestion:
'Choose a library brand from the company name dropdown before selecting a question.',
missingPrimaryQuestion: 'Choose one primary brand question.',
missingTitle: 'Please choose or enter an article title.',
missingOutline: 'Please select at least one outline section.',
missingGeneratedOutline: 'Please generate and confirm the article outline first.',
customOutlineTooLong: 'Custom sections cannot exceed {count} characters.',
selectBrandBeforeCompetitors: 'Choose a brand from the library before managing competitors.',
selectBrandBeforeFavorite: 'Choose a brand from the library before saving competitors.',
selectBrandBeforeCompetitors:
'Choose a library brand from the company name dropdown before managing competitors.',
selectBrandBeforeFavorite:
'Choose a library brand from the company name dropdown before saving competitors.',
missingCompetitorName: 'Enter a competitor name before saving it to the library.',
savedCompetitor: 'Competitor saved to the brand library.',
unsavedCompetitor: 'Competitor removed from the brand library.',
@@ -1285,20 +1289,21 @@ const enUS = {
},
},
brands: {
eyebrow: 'Brand Library',
title: 'Brand Management',
description: 'Manage question sets and competitor assets around each brand.',
eyebrow: 'Company & Lexicon',
title: 'Company & Lexicon',
description: 'Manage the keyword library (search terms) and competitor assets around each brand.',
railTitle: 'Brands',
selectBrand: 'Pick a brand to continue managing question sets.',
selectBrand: 'Pick a brand to continue managing the keyword library (search terms).',
newBrand: 'New brand',
editBrand: 'Edit brand',
deleteBrand: 'Delete brand',
guide: {
title: 'Brand management guide',
body: 'Manage brands, question sets, and competitor libraries for AI platform monitoring and GEO analysis.',
body:
'Manage brands, the keyword library (search terms), and competitor libraries for AI platform monitoring and GEO analysis.',
},
tabs: {
questions: 'Question set',
questions: 'Keyword library (search terms)',
competitors: 'Competitors',
},
quota: {
@@ -1321,20 +1326,20 @@ const enUS = {
},
sections: {
keywords: 'Keywords',
questions: 'Question set',
questions: 'Keyword library (search terms)',
competitors: 'Competitor library',
versions: 'Version history',
},
actions: {
addBrand: 'Add brand',
newKeyword: 'New keyword',
newQuestion: 'New question',
newQuestion: 'New search term',
newCompetitor: 'New competitor',
viewVersions: 'View versions',
},
empty: {
brands: 'No brand data yet. Create your first brand to start.',
questions: 'No questions under this brand yet.',
questions: 'No search terms under this brand yet.',
competitors: 'No competitors under this brand yet.',
brandDescription: 'No brand description yet',
competitorDescription: 'No competitor description yet',
@@ -1344,7 +1349,7 @@ const enUS = {
brandWebsite: 'Company website (optional)',
brandDescription: 'Brand description',
keywordName: 'Keyword',
questionText: 'Question text',
questionText: 'Search term',
competitorName: 'Competitor name',
competitorWebsite: 'Competitor website',
competitorDescription: 'Competitor description',
@@ -1358,9 +1363,9 @@ const enUS = {
createKeyword: 'Keyword created.',
updateKeyword: 'Keyword updated.',
deleteKeyword: 'Keyword deleted.',
createQuestion: 'Question created.',
updateQuestion: 'Question updated.',
deleteQuestion: 'Question deleted.',
createQuestion: 'Search term created.',
updateQuestion: 'Search term updated.',
deleteQuestion: 'Search term deleted.',
createCompetitor: 'Competitor created.',
updateCompetitor: 'Competitor updated.',
deleteCompetitor: 'Competitor deleted.',
@@ -1368,21 +1373,21 @@ const enUS = {
chooseKeyword: 'Please choose a keyword first.',
brandLimitReached: 'Your current plan allows up to {limit} brand companies.',
keywordLimitReached: 'This account allows up to {limit} keywords.',
questionLimitReached: 'This account allows up to {limit} questions.',
questionLimitReached: 'This account allows up to {limit} search terms.',
},
questions: {
create: 'New question',
createSingle: 'Add single question',
expand: 'Bulk expand questions',
usage: 'Questions {used} / {total}',
create: 'New search term',
createSingle: 'Add single search term',
expand: 'Bulk expand search terms',
usage: 'Search terms {used} / {total}',
remaining: '{count} remaining',
generate: 'Generate candidates',
emptyCandidates: 'No candidates yet. Generate or paste questions above.',
emptyCandidates: 'No candidates yet. Generate or paste search terms above.',
table: {
question: 'Question set',
question: 'Keyword library (search terms)',
},
modal: {
title: 'New question',
title: 'New search term',
currentBrand: 'Current brand',
methods: {
single: 'Single',
@@ -1393,17 +1398,17 @@ const enUS = {
preview: 'Candidate preview',
summary:
'{total} candidates · {selected} selected · {tooShort} too short · {duplicate} duplicates',
save: 'Save questions ({count})',
save: 'Save search terms ({count})',
},
combination: {
region: '1. Region',
prefix: '2. Prefix',
core: '3. Core (required)',
core: '3. Precise term (required)',
industry: '4. Industry (required)',
suffix: '5. Suffix',
defaultWord: 'Default',
estimate: '{count} candidates estimated',
coreRequired: 'Core is required',
coreRequired: 'Precise term is required',
industryRequired: 'Industry is required',
},
aiFill: {
@@ -1414,25 +1419,25 @@ const enUS = {
waitingStages: {
region: 'Detecting city, province capital, and macro region...',
generate: 'Analyzing brand context and high-frequency intent...',
refine: 'Refining short combinable keyword groups...',
refine: 'Refining short combinable precise term groups...',
},
},
ai: {
note: 'AI expansion consumes 1 AI point. Failed generations are refunded automatically.',
seedTopic: 'Seed topic',
seedTopicPlaceholder: 'Enter a topic and press Enter; multiple topics supported',
seedTopic: 'User search term',
seedTopicPlaceholder: 'Enter a search term and press Enter; multiple terms supported',
seedTopicHint:
'Use Enter, commas, or semicolons to add multiple topics. Candidates are merged and deduplicated automatically.',
'Candidates expand from user search terms with strong commercial intent. The company name is a target term, not a candidate search term.',
generate: 'Generate with AI',
waitingTitle: 'AI is generating candidate questions. Please wait...',
waitingTitle: 'AI is generating candidate search terms. Please wait...',
waitingStages: {
context: 'Organizing brand and topic context...',
generate: 'Generating high-intent candidate questions...',
refine: 'Deduplicating and filtering savable questions...',
context: 'Organizing user search terms and the target term...',
generate: 'Generating commercial candidate search terms suited for recommending companies...',
refine: 'Deduplicating and filtering savable search terms...',
},
},
batch: {
label: 'One question per line',
label: 'One search term per line',
},
badges: {
duplicate: 'Duplicate',
@@ -1440,20 +1445,22 @@ const enUS = {
},
messages: {
partialSaved: 'Saved {created}; skipped {skipped}.',
limitReached: 'This account allows up to {limit} questions shared across all brands.',
selectionLimitReached: 'This plan allows up to {limit} questions. Selection limit reached.',
limitReached: 'This account allows up to {limit} search terms shared across all brands.',
selectionLimitReached:
'This plan allows up to {limit} search terms. Selection limit reached.',
truncated: 'Candidates exceeded the limit and were truncated to 500.',
cacheHit: 'Used the most recent AI expansion result.',
aiCharged: 'This AI expansion consumed {count} AI point.',
},
errors: {
emptyInput: 'Enter question content first.',
emptyInput: 'Enter search term content first.',
noSelection: 'Select at least one candidate.',
noValidQuestions: 'No valid questions can be saved. Adjust candidates and try again.',
noValidQuestions: 'No valid search terms can be saved. Adjust candidates and try again.',
},
page: {
title: 'New question',
subtitle: 'Generate candidates, review them, then save them to this brand question set.',
title: 'New search term',
subtitle:
'Generate candidates, review them, then save them to this brand keyword library (search terms).',
steps: {
configure: 'Configure',
preview: 'Review candidates',
@@ -1462,13 +1469,13 @@ const enUS = {
methodHint:
'Candidates remain temporary until saved, so you can generate, edit, and select first.',
methods: {
combination: 'Expand candidates from the column-based word tool.',
ai: 'Let AI generate candidates around a seed topic.',
batch: 'Paste questions from a sheet or document.',
combination: 'Expand search term candidates from the column-based word tool.',
ai: 'Let AI generate search term candidates around a seed topic.',
batch: 'Paste search terms from a sheet or document.',
},
expansionHint:
'One word per line. Empty optional columns are skipped; core and industry are required.',
batchHint: 'One question per line. You can edit and select each one before saving.',
'One word per line. Empty optional columns are skipped; precise term and industry are required.',
batchHint: 'One search term per line. You can edit and select each one before saving.',
},
},
},