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
+31 -19
View File
@@ -286,6 +286,12 @@ const templateMeta = computed(() => {
const selectedBrand = computed(
() => brandsQuery.data.value?.find((brand) => brand.id === selectedBrandId.value) ?? null,
)
const brandNameOptions = computed(() =>
(brandsQuery.data.value ?? []).map((item) => ({
label: item.name,
value: item.name,
})),
)
function findQuestionById(id: number | null): Question | null {
if (!id) {
@@ -654,6 +660,23 @@ watch(supplementalQuestionIds, (ids) => {
}
})
function handleBrandNameInput(value: string): void {
brandName.value = value
const matchedBrand = brandsQuery.data.value?.find((item) => item.name === value)
selectedBrandId.value = matchedBrand?.id ?? null
}
function handleBrandNameSelect(value: string): void {
const brand = brandsQuery.data.value?.find((item) => item.name === value)
if (!brand) {
selectedBrandId.value = null
brandName.value = value
return
}
selectedBrandId.value = brand.id
brandName.value = brand.name
}
watch(selectedBrandId, async (brandId) => {
if (restoringDraft.value) {
return
@@ -1192,8 +1215,9 @@ function sanitizeLegacyKeywordCopy(copy: TemplateCardCopy): TemplateCardCopy {
function sanitizeLegacyKeywordText(value: string | undefined): string {
return (value || '')
.replace(/核心关键词/g, '品牌主问题')
.replace(/关键词/g, '品牌问题')
.replace(/品牌主问题/g, '优化关键词')
.replace(/补充覆盖问题/g, '补充关键词')
.replace(/核心关键词/g, '优化关键词')
.replace(/标题生成/g, '后续生成')
}
@@ -2392,29 +2416,17 @@ function onStructureDragEnd(): void {
/>
</div>
<div class="field-item">
<label>{{ t('templates.wizard.sections.brandLibrary') }}</label>
<a-select
v-model:value="selectedBrandId"
allow-clear
show-search
:placeholder="t('templates.wizard.placeholders.brandLibrary')"
:options="
(brandsQuery.data.value || []).map((item) => ({
label: item.name,
value: item.id,
}))
"
/>
</div>
<div class="field-item">
<label class="required-asterisk">
{{ t('templates.wizard.sections.brandName') }}
</label>
<a-input
<a-auto-complete
v-model:value="brandName"
allow-clear
:options="brandNameOptions"
:placeholder="t('templates.wizard.placeholders.brandName')"
@change="handleBrandNameInput"
@select="handleBrandNameSelect"
/>
</div>