feat: Enhance article generation and outline handling
- Added support for external markdown synchronization in ArticleEditorCanvas.vue. - Implemented a new function to sync markdown from props, ensuring the editor reflects external changes. - Introduced a removeOutlineSection function in TemplateWizardView.vue to manage outline sections dynamically. - Updated UI components to improve user experience, including replacing a-input with a-textarea for better text handling. - Refactored CSS styles for a cleaner layout and improved responsiveness in TemplateWizardView.vue. - Enhanced LLM generation requests to include response format options in ark.go and client.go. - Updated template assist logic to enforce structured outline outputs in template_assist.go. - Added tests for outline result decoding and prompt generation to ensure robustness. - Created migration scripts to update prompt templates with new writing requirements for top X articles.
This commit is contained in:
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
var promptVariablePattern = regexp.MustCompile(`\{\{\s*([a-zA-Z0-9_]+)\s*\}\}`)
|
||||
|
||||
func buildGenerationPrompt(templateName string, promptTemplate *string, params map[string]interface{}) string {
|
||||
func buildGenerationPrompt(templateKey, templateName string, promptTemplate *string, params map[string]interface{}) string {
|
||||
basePrompt := strings.TrimSpace(renderPromptTemplate(promptTemplate, params))
|
||||
if basePrompt == "" {
|
||||
basePrompt = fmt.Sprintf("你是一名专业内容编辑,请基于已提供的信息,为模板「%s」写出一篇完整的 Markdown 文章。", templateName)
|
||||
@@ -40,6 +40,10 @@ func buildGenerationPrompt(templateName string, promptTemplate *string, params m
|
||||
"- 结尾应给出清晰结论、适合对象、不适合对象,或下一步建议,帮助读者完成判断。",
|
||||
}, "\n"))
|
||||
|
||||
if templateRules := buildTemplateSpecificWritingRules(templateKey, params); templateRules != "" {
|
||||
sections = append(sections, "模板专项要求:\n"+templateRules)
|
||||
}
|
||||
|
||||
if lengthGuidance := buildGenerationLengthGuidance(params); lengthGuidance != "" {
|
||||
sections = append(sections, "篇幅要求:\n"+lengthGuidance)
|
||||
}
|
||||
@@ -47,6 +51,26 @@ func buildGenerationPrompt(templateName string, promptTemplate *string, params m
|
||||
return strings.Join(sections, "\n\n")
|
||||
}
|
||||
|
||||
func buildTemplateSpecificWritingRules(templateKey string, params map[string]interface{}) string {
|
||||
switch strings.TrimSpace(templateKey) {
|
||||
case "top_x_article":
|
||||
brandName := strings.TrimSpace(stringValue(params["brand_name"]))
|
||||
if brandName == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.Join([]string{
|
||||
fmt.Sprintf("- 若文章包含品牌、公司或服务商榜单,请将「%s」排在第 1 位,并作为全文主推对象。", brandName),
|
||||
fmt.Sprintf("- 「%s」对应条目的篇幅应明显多于其他对象,优先展开其核心优势、本地化服务、适合人群、报价或配置亮点、交付安装、售后表现与避坑建议。", brandName),
|
||||
"- 其他对象保留客观介绍,但篇幅更短,重点写与主推对象的差异点和各自适用场景,不要平均分配篇幅。",
|
||||
fmt.Sprintf("- 结论部分先明确推荐「%s」作为优先选择,再简要补充其他对象分别适合哪些预算、户型或需求。", brandName),
|
||||
fmt.Sprintf("- 如果标题或正文属于“Top X / 推荐 / 最好 / 排行榜”这类排序导向内容,榜单顺序必须与结论保持一致,将「%s」放在第 1 位。", brandName),
|
||||
"- 在不编造事实、不捏造数据的前提下,优先呈现主推对象的优势信息;对其他对象保持克制、客观的弱化式比较。",
|
||||
}, "\n")
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func buildGenerationLengthGuidance(params map[string]interface{}) string {
|
||||
sectionCount := estimateTopLevelSectionCount(params)
|
||||
if sectionCount <= 0 {
|
||||
@@ -68,9 +92,9 @@ func buildGenerationLengthGuidance(params map[string]interface{}) string {
|
||||
maxWords = 2100
|
||||
}
|
||||
return strings.Join([]string{
|
||||
fmt.Sprintf("- 建议全文控制在 %d-%d English words,优先信息密度,不要为了拉长篇幅重复表达。", minWords, maxWords),
|
||||
"- Intro and conclusion should stay concise. Most top-level sections only need 1-3 tight paragraphs; expand only the most important sections.",
|
||||
"- If one paragraph can make the point clearly, do not split it into multiple repetitive paragraphs.",
|
||||
fmt.Sprintf("- 建议全文控制在 %d-%d 个英文单词,优先信息密度,不要为了拉长篇幅重复表达。", minWords, maxWords),
|
||||
"- 引言和结论保持简洁,大多数一级章节写 1-3 个紧凑段落即可;只有最重要的章节需要展开。",
|
||||
"- 一段能说清的内容不要拆成多段重复表达。",
|
||||
}, "\n")
|
||||
}
|
||||
|
||||
@@ -182,7 +206,7 @@ func buildPromptContext(params map[string]interface{}) string {
|
||||
if formatted == "" {
|
||||
return
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf("- %s: %s", key, formatted))
|
||||
lines = append(lines, fmt.Sprintf("- %s: %s", promptContextLabel(key), formatted))
|
||||
used[key] = struct{}{}
|
||||
}
|
||||
|
||||
@@ -200,6 +224,67 @@ func buildPromptContext(params map[string]interface{}) string {
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func promptContextLabel(key string) string {
|
||||
switch key {
|
||||
case "locale":
|
||||
return "语言"
|
||||
case "title":
|
||||
return "标题"
|
||||
case "topic":
|
||||
return "主题"
|
||||
case "product_name":
|
||||
return "产品名"
|
||||
case "subject":
|
||||
return "研究主题"
|
||||
case "brand_name":
|
||||
return "品牌名"
|
||||
case "brand":
|
||||
return "品牌"
|
||||
case "official_website", "website":
|
||||
return "官网"
|
||||
case "primary_keyword":
|
||||
return "核心关键词"
|
||||
case "keywords", "existing_keywords":
|
||||
return "关键词"
|
||||
case "competitors", "existing_competitors":
|
||||
return "竞品"
|
||||
case "competitor_names":
|
||||
return "竞品名称"
|
||||
case "competitor_count":
|
||||
return "竞品数量"
|
||||
case "brand_summary":
|
||||
return "品牌摘要"
|
||||
case "category":
|
||||
return "品类"
|
||||
case "count":
|
||||
return "数量"
|
||||
case "top_count":
|
||||
return "推荐数量"
|
||||
case "keyword_count":
|
||||
return "关键词数量"
|
||||
case "depth":
|
||||
return "深度"
|
||||
case "article_outline":
|
||||
return "文章大纲"
|
||||
case "outline_sections":
|
||||
return "已选段落"
|
||||
case "key_points":
|
||||
return "关键要点"
|
||||
case "review_intro_hook":
|
||||
return "评测引言钩子"
|
||||
case "template_key":
|
||||
return "模板标识"
|
||||
case "template_name":
|
||||
return "模板名称"
|
||||
case "current_year":
|
||||
return "当前年份"
|
||||
case "input_params":
|
||||
return "输入参数"
|
||||
default:
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
func formatPromptContextValue(key string, value interface{}) string {
|
||||
switch key {
|
||||
case "keywords":
|
||||
|
||||
Reference in New Issue
Block a user