fix(content-gen): strip URLs and site-list sections from generated content

Forbid URLs/domains/markdown links across prompt rules (runtime, platform
templates, outline/imitation/title flows) and rename "官网" placeholder to
"官网信息" with explicit instructions that links are background only.
Add sanitizeGeneratedArticleMarkdown to strip URLs, HTML images, and
"网站列表/官网列表" labels from LLM output, wired into article, prompt-rule,
template, and outline generation. Drop the site_list outline section seed
and filter blocked outline keys in the wizard so stored drafts cannot
resurrect it.
This commit is contained in:
2026-05-24 22:19:43 +08:00
parent 946a084c77
commit 52997e36fe
12 changed files with 290 additions and 39 deletions
@@ -379,9 +379,10 @@ const baseOutlineOptions = computed<ResolvedOutlineOption[]>(() => {
)
return [...configured, ...customOutlineSections.value]
})
const outlineOptions = computed<ResolvedOutlineOption[]>(() =>
sortOutlineOptions(baseOutlineOptions.value, outlineOptionOrder.value),
)
const outlineOptions = computed<ResolvedOutlineOption[]>(() => {
const visible = baseOutlineOptions.value.filter((section) => !isBlockedOutlineOption(section))
return sortOutlineOptions(visible, outlineOptionOrder.value)
})
const selectedOutlineLabels = computed(() =>
outlineOptions.value
.filter((section) => outlineSections.value.includes(section.key))
@@ -517,7 +518,7 @@ function resetWizardState(detail: NonNullable<typeof templateDetail.value>): voi
competitorCount: 0,
}),
)
.filter((section) => section.default)
.filter((section) => section.default && !isBlockedOutlineOption(section))
.map((section) => section.key)
outlineOptionOrder.value = buildOutlineOptions(
templateCardConfig.value.wizard?.outline,
@@ -532,7 +533,9 @@ function resetWizardState(detail: NonNullable<typeof templateDetail.value>): voi
inputParams: {},
competitorCount: 0,
}),
).map((section) => section.key)
)
.filter((section) => !isBlockedOutlineOption(section))
.map((section) => section.key)
outlineSections.value = defaults
}
@@ -562,9 +565,9 @@ function applyDraftArticleState(detail: ArticleDetail): void {
selectedKnowledgeGroupIds.value = numberListFromUnknown(draftState.knowledge_group_ids) ?? []
customOutlineSections.value = parseCustomOutlineSections(draftState.custom_outline_sections)
outlineOptionOrder.value =
stringListFromUnknown(draftState.outline_option_order) ?? outlineOptionOrder.value
sanitizeOutlineKeys(stringListFromUnknown(draftState.outline_option_order)) ?? outlineOptionOrder.value
outlineSections.value =
stringListFromUnknown(draftState.outline_sections) ?? outlineSections.value
sanitizeOutlineKeys(stringListFromUnknown(draftState.outline_sections)) ?? outlineSections.value
generatedOutline.value = decorateOutlineNodes(parseOutlineNodes(draftState.generated_outline))
restoringDraft.value = false
@@ -1307,8 +1310,8 @@ function buildDraftState(): Record<string, JsonValue> {
title: finalTitle.value || selectedTitle.value || '',
review_intro_hook: reviewIntroHook.value,
knowledge_group_ids: selectedKnowledgeGroupIds.value,
outline_sections: outlineSections.value,
outline_option_order: outlineOptionOrder.value,
outline_sections: sanitizeOutlineKeys(outlineSections.value) ?? [],
outline_option_order: sanitizeOutlineKeys(outlineOptionOrder.value) ?? [],
custom_outline_sections: customOutlineSections.value.map((item) => ({
key: item.key,
label: item.label,
@@ -1764,6 +1767,23 @@ function buildOutlineOptions(
}))
}
function isBlockedOutlineOption(section: ResolvedOutlineOption): boolean {
const key = section.key.trim().toLowerCase()
const label = section.label.trim()
return key === 'site_list' || ['网站列表', '官网列表', '网址列表'].includes(label)
}
function sanitizeOutlineKeys(keys: string[] | undefined): string[] | undefined {
if (!keys) {
return undefined
}
const blockedKeys = new Set(
baseOutlineOptions.value.filter((section) => isBlockedOutlineOption(section)).map((section) => section.key),
)
const filtered = keys.filter((key) => !blockedKeys.has(key) && key.trim().toLowerCase() !== 'site_list')
return filtered.length > 0 ? filtered : []
}
function buildFallbackTitles(
_templateKey: string | undefined,
currentBrand: string,