feat: add ReviewImport and ReviewAtomize nodes

This commit is contained in:
诺斯费拉图 2026-04-12 18:10:46 +08:00
parent 8140dac594
commit dc952f6daf
3 changed files with 162 additions and 1 deletions

View File

@ -18,7 +18,11 @@ class ResearchExtension(ComfyExtension):
from custom_nodes.research.methods_draft import MethodsDraft
from custom_nodes.research.consistency_check import ConsistencyCheck
from custom_nodes.research.export_manuscript import ExportManuscript
return [PaperSearch, PaperClaimExtract, ClaimEvidenceAssemble, StyleProfileExtract, ReferencePaperSelect, SectionPlan, AbstractDraft, IntroductionDraft, MethodsDraft, ConsistencyCheck, ExportManuscript]
from custom_nodes.research.review_import import ReviewImport
from custom_nodes.research.review_atomize import ReviewAtomize
from custom_nodes.research.review_classify import ReviewClassify
from custom_nodes.research.review_map import ReviewMap
return [PaperSearch, PaperClaimExtract, ClaimEvidenceAssemble, StyleProfileExtract, ReferencePaperSelect, SectionPlan, AbstractDraft, IntroductionDraft, MethodsDraft, ConsistencyCheck, ExportManuscript, ReviewImport, ReviewAtomize, ReviewClassify, ReviewMap]
async def comfy_entrypoint() -> ComfyExtension:

View File

@ -0,0 +1,109 @@
# custom_nodes/research/review_atomize.py
"""ReviewAtomize node - split review into atomic items."""
import json
import re
from typing_extensions import override
from comfy_api.latest import ComfyNode, io
class ReviewAtomize(io.ComfyNode):
"""Split review text into atomic feedback items."""
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="ReviewAtomize",
display_name="Atomize Review",
category="Research",
inputs=[
io.String.Input(
"raw_review",
display_name="Raw Review (JSON)",
default="{}",
multiline=True,
),
io.Int.Input(
"min_severity",
display_name="Min Severity to Extract",
default=1,
min=1,
max=3,
step=1,
),
],
outputs=[
io.String.Output(display_name="Review Items (JSON)"),
],
)
@classmethod
def execute(cls, raw_review: str, min_severity: int) -> io.NodeOutput:
try:
review = json.loads(raw_review) if raw_review else {}
except json.JSONDecodeError:
review = {"original_text": raw_review}
original_text = review.get("original_text", "")
paragraphs = review.get("paragraphs", [])
items = []
item_id = 1
# Common reviewer phrases that indicate feedback
feedback_patterns = [
(r"the (?:paper|method|approach|model|results?|experiments?) (?:is|are|should|could|may|might) (.+)", "methodology"),
(r"(?:it is|this is|there is) (?:not|rarely|insufficiently) (.+)", "gap"),
(r"(?:more|additional|further) (.+) (?:is|are) needed", "missing"),
(r"(?:unclear|confusing|vague|ambiguous) (.+)", "clarity"),
(r"(?:incorrect|inaccurate|wrong|error) (.+)", "error"),
(r"(?:major|significant|critical|important) (.+)", "major"),
(r"(?:minor|small|small) (.+)", "minor"),
(r"see (?:Section|Figure|Table) (\d+)", "reference"),
]
# Split by sentence and extract items
sentences = re.split(r'(?<=[.!?])\s+', original_text)
for sentence in sentences:
sentence = sentence.strip()
if len(sentence) < 20: # Skip very short sentences
continue
# Classify the item
item_type = "general"
severity = 1
sentence_lower = sentence.lower()
if any(w in sentence_lower for w in ["major", "significant", "critical", "important", "must"]):
severity = 3
item_type = "major_concern"
elif any(w in sentence_lower for w in ["minor", "small", "suggestion"]):
severity = 1
item_type = "suggestion"
elif any(w in sentence_lower for w in ["unclear", "confusing", "vague", "ambiguous"]):
severity = 2
item_type = "clarity"
elif any(w in sentence_lower for w in ["error", "incorrect", "wrong", "mistake"]):
severity = 3
item_type = "error"
elif any(w in sentence_lower for w in ["missing", "lacking", "insufficient", "not enough"]):
severity = 2
item_type = "missing_info"
if severity >= min_severity:
items.append({
"id": f"item_{item_id}",
"text": sentence,
"type": item_type,
"severity": severity,
"quoted_claims": [],
})
item_id += 1
result = {
"total_items": len(items),
"items": items,
}
return io.NodeOutput(review_items=json.dumps(result, indent=2))

View File

@ -0,0 +1,48 @@
# custom_nodes/research/review_import.py
"""ReviewImport node - import review text."""
import json
from typing_extensions import override
from comfy_api.latest import ComfyNode, io
class ReviewImport(io.ComfyNode):
"""Import reviewer feedback text for analysis."""
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="ReviewImport",
display_name="Import Review",
category="Research",
inputs=[
io.String.Input(
"review_text",
display_name="Review Text",
default="",
multiline=True,
),
io.String.Input(
"reviewer_role",
display_name="Reviewer Role",
default="reviewer",
),
],
outputs=[
io.String.Output(display_name="Raw Review (JSON)"),
],
)
@classmethod
def execute(cls, review_text: str, reviewer_role: str) -> io.NodeOutput:
if not review_text.strip():
return io.NodeOutput(raw_review=json.dumps({"error": "No review text provided"}))
# Parse the review into structured format
raw_review = {
"reviewer_role": reviewer_role,
"original_text": review_text,
"paragraphs": [p.strip() for p in review_text.split("\n\n") if p.strip()],
"word_count": len(review_text.split()),
}
return io.NodeOutput(raw_review=json.dumps(raw_review, indent=2))