From 1a5913b6a80960fece5f15e77cc0cf41f4b281b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AF=BA=E6=96=AF=E8=B4=B9=E6=8B=89=E5=9B=BE?= <1132505822@qq.com> Date: Sun, 12 Apr 2026 17:44:09 +0800 Subject: [PATCH] feat: add SectionPlan node for manuscript section planning --- custom_nodes/research/__init__.py | 3 +- custom_nodes/research/section_plan.py | 89 +++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 custom_nodes/research/section_plan.py diff --git a/custom_nodes/research/__init__.py b/custom_nodes/research/__init__.py index 15300b2a3..38633aa7a 100644 --- a/custom_nodes/research/__init__.py +++ b/custom_nodes/research/__init__.py @@ -12,7 +12,8 @@ class ResearchExtension(ComfyExtension): from custom_nodes.research.evidence_assemble import ClaimEvidenceAssemble from custom_nodes.research.style_profile import StyleProfileExtract from custom_nodes.research.reference_paper_select import ReferencePaperSelect - return [PaperSearch, PaperClaimExtract, ClaimEvidenceAssemble, StyleProfileExtract, ReferencePaperSelect] + from custom_nodes.research.section_plan import SectionPlan + return [PaperSearch, PaperClaimExtract, ClaimEvidenceAssemble, StyleProfileExtract, ReferencePaperSelect, SectionPlan] async def comfy_entrypoint() -> ComfyExtension: diff --git a/custom_nodes/research/section_plan.py b/custom_nodes/research/section_plan.py new file mode 100644 index 000000000..90524377e --- /dev/null +++ b/custom_nodes/research/section_plan.py @@ -0,0 +1,89 @@ +"""SectionPlan node - generate section narrative plan.""" +import json +from typing_extensions import override +from comfy_api.latest import ComfyNode, io + + +class SectionPlan(io.ComfyNode): + """Generate a narrative plan for a manuscript section based on claims and style.""" + + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="SectionPlan", + display_name="Section Plan", + category="Research", + inputs=[ + io.String.Input( + "section_type", + display_name="Section Type", + default="abstract", + ), + io.String.Input( + "claims", + display_name="Claims (JSON)", + default="[]", + multiline=True, + ), + io.String.Input( + "style_profile", + display_name="Style Profile (JSON)", + default="{}", + multiline=True, + ), + ], + outputs=[ + io.String.Output(display_name="Section Plan (JSON)"), + ], + ) + + @classmethod + def execute(cls, section_type: str, claims: str, style_profile: str) -> io.NodeOutput: + try: + claims_list = json.loads(claims) if claims else [] + style = json.loads(style_profile) if style_profile else {} + except json.JSONDecodeError: + claims_list = [] + style = {} + + # Generate plan based on section type + plan = { + "section_type": section_type, + "paragraphs": [], + "estimated_length": "", + } + + if section_type.lower() == "abstract": + plan["paragraphs"] = [ + {"order": 1, "purpose": "Background and problem statement", "key_claims": []}, + {"order": 2, "purpose": "Proposed method", "key_claims": []}, + {"order": 3, "purpose": "Key results", "key_claims": []}, + {"order": 4, "purpose": "Conclusion and significance", "key_claims": []}, + ] + plan["estimated_length"] = "150-250 words" + elif section_type.lower() == "introduction": + plan["paragraphs"] = [ + {"order": 1, "purpose": "Hook - why this matters", "key_claims": []}, + {"order": 2, "purpose": "Background and related work", "key_claims": []}, + {"order": 3, "purpose": "Gap in existing approaches", "key_claims": []}, + {"order": 4, "purpose": "Our contribution", "key_claims": []}, + ] + plan["estimated_length"] = "400-800 words" + elif section_type.lower() == "methods": + plan["paragraphs"] = [ + {"order": 1, "purpose": "Overview of approach", "key_claims": []}, + {"order": 2, "purpose": "Dataset and preprocessing", "key_claims": []}, + {"order": 3, "purpose": "Model architecture", "key_claims": []}, + {"order": 4, "purpose": "Training procedure", "key_claims": []}, + {"order": 5, "purpose": "Evaluation metrics", "key_claims": []}, + ] + plan["estimated_length"] = "600-1000 words" + + # Associate claims with paragraphs + for i, claim in enumerate(claims_list[:4]): + if i < len(plan["paragraphs"]): + plan["paragraphs"][i]["key_claims"].append(claim.get("text", "")) + + plan["style_notes"] = style.get("abstract_pattern", "standard") + + return io.NodeOutput(section_plan=json.dumps(plan, indent=2))