From d719dafa07490b4e0dec6ae0da5f0afdb8121d73 Mon Sep 17 00:00:00 2001 From: sid597 Date: Wed, 15 Apr 2026 14:07:45 +0530 Subject: [PATCH 1/4] ENG-1596: Make extract results panel data-driven Replaces the skeleton's hardcoded SAMPLE_NODES + fixed EXPANDED_INDICES with a props-driven MainContent. Accepts ExtractedNode[], derives the filter tabs from the actual nodes, wires per-row expand/collapse, wires the type-filter tabs, and renders an empty state when no nodes. --- .../extract-nodes/components/MainContent.tsx | 224 ++++++++---------- .../app/(extract)/extract-nodes/page.tsx | 84 ++++++- 2 files changed, 187 insertions(+), 121 deletions(-) diff --git a/apps/website/app/(extract)/extract-nodes/components/MainContent.tsx b/apps/website/app/(extract)/extract-nodes/components/MainContent.tsx index 9f321a358..deedb185c 100644 --- a/apps/website/app/(extract)/extract-nodes/components/MainContent.tsx +++ b/apps/website/app/(extract)/extract-nodes/components/MainContent.tsx @@ -1,8 +1,12 @@ +"use client"; + +import { useState, useMemo } from "react"; import { Badge } from "@repo/ui/components/ui/badge"; import { Button } from "@repo/ui/components/ui/button"; import { Card, CardContent } from "@repo/ui/components/ui/card"; import { Checkbox } from "@repo/ui/components/ui/checkbox"; import { Copy } from "lucide-react"; +import type { ExtractedNode } from "~/types/extraction"; const NODE_TYPE_COLORS: Record = { claim: "#7DA13E", @@ -14,141 +18,119 @@ const NODE_TYPE_COLORS: Record = { theory: "#8B5CF6", }; -const SAMPLE_NODES = [ - { - nodeType: "claim", - content: - "Basolateral secretion of Wnt5a is essential for establishing apical-basal polarity in epithelial cells.", - supportSnippet: - '"Wnt5a secreted from the basolateral surface was both necessary and sufficient for the establishment of apical-basal polarity" (p.9)', - sourceSection: "Discussion", - }, - { - nodeType: "evidence", - content: - "Wnt5a was detected exclusively in the basolateral medium of polarized MDCK cells grown on Transwell filters, with no detectable signal in the apical fraction.", - supportSnippet: - '"Western blot analysis of conditioned media showed Wnt5a protein exclusively in the basolateral fraction (Fig. 2A, lanes 3-4)"', - sourceSection: "Results", - }, - { - nodeType: "question", - content: - "What is the mechanism by which Wnt5a polarized secretion is directed to the basolateral membrane?", - supportSnippet: - '"The mechanism that directs Wnt5a specifically to the basolateral surface remains an open question" (p.11)', - sourceSection: "Discussion", - }, - { - nodeType: "hypothesis", - content: - "Ror2 receptor activation at the basolateral surface mediates Wnt5a-dependent lumen positioning.", - supportSnippet: - '"We hypothesize that Ror2, as the primary receptor for Wnt5a at the basolateral membrane, transduces the polarity signal required for single-lumen formation"', - sourceSection: "Discussion", - }, - { - nodeType: "result", - content: - "shRNA-mediated knockdown of Wnt5a resulted in multi-lumen cysts in 68% of colonies compared to 12% in control conditions.", - supportSnippet: - '"Quantification of cyst morphology revealed 68 ± 4% multi-lumen cysts in Wnt5a-KD versus 12 ± 3% in controls (Fig. 4B, p < 0.001)"', - sourceSection: "Results", - }, - { - nodeType: "source", - content: "Yamamoto et al. (2015) Nature Cell Biology 17(8):1024-1035", - supportSnippet: - "Primary research article on Wnt5a basolateral secretion and lumen formation in polarized epithelia.", - sourceSection: "References", - }, - { - nodeType: "theory", - content: - "Non-canonical Wnt signaling through the planar cell polarity pathway is a conserved mechanism for epithelial lumen morphogenesis.", - supportSnippet: - '"Our findings place Wnt5a upstream of the PCP pathway in the regulation of epithelial lumen morphogenesis, consistent with the broader role of non-canonical Wnt signaling in tissue polarity"', - sourceSection: "Discussion", - }, - { - nodeType: "evidence", - content: - "Co-immunoprecipitation showed that Wnt5a preferentially binds Ror2 receptor at the basolateral surface.", - supportSnippet: - '"IP-Western analysis demonstrated direct Wnt5a-Ror2 interaction in basolateral but not apical membrane fractions (Fig. 5C)"', - sourceSection: "Results", - }, - { - nodeType: "claim", - content: - "Loss of Wnt5a function disrupts lumen formation in 3D cyst cultures derived from epithelial cells.", - supportSnippet: - '"These data demonstrate that Wnt5a is required for proper lumen formation in three-dimensional culture systems"', - sourceSection: "Discussion", - }, -]; +type MainContentProps = { + nodes: ExtractedNode[]; + paperTitle?: string; +}; + +export const MainContent = ({ + nodes, + paperTitle, +}: MainContentProps): React.ReactElement => { + const [expandedNodes, setExpandedNodes] = useState>(new Set()); + const [activeFilter, setActiveFilter] = useState("all"); + + const typeCounts = useMemo( + () => + nodes.reduce>((acc, node) => { + const key = node.nodeType.toLowerCase(); + acc[key] = (acc[key] ?? 0) + 1; + return acc; + }, {}), + [nodes], + ); + + const tabs = useMemo( + () => [ + { id: "all", label: "All", count: nodes.length, color: undefined }, + ...Object.entries(typeCounts).map(([nodeType, count]) => ({ + id: nodeType, + label: nodeType.charAt(0).toUpperCase() + nodeType.slice(1), + count, + color: NODE_TYPE_COLORS[nodeType], + })), + ], + [nodes.length, typeCounts], + ); -const EXPANDED_INDICES = new Set([0, 1]); + const filteredNodes = useMemo( + () => + activeFilter === "all" + ? nodes + : nodes.filter((node) => node.nodeType.toLowerCase() === activeFilter), + [nodes, activeFilter], + ); -const typeCounts = SAMPLE_NODES.reduce>((acc, node) => { - acc[node.nodeType] = (acc[node.nodeType] ?? 0) + 1; - return acc; -}, {}); + const toggleExpanded = (index: number): void => { + setExpandedNodes((prev) => { + const next = new Set(prev); + if (next.has(index)) { + next.delete(index); + } else { + next.add(index); + } + return next; + }); + }; -const TABS = [ - { id: "all", label: "All", count: SAMPLE_NODES.length, color: undefined }, - ...Object.entries(typeCounts).map(([nodeType, count]) => ({ - id: nodeType, - label: nodeType.charAt(0).toUpperCase() + nodeType.slice(1), - count, - color: NODE_TYPE_COLORS[nodeType], - })), -]; + if (nodes.length === 0) { + return ( +
+
+

+ No extracted nodes yet +

+

+ Upload a paper and run extraction to see results here. +

+
+
+ ); + } -export const MainContent = (): React.ReactElement => { return (
-
-
-

- Basolateral secretion of Wnt5a in polarized epithelial cells is - required for apical lumen formation -

-

- Yamamoto H, Komekado H, Kikuchi A -

-
+ {paperTitle && ( +
+
+

+ {paperTitle} +

+
+ )}
- {TABS.map((tab) => ( - - {tab.color && ( - - )} - {tab.label} {tab.count} - + {tabs.map((tab) => ( + ))}
- {SAMPLE_NODES.map((node, index) => { - const color = NODE_TYPE_COLORS[node.nodeType] ?? "#64748b"; - const isExpanded = EXPANDED_INDICES.has(index); + {filteredNodes.map((node, index) => { + const color = + NODE_TYPE_COLORS[node.nodeType.toLowerCase()] ?? "#64748b"; + const isExpanded = expandedNodes.has(index); return ( @@ -182,6 +164,7 @@ export const MainContent = (): React.ReactElement => { variant="ghost" size="sm" className="mt-1 h-auto p-0 text-[13px] font-medium text-slate-400 hover:text-slate-600" + onClick={() => toggleExpanded(index)} > Hide details @@ -191,6 +174,7 @@ export const MainContent = (): React.ReactElement => { variant="ghost" size="sm" className="mt-1 h-auto p-0 text-[13px] font-medium text-slate-400 hover:text-slate-600" + onClick={() => toggleExpanded(index)} > Show details @@ -214,7 +198,7 @@ export const MainContent = (): React.ReactElement => { Deselect All - {SAMPLE_NODES.length} of {SAMPLE_NODES.length} selected + {nodes.length} of {nodes.length} selected
diff --git a/apps/website/app/(extract)/extract-nodes/page.tsx b/apps/website/app/(extract)/extract-nodes/page.tsx index fa545f9de..7790fe059 100644 --- a/apps/website/app/(extract)/extract-nodes/page.tsx +++ b/apps/website/app/(extract)/extract-nodes/page.tsx @@ -1,11 +1,93 @@ +"use client"; + +import { useState } from "react"; +import type { ExtractedNode } from "~/types/extraction"; import { MainContent } from "./components/MainContent"; import { Sidebar } from "./components/Sidebar"; +// TODO(ENG-1592): Replace with actual extraction results from API +const SAMPLE_NODES: ExtractedNode[] = [ + { + nodeType: "Claim", + content: + "Basolateral secretion of Wnt5a is essential for establishing apical-basal polarity in epithelial cells.", + supportSnippet: + '"Wnt5a secreted from the basolateral surface was both necessary and sufficient for the establishment of apical-basal polarity" (p.9)', + sourceSection: "Discussion", + }, + { + nodeType: "Evidence", + content: + "Wnt5a was detected exclusively in the basolateral medium of polarized MDCK cells grown on Transwell filters, with no detectable signal in the apical fraction.", + supportSnippet: + '"Western blot analysis of conditioned media showed Wnt5a protein exclusively in the basolateral fraction (Fig. 2A, lanes 3-4)"', + sourceSection: "Results", + }, + { + nodeType: "Question", + content: + "What is the mechanism by which Wnt5a polarized secretion is directed to the basolateral membrane?", + supportSnippet: + '"The mechanism that directs Wnt5a specifically to the basolateral surface remains an open question" (p.11)', + sourceSection: "Discussion", + }, + { + nodeType: "Hypothesis", + content: + "Ror2 receptor activation at the basolateral surface mediates Wnt5a-dependent lumen positioning.", + supportSnippet: + '"We hypothesize that Ror2, as the primary receptor for Wnt5a at the basolateral membrane, transduces the polarity signal required for single-lumen formation"', + sourceSection: "Discussion", + }, + { + nodeType: "Result", + content: + "shRNA-mediated knockdown of Wnt5a resulted in multi-lumen cysts in 68% of colonies compared to 12% in control conditions.", + supportSnippet: + '"Quantification of cyst morphology revealed 68 ± 4% multi-lumen cysts in Wnt5a-KD versus 12 ± 3% in controls (Fig. 4B, p < 0.001)"', + sourceSection: "Results", + }, + { + nodeType: "Source", + content: "Yamamoto et al. (2015) Nature Cell Biology 17(8):1024-1035", + supportSnippet: + "Primary research article on Wnt5a basolateral secretion and lumen formation in polarized epithelia.", + sourceSection: "References", + }, + { + nodeType: "Theory", + content: + "Non-canonical Wnt signaling through the planar cell polarity pathway is a conserved mechanism for epithelial lumen morphogenesis.", + supportSnippet: + '"Our findings place Wnt5a upstream of the PCP pathway in the regulation of epithelial lumen morphogenesis, consistent with the broader role of non-canonical Wnt signaling in tissue polarity"', + sourceSection: "Discussion", + }, + { + nodeType: "Evidence", + content: + "Co-immunoprecipitation showed that Wnt5a preferentially binds Ror2 receptor at the basolateral surface.", + supportSnippet: + '"IP-Western analysis demonstrated direct Wnt5a-Ror2 interaction in basolateral but not apical membrane fractions (Fig. 5C)"', + sourceSection: "Results", + }, + { + nodeType: "Claim", + content: + "Loss of Wnt5a function disrupts lumen formation in 3D cyst cultures derived from epithelial cells.", + supportSnippet: + '"These data demonstrate that Wnt5a is required for proper lumen formation in three-dimensional culture systems"', + sourceSection: "Discussion", + }, +]; + const ExtractNodesPage = (): React.ReactElement => { + // TODO(ENG-1592): Wire to actual extraction API results + const [nodes] = useState(SAMPLE_NODES); + return (
- +
); }; From 1293ad61edc5f43e4d584028cbb096892ef3c595 Mon Sep 17 00:00:00 2001 From: sid597 Date: Wed, 15 Apr 2026 15:17:39 +0530 Subject: [PATCH 2/4] ENG-1596/1597/1598: Add multi-select and copy-to-clipboard --- .../extract-nodes/components/MainContent.tsx | 138 +++++++++++++----- .../app/(extract)/extract-nodes/nodeTypes.ts | 19 +++ 2 files changed, 123 insertions(+), 34 deletions(-) create mode 100644 apps/website/app/(extract)/extract-nodes/nodeTypes.ts diff --git a/apps/website/app/(extract)/extract-nodes/components/MainContent.tsx b/apps/website/app/(extract)/extract-nodes/components/MainContent.tsx index deedb185c..3b2aca1da 100644 --- a/apps/website/app/(extract)/extract-nodes/components/MainContent.tsx +++ b/apps/website/app/(extract)/extract-nodes/components/MainContent.tsx @@ -7,15 +7,21 @@ import { Card, CardContent } from "@repo/ui/components/ui/card"; import { Checkbox } from "@repo/ui/components/ui/checkbox"; import { Copy } from "lucide-react"; import type { ExtractedNode } from "~/types/extraction"; +import { NODE_TYPE_DEFINITIONS } from "../nodeTypes"; -const NODE_TYPE_COLORS: Record = { - claim: "#7DA13E", - question: "#99890E", - hypothesis: "#7C4DFF", - evidence: "#dc0c4a", - result: "#E6A23C", - source: "#9E9E9E", - theory: "#8B5CF6", +const findNodeTypeDefinition = (nodeType: string) => + NODE_TYPE_DEFINITIONS.find( + (t) => t.label.toLowerCase() === nodeType.toLowerCase(), + ); + +const formatNodeForClipboard = (node: ExtractedNode): string => { + const meta = findNodeTypeDefinition(node.nodeType); + const header = meta ? `${node.content} ${meta.candidateTag}` : node.content; + const lines = [header, `\tSource quote: "${node.supportSnippet}"`]; + if (node.sourceSection) { + lines.push(`\tSection: ${node.sourceSection}`); + } + return lines.join("\n"); }; type MainContentProps = { @@ -28,7 +34,9 @@ export const MainContent = ({ paperTitle, }: MainContentProps): React.ReactElement => { const [expandedNodes, setExpandedNodes] = useState>(new Set()); + const [selected, setSelected] = useState>(new Set()); const [activeFilter, setActiveFilter] = useState("all"); + const [copied, setCopied] = useState(false); const typeCounts = useMemo( () => @@ -47,19 +55,23 @@ export const MainContent = ({ id: nodeType, label: nodeType.charAt(0).toUpperCase() + nodeType.slice(1), count, - color: NODE_TYPE_COLORS[nodeType], + color: findNodeTypeDefinition(nodeType)?.color, })), ], [nodes.length, typeCounts], ); - const filteredNodes = useMemo( - () => - activeFilter === "all" - ? nodes - : nodes.filter((node) => node.nodeType.toLowerCase() === activeFilter), - [nodes, activeFilter], - ); + const filteredNodes = useMemo(() => { + const indexed = nodes.map((node, originalIndex) => ({ + node, + originalIndex, + })); + return activeFilter === "all" + ? indexed + : indexed.filter( + ({ node }) => node.nodeType.toLowerCase() === activeFilter, + ); + }, [nodes, activeFilter]); const toggleExpanded = (index: number): void => { setExpandedNodes((prev) => { @@ -73,6 +85,36 @@ export const MainContent = ({ }); }; + const toggleSelected = (index: number): void => { + setSelected((prev) => { + const next = new Set(prev); + if (next.has(index)) { + next.delete(index); + } else { + next.add(index); + } + return next; + }); + }; + + const selectAll = (): void => { + setSelected(new Set(nodes.keys())); + }; + + const deselectAll = (): void => { + setSelected(new Set()); + }; + + const handleCopy = async (): Promise => { + const text = [...selected] + .sort((a, b) => a - b) + .map((i) => formatNodeForClipboard(nodes[i]!)) + .join("\n"); + await navigator.clipboard.writeText(text); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + }; + if (nodes.length === 0) { return (
@@ -127,15 +169,23 @@ export const MainContent = ({
- {filteredNodes.map((node, index) => { + {filteredNodes.map(({ node, originalIndex }) => { const color = - NODE_TYPE_COLORS[node.nodeType.toLowerCase()] ?? "#64748b"; - const isExpanded = expandedNodes.has(index); + findNodeTypeDefinition(node.nodeType)?.color ?? "#64748b"; + const isExpanded = expandedNodes.has(originalIndex); + const isSelected = selected.has(originalIndex); return ( - +
- + toggleSelected(originalIndex)} + className="mt-1" + />
toggleExpanded(index)} + onClick={() => toggleExpanded(originalIndex)} > Hide details @@ -174,7 +224,7 @@ export const MainContent = ({ variant="ghost" size="sm" className="mt-1 h-auto p-0 text-[13px] font-medium text-slate-400 hover:text-slate-600" - onClick={() => toggleExpanded(index)} + onClick={() => toggleExpanded(originalIndex)} > Show details @@ -190,21 +240,41 @@ export const MainContent = ({
- - - {nodes.length} of {nodes.length} selected +
+ +
+ +
+ + {selected.size} of {nodes.length} selected
-
diff --git a/apps/website/app/(extract)/extract-nodes/nodeTypes.ts b/apps/website/app/(extract)/extract-nodes/nodeTypes.ts new file mode 100644 index 000000000..fcb7a4d80 --- /dev/null +++ b/apps/website/app/(extract)/extract-nodes/nodeTypes.ts @@ -0,0 +1,19 @@ +// TEMPORARY: mirrors the shape of NODE_TYPE_DEFINITIONS being added in +// ENG-1595 (apps/website/app/types/extraction.ts). Delete this file and +// update imports to "~/types/extraction" once ENG-1595 merges to main. + +export type NodeTypeDefinition = { + label: string; + candidateTag: string; + color: string; +}; + +export const NODE_TYPE_DEFINITIONS: NodeTypeDefinition[] = [ + { label: "Claim", candidateTag: "#clm-candidate", color: "#7DA13E" }, + { label: "Question", candidateTag: "#que-candidate", color: "#99890E" }, + { label: "Hypothesis", candidateTag: "#hyp-candidate", color: "#7C4DFF" }, + { label: "Evidence", candidateTag: "#evd-candidate", color: "#dc0c4a" }, + { label: "Result", candidateTag: "#res-candidate", color: "#E6A23C" }, + { label: "Source", candidateTag: "#src-candidate", color: "#9E9E9E" }, + { label: "Theory", candidateTag: "#the-candidate", color: "#8B5CF6" }, +]; From 8c52b552aef5a7b8c7aa0ca5d9ad073f6ad19ef7 Mon Sep 17 00:00:00 2001 From: sid597 Date: Wed, 15 Apr 2026 15:56:46 +0530 Subject: [PATCH 3/4] Align stub and sample data with ENG-1595 canonical node types --- .../app/(extract)/extract-nodes/nodeTypes.ts | 45 +++++++++++++++---- .../app/(extract)/extract-nodes/page.tsx | 15 ++++--- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/apps/website/app/(extract)/extract-nodes/nodeTypes.ts b/apps/website/app/(extract)/extract-nodes/nodeTypes.ts index fcb7a4d80..17470119f 100644 --- a/apps/website/app/(extract)/extract-nodes/nodeTypes.ts +++ b/apps/website/app/(extract)/extract-nodes/nodeTypes.ts @@ -4,16 +4,45 @@ export type NodeTypeDefinition = { label: string; + definition: string; candidateTag: string; - color: string; + color?: string; }; export const NODE_TYPE_DEFINITIONS: NodeTypeDefinition[] = [ - { label: "Claim", candidateTag: "#clm-candidate", color: "#7DA13E" }, - { label: "Question", candidateTag: "#que-candidate", color: "#99890E" }, - { label: "Hypothesis", candidateTag: "#hyp-candidate", color: "#7C4DFF" }, - { label: "Evidence", candidateTag: "#evd-candidate", color: "#dc0c4a" }, - { label: "Result", candidateTag: "#res-candidate", color: "#E6A23C" }, - { label: "Source", candidateTag: "#src-candidate", color: "#9E9E9E" }, - { label: "Theory", candidateTag: "#the-candidate", color: "#8B5CF6" }, + { + label: "Evidence", + definition: + "A specific empirical observation from a particular study. One distinct statistical test, measurement, or analytical finding. Past tense. Includes observable, model system, method.", + candidateTag: "#evd-candidate", + color: "#DB134A", + }, + { + label: "Claim", + definition: + "An atomic, generalized assertion about the world that proposes to answer a research question. Goes beyond data to state what it means. Specific enough to test or argue against.", + candidateTag: "#clm-candidate", + color: "#7DA13E", + }, + { + label: "Question", + definition: + "A research question — explicitly stated or implied by a gap in the literature. Open-ended, answerable by empirical evidence.", + candidateTag: "#que-candidate", + color: "#99890E", + }, + { + label: "Pattern", + definition: + "A conceptual class — a theoretical object, heuristic, design pattern, or methodological approach — abstracted from specific implementations.", + candidateTag: "#ptn-candidate", + color: "#E040FB", + }, + { + label: "Artifact", + definition: + "A specific concrete system, tool, standard, dataset, or protocol that instantiates one or more patterns.", + candidateTag: "#art-candidate", + color: "#67C23A", + }, ]; diff --git a/apps/website/app/(extract)/extract-nodes/page.tsx b/apps/website/app/(extract)/extract-nodes/page.tsx index 7790fe059..129e61e4a 100644 --- a/apps/website/app/(extract)/extract-nodes/page.tsx +++ b/apps/website/app/(extract)/extract-nodes/page.tsx @@ -32,7 +32,7 @@ const SAMPLE_NODES: ExtractedNode[] = [ sourceSection: "Discussion", }, { - nodeType: "Hypothesis", + nodeType: "Claim", content: "Ror2 receptor activation at the basolateral surface mediates Wnt5a-dependent lumen positioning.", supportSnippet: @@ -40,7 +40,7 @@ const SAMPLE_NODES: ExtractedNode[] = [ sourceSection: "Discussion", }, { - nodeType: "Result", + nodeType: "Evidence", content: "shRNA-mediated knockdown of Wnt5a resulted in multi-lumen cysts in 68% of colonies compared to 12% in control conditions.", supportSnippet: @@ -48,14 +48,15 @@ const SAMPLE_NODES: ExtractedNode[] = [ sourceSection: "Results", }, { - nodeType: "Source", - content: "Yamamoto et al. (2015) Nature Cell Biology 17(8):1024-1035", + nodeType: "Artifact", + content: + "MDCK 3D cyst culture model grown on Matrigel, used to visualize lumen formation by confocal live imaging of F-actin and podocalyxin markers.", supportSnippet: - "Primary research article on Wnt5a basolateral secretion and lumen formation in polarized epithelia.", - sourceSection: "References", + '"We used MDCK II cells seeded in 100% Matrigel and imaged cyst development over 96 hours using spinning-disk confocal microscopy (Materials and Methods)"', + sourceSection: "Methods", }, { - nodeType: "Theory", + nodeType: "Pattern", content: "Non-canonical Wnt signaling through the planar cell polarity pathway is a conserved mechanism for epithelial lumen morphogenesis.", supportSnippet: From 2b5eb53f04ce821d7022ab17ad68a7c6fca720f6 Mon Sep 17 00:00:00 2001 From: sid597 Date: Wed, 15 Apr 2026 22:02:08 +0530 Subject: [PATCH 4/4] Fix extract node review comments --- .../extract-nodes/components/MainContent.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/website/app/(extract)/extract-nodes/components/MainContent.tsx b/apps/website/app/(extract)/extract-nodes/components/MainContent.tsx index 3b2aca1da..f363d0136 100644 --- a/apps/website/app/(extract)/extract-nodes/components/MainContent.tsx +++ b/apps/website/app/(extract)/extract-nodes/components/MainContent.tsx @@ -1,15 +1,17 @@ "use client"; -import { useState, useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Badge } from "@repo/ui/components/ui/badge"; import { Button } from "@repo/ui/components/ui/button"; import { Card, CardContent } from "@repo/ui/components/ui/card"; import { Checkbox } from "@repo/ui/components/ui/checkbox"; import { Copy } from "lucide-react"; import type { ExtractedNode } from "~/types/extraction"; -import { NODE_TYPE_DEFINITIONS } from "../nodeTypes"; +import { NODE_TYPE_DEFINITIONS, type NodeTypeDefinition } from "../nodeTypes"; -const findNodeTypeDefinition = (nodeType: string) => +const findNodeTypeDefinition = ( + nodeType: string, +): NodeTypeDefinition | undefined => NODE_TYPE_DEFINITIONS.find( (t) => t.label.toLowerCase() === nodeType.toLowerCase(), ); @@ -38,6 +40,10 @@ export const MainContent = ({ const [activeFilter, setActiveFilter] = useState("all"); const [copied, setCopied] = useState(false); + useEffect(() => { + setSelected(new Set()); + }, [nodes]); + const typeCounts = useMemo( () => nodes.reduce>((acc, node) => {