章节问答

This commit is contained in:
董宏凯 committed 2026-05-19 19:51:44 +08:00
1 parent ab85e4df72
commit 81aca93b81
4 files changed
+106 -24

No files matched your search

@@ -252,7 +252,12 @@ function OutlineConfirmCard({ outline: initialOutline, onSubmit, disabled }: Out
const text = feedback.trim();
if (!text) return;
setSubmitting(true);
await onSubmit({ action: 're_outline', outlineFeedback: text });
// 带上卡片里的当前大纲,让 AI 在此基础上按要求增删/调整,而非从零重生成
await onSubmit({
action: 're_outline',
outlineFeedback: text,
editedOutline: editedOutline ?? undefined,
});
setSubmitting(false);
}
@@ -112,11 +112,16 @@ async def pause_outline_confirm(state: AIWritingState) -> dict:
# re_outline
feedback = user_input.get("outlineFeedback", "")
return {
updates: dict = {
"last_user_intervention": {**user_input, "_feedback": feedback},
"status": "writing_outline",
"progress_events": [{"type": "resumed", "agent_name": "系统", "message": f"重新规划大纲({feedback[:30]})"}],
}
# 用户在卡片里手动改过大纲 → 以手动版作为本次重规划的编辑基准
raw_edited = user_input.get("editedOutline")
if raw_edited:
updates["current_outline"] = _normalize_outline(raw_edited)
return updates
async def pause_draft_confirm(state: AIWritingState) -> dict:
@@ -2,13 +2,16 @@
from __future__ import annotations
import asyncio
import json
import logging
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.runnables import RunnableConfig
from ..state import AIWritingState, Outline, OutlineSection
from ..prompts.writer_prompts import OUTLINE_SYSTEM, OUTLINE_USER
from ..prompts.writer_prompts import (
OUTLINE_SYSTEM, OUTLINE_USER, OUTLINE_REVISE_SYSTEM, OUTLINE_REVISE_USER,
)
from ._utils import get_model, parse_json, _content_to_text, ThinkTagParser
logger = logging.getLogger(__name__)
@@ -42,20 +45,39 @@ async def writer_outline_node(state: AIWritingState, config: RunnableConfig) ->
pkg = state.get("material_package") or {"keywords": [], "materials": [], "summary": ""}
intervention = state.get("last_user_intervention") or {}
feedback_section = ""
if intervention.get("action") == "re_outline" and intervention.get("_feedback"):
feedback_section = f"\n重新规划要求:{intervention['_feedback']}"
feedback = ""
if intervention.get("action") == "re_outline":
feedback = intervention.get("_feedback") or intervention.get("outline_feedback") or ""
elif intervention.get("outline_feedback"):
feedback_section = f"\n重新规划要求:{intervention['outline_feedback']}"
feedback = intervention.get("outline_feedback") or ""
user_prompt = OUTLINE_USER.format(
user_intent=state["user_intent"],
article_type=state.get("article_type", "analysis"),
target_audience=state.get("target_audience", "普通读者"),
word_count_target=state.get("word_count_target", 800),
materials_summary=_materials_summary(pkg),
feedback_section=feedback_section,
)
# 重新规划且已有大纲 → 走「编辑模式」:把当前大纲喂给模型,按用户要求调整,
# 而非从零重生成(否则「加一节/删一节」这类指令没有可编辑的基准)。
current_outline = state.get("current_outline")
is_revise = bool(feedback) and bool(current_outline)
if is_revise:
system_prompt = OUTLINE_REVISE_SYSTEM
user_prompt = OUTLINE_REVISE_USER.format(
user_intent=state["user_intent"],
article_type=state.get("article_type", "analysis"),
target_audience=state.get("target_audience", "普通读者"),
word_count_target=state.get("word_count_target", 800),
current_outline=json.dumps(current_outline, ensure_ascii=False, indent=2),
feedback=feedback,
materials_summary=_materials_summary(pkg),
)
else:
system_prompt = OUTLINE_SYSTEM
feedback_section = f"\n重新规划要求:{feedback}" if feedback else ""
user_prompt = OUTLINE_USER.format(
user_intent=state["user_intent"],
article_type=state.get("article_type", "analysis"),
target_audience=state.get("target_audience", "普通读者"),
word_count_target=state.get("word_count_target", 800),
materials_summary=_materials_summary(pkg),
feedback_section=feedback_section,
)
# SSE 流式推送
cfg = config.get("configurable", {})
@@ -70,7 +92,7 @@ async def writer_outline_node(state: AIWritingState, config: RunnableConfig) ->
# 留在 try 外会硬崩溃;放进来则统一走下方的 status:error 兜底。
model = get_model(config)
async for chunk in model.astream([
SystemMessage(content=OUTLINE_SYSTEM),
SystemMessage(content=system_prompt),
HumanMessage(content=user_prompt),
]):
delta = _content_to_text(chunk.content)
@@ -98,14 +120,18 @@ async def writer_outline_node(state: AIWritingState, config: RunnableConfig) ->
}],
}
fallback = {
"title": "未命名文章",
"sections": [
{"section_title": "引言", "key_points": ["背景介绍"], "material_ids": []},
{"section_title": "正文", "key_points": ["核心内容"], "material_ids": []},
{"section_title": "结论", "key_points": ["总结"], "material_ids": []},
],
}
# 编辑模式解析失败时回退为「当前大纲」,避免把用户已有大纲弄丢
if is_revise:
fallback = current_outline
else:
fallback = {
"title": "未命名文章",
"sections": [
{"section_title": "引言", "key_points": ["背景介绍"], "material_ids": []},
{"section_title": "正文", "key_points": ["核心内容"], "material_ids": []},
{"section_title": "结论", "key_points": ["总结"], "material_ids": []},
],
}
result = parse_json(full_text, fallback)
outline = _parse_outline(result)
@@ -43,6 +43,52 @@ OUTLINE_USER = """写作意图:{user_intent}
请规划文章大纲(3-5个章节),只输出 JSON 格式。"""
# ── 重新规划(基于现有大纲的编辑)─────────────────────────────────────────────
OUTLINE_REVISE_SYSTEM = """你是一名专业内容创作者(作家)。
你的目标:根据用户的修改要求,调整一份【已有的文章大纲】。
你的工作背景:你有十年内容创作经验,擅长精准理解修改意图并落实到大纲结构上。
你只能做以下事情:
1. 在「当前大纲」基础上,按「用户修改要求」做调整
你不能写正文,只输出调整后的大纲。
【修改原则】(最高优先级)
- 准确识别并落实用户意图,常见操作:新增章节、删除章节、合并章节、拆分章节、调整章节顺序、修改章节标题、调整章节要点。
- 只改用户明确提到的部分:用户没要求改动的章节,必须原样保留(标题、要点、material_ids 全部不变)。
- 新增章节时:紧扣写作意图补充合理章节,并尽量关联可用素材的 material_ids。
- 章节数量完全由用户要求决定,没有上下限——用户要加就加、要减就减。
【严禁思考与表态】(最高优先级)
- 禁止输出任何思考、分析、规划过程,禁止 <think></think> 标签及其中内容。
- 禁止输出任何对用户说的话;收到输入后立即、直接输出最终 JSON。
【要点(key_points)的特殊要求】
- 每个要点会作为文章里的二级小标题呈现,写成简洁名词性短语,15 字以内,不带句末标点。
- 每个章节 2-4 个要点。
【输出要求】
- 只输出一个调整后的【完整】大纲 JSON 对象,不要解释文字,不要 markdown 代码块。
- 必须使用英文双引号 "。
- material_ids 中的 ID 必须来自给定素材,没有合适素材时填 []。
- 结构与「当前大纲」一致:
{"title": "...", "sections": [{"section_title": "...", "key_points": ["..."], "material_ids": ["..."]}]}"""
OUTLINE_REVISE_USER = """写作意图:{user_intent}
文章类型:{article_type}
目标读者:{target_audience}
目标字数:{word_count_target}字
当前大纲(JSON):
{current_outline}
用户修改要求:{feedback}
可用素材摘要:
{materials_summary}
请在「当前大纲」基础上落实上述「用户修改要求」,未提到的章节保持原样,输出调整后的完整大纲 JSON。"""
DRAFT_SECTION_SYSTEM = """你是一名专业内容创作者(作家)。
你的目标:根据大纲和素材,写出一段结构清晰、高质量的文章内容。
你的工作背景:你有十年内容创作经验,文字流畅,善于用事实说话。