import React, { memo } from "react"; import { motion } from "motion/react"; import "./parsing.css"; import type { ViewProps } from "./logic/types"; import Word from "./Word"; import { clauseVariants, createHoverEffect } from "./animations"; import { useZoom } from "./hooks/useZoom"; import { NLP } from "sortug-ai"; // Function to check if a string is a punctuation character const isLeaf = (node: NLP.Stanza.TreeNode): boolean => { return node.children && node.children.length === 0; }; const toIgnore = ["root", "s"]; // Component to render each node in the constituency tree const TreeNode = ({ node, nest = 0, idx, }: { idx: number; nest?: number; node: NLP.Stanza.TreeNode; }) => { const neatChildren = node.children.reduce( (acc: NLP.Stanza.TreeNode[], item) => { if (NLP.isPunctuation(item.label)) return acc; else return [...acc, item]; }, [], ); return !isLeaf(node) ? ( ) : ( ); }; const BranchNode = ({ node, nest = 0, idx, }: { idx: number; nest?: number; node: NLP.Stanza.TreeNode; }) => { const { viewState, handleElementClick } = useZoom(); const { level, cIndex } = viewState; const selected = cIndex === idx; const isFocused = level === "clause" && selected; const color = `rgba(${SYNTAX_COLORS[node.label]})` || "100, 100, 100"; const style: any = { "--clause-underline-color": color }; const out = toIgnore.includes(node.label.toLowerCase()); const isPunct = NLP.isPunctuation(node.label); if (isPunct) return null; const clauseLabel = out ? null : NLP.Stanza.oneDescendant(node) ? (
{node.label}
) : (
{node.label}
); return ( handleElementClick(e, idx)} whileHover={createHoverEffect( level, "sentence", SYNTAX_COLORS[node.label], )} >
{node.children.map((child, childIdx) => ( ))}
{clauseLabel}
); }; function LeafNode({ text }: { text: string }) { return {text}; } // Main Clause component interface Props extends ViewProps { sentence: NLP.Stanza.Sentence; data: NLP.Stanza.TreeNode; } function SimpleClause(props: Props) { const { sentence, data, context, rawText, idx } = props; const { viewState, handleElementClick } = useZoom(); const { level, cIndex } = viewState; const selected = cIndex === idx; const isFocused = level === "clause" && selected; console.log({ viewState, rawText, f: isFocused, idx, cIndex }); const words = extractWordsFromTree(sentence, data); const segmented = words.map((w) => w.text); console.log({ words }); return ( handleElementClick(e, idx)} whileHover={createHoverEffect(level, "sentence", "220, 200, 255")} > {level === "sentence" ? ( {rawText} ) : (
{words.map((word, wordIdx) => ( ))}
)}
); } function RecursiveClause(props: Props) { const { sentence, data, context, rawText, idx } = props; const { viewState, handleElementClick } = useZoom(); const { level, cIndex } = viewState; const selected = cIndex === idx; const isFocused = level === "clause" && selected; // If we're at the word level, display words instead of the constituency tree if (level === "word" && selected && data.children) { // Extract word objects if available const words = extractWordsFromTree(sentence, data); if (words.length > 0) { return (
{words.map((word, wordIdx) => ( w.text), }} /> ))}
); } } return ( <> ); } // Helper function to extract words from the tree function extractWordsFromTree( sentence: NLP.Stanza.Sentence, node: NLP.Stanza.TreeNode, ): NLP.Stanza.Word[] { const words: NLP.Stanza.Word[] = []; if (node.label && node.children.length === 0) { // Check if this is a punctuation node if (!NLP.isPunctuation(node.label)) { // Find the matching word in the sentence const word = sentence.words.find((w) => w.text === node.label); if (word) { words.push(word); } } } // Recursively process children if (node.children) { for (const child of node.children) { words.push(...extractWordsFromTree(sentence, child)); } } // Remove duplicates by word id const uniqueWords = Array.from( new Map(words.map((word) => [word?.id, word])).values(), ).filter(Boolean) as NLP.Stanza.Word[]; return uniqueWords; } // Syntax highlighting colors - reusing from Stanza utils const SYNTAX_COLORS: any = { // Sentence - cornflower blue S: "100,149,237", // Sentence - cornflower blue SBAR: "100,149,237", // Sentence - cornflower blue SBARQ: "100,149,237", // Noun Phrase - coral NP: "255,127,80", // Verb Phrase - lime green VP: "50,205,50", // Prepositional Phrase - medium purple PP: "147,112,219", // Adjective Phrase - gold AP: "255,215,0", // Adverb Phrase - hot pink AVP: "255,105,180", // Noun - light salmon NN: "255,160,122", // Verb - light green V: "144,238,144", // Verb - light green VB: "144,238,144", // Verb - light green VBP: "144,238,144", // Verb - light green VBG: "144,238,144", // Verb - light green VBZ: "144,238,144", // Verb - light green VBD: "144,238,144", // Verb - light green VBN: "144,238,144", // Adjective - khaki JJ: "240,230,140", // Adverb - plum ADV: "221,160,221", // Preposition - light sky blue PR: "135,206,250", // Preposition - light sky blue IN: "135,206,250", // Preposition - light sky blue TO: "135,206,250", // Determiner - light gray DT: "211,211,211", // Personal pronoun - thistle PPN: "216,191,216", // Coordinating conjunction - dark gray CC: "169,169,169", }; export default memo(RecursiveClause);