章节问答
This commit is contained in:
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);
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -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:
|
||||
|
||||
+48
-22
@@ -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)
|
||||
|
||||
+46
@@ -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 = """你是一名专业内容创作者(作家)。
|
||||
你的目标:根据大纲和素材,写出一段结构清晰、高质量的文章内容。
|
||||
你的工作背景:你有十年内容创作经验,文字流畅,善于用事实说话。
|
||||
|
||||
Reference in new issue
Block a user