import { TemporalNode, TemporalLink, GraphFrame } from "@/types/temporal"; /** * Standardize node data from different sources */ export function standardizeNodes(entities: any[]): TemporalNode[] { if (!Array.isArray(entities)) { console.warn("Entities is not an array, returning empty array"); return []; } return entities .filter((entity) => { if (!entity || typeof entity !== "object") { console.warn("Skipping invalid entity (not an object):", entity); return false; } if (!entity.id || typeof entity.id !== "string") { console.warn("Skipping entity with invalid ID:", entity); return false; } return true; }) .map((entity) => ({ id: entity.id, name: entity.name || entity.id, type: entity.type || "Unknown", raw_prompt: entity.raw_prompt || "", x: entity.x, y: entity.y, fx: entity.fx || null, fy: entity.fy || null, })); } /** * Standardize link data from different sources */ export function standardizeLinks( relations: any[], validNodeIds: Set ): TemporalLink[] { if (!Array.isArray(relations)) { console.warn("Relations is not an array, returning empty array"); return []; } return relations .filter((relation) => { if (!relation || typeof relation !== "object") { console.warn("Skipping invalid relation (not an object):", relation); return false; } if (!relation.id || typeof relation.id !== "string") { console.warn("Skipping relation with invalid ID:", relation); return false; } if (!relation.source || !relation.target) { console.warn("Skipping relation with missing source/target:", relation); return false; } // Check if both source and target nodes exist if (!validNodeIds.has(relation.source)) { console.warn( `Skipping relation ${relation.id}: source node '${relation.source}' not found` ); return false; } if (!validNodeIds.has(relation.target)) { console.warn( `Skipping relation ${relation.id}: target node '${relation.target}' not found` ); return false; } return true; }) .map((relation) => ({ id: relation.id, source: relation.source, target: relation.target, type: relation.type || "Unknown", interaction_prompt: relation.interaction_prompt || "", value: relation.value || 1, })); } /** * Validate and clean graph data */ export function validateGraphData(data: any): GraphFrame { const nodes = standardizeNodes(data.entities || data.nodes || []); const validNodeIds = new Set(nodes.map((node) => node.id)); const links = standardizeLinks( data.relations || data.links || [], validNodeIds ); // Log warnings if data was filtered const originalNodeCount = data.entities?.length || data.nodes?.length || 0; const originalLinkCount = data.relations?.length || data.links?.length || 0; if (nodes.length < originalNodeCount) { console.warn( `Filtered out ${originalNodeCount - nodes.length} invalid nodes` ); } if (links.length < originalLinkCount) { console.warn( `Filtered out ${originalLinkCount - links.length} invalid links` ); } return { nodes, links, metadata: data.metadata || { entity_count: nodes.length, relation_count: links.length, }, }; } /** * Get node color based on type - Modern design system colors */ export function getNodeColor(type?: string): string { const colors: Record = { // Core entity types - aligned with sidebar design system Agent: "#ef4444", // Red - matches modern design Tool: "#10b981", // Emerald - matches sidebar entities Task: "#3b82f6", // Blue - matches sidebar relations Input: "#8b5cf6", // Purple - professional accent Output: "#f59e0b", // Amber - warm accent Human: "#ec4899", // Pink - human interaction Entity: "#06b6d4", // Cyan - information // Semantic types Person: "#ef4444", // Red - human entities Organization: "#10b981", // Emerald - structured entities Location: "#3b82f6", // Blue - geographic Event: "#8b5cf6", // Purple - temporal Concept: "#f59e0b", // Amber - abstract Product: "#ec4899", // Pink - tangible items // Technical types Service: "#06b6d4", // Cyan - technical services Database: "#84cc16", // Lime - data storage API: "#f97316", // Orange - interfaces Process: "#6366f1", // Indigo - workflows }; return colors[type || "Unknown"] || "#64748b"; // Modern gray default } /** * Get node size based on type */ export function getNodeSize(_type?: string): number { return 75; } /** * Format tooltip content for nodes */ export function formatNodeTooltip(node: TemporalNode): string { return `${node.name || node.id}\nType: ${node.type || "Unknown"}\n${ node.raw_prompt || "" }`; } /** * Format tooltip content for links */ export function formatLinkTooltip(link: TemporalLink): string { return `${link.type || "Relation"}\n${link.interaction_prompt || ""}`; } export function processLinksForVisualization( links: TemporalLink[] ): TemporalLink[] { // Group links by source-target pairs to handle multiple connections const linkGroups = new Map(); links.forEach((link) => { const sourceId = typeof link.source === "object" ? link.source.id : link.source; const targetId = typeof link.target === "object" ? link.target.id : link.target; const key = `${sourceId}-${targetId}`; const reverseKey = `${targetId}-${sourceId}`; // Use consistent key for bidirectional relationships const groupKey = sourceId < targetId ? key : reverseKey; if (!linkGroups.has(groupKey)) { linkGroups.set(groupKey, []); } linkGroups.get(groupKey)!.push(link); }); // Assign link indices for visual separation const processedLinks: TemporalLink[] = []; linkGroups.forEach((groupLinks, _key) => { groupLinks.forEach((link, index) => { processedLinks.push({ ...link, linkIndex: index, groupSize: groupLinks.length, }); }); }); return processedLinks; } /** * Get relation color based on type */ export function getRelationColor(type?: string): string { const colors: Record = { // Data Flow Relations (Blue family) CONSUMED_BY: "#3b82f6", // Blue - data input PRODUCES: "#1d4ed8", // Darker blue - data output DELIVERS_TO: "#60a5fa", // Light blue - delivery // Execution Relations (Green family) PERFORMS: "#10b981", // Emerald - primary execution ASSIGNED_TO: "#059669", // Dark emerald - assignment // Tool Relations (Orange family) USES: "#f59e0b", // Amber - tool usage REQUIRED_BY: "#d97706", // Dark amber - tool dependency // Workflow Relations (Purple family) SUBTASK_OF: "#8b5cf6", // Violet - hierarchy NEXT: "#7c3aed", // Dark violet - sequence // Human Relations (Pink family) INTERVENES: "#ec4899", // Pink - human intervention REVIEWS: "#db2777", // Dark pink - human review }; return colors[type || "Unknown"] || "#64748b"; // Default gray } /** * Get relation line style based on type */ export function getRelationLineStyle(type?: string): string { const styles: Record = { // Primary actions - solid thick lines PERFORMS: "solid", ASSIGNED_TO: "solid", // Data flow - dotted lines CONSUMED_BY: "dotted", PRODUCES: "dotted", DELIVERS_TO: "dotted", // Tool usage - dashed lines USES: "dashed", REQUIRED_BY: "dashed", // Workflow - solid thin lines SUBTASK_OF: "solid", NEXT: "solid", // Human interactions - solid lines INTERVENES: "solid", REVIEWS: "solid", }; return styles[type || "Unknown"] || "solid"; } /** * Get relation width multiplier based on importance and type */ export function getRelationWidth(type?: string, importance?: string): number { // Base width multipliers by type const typeWidths: Record = { PERFORMS: 1.2, // Thicker for primary actions ASSIGNED_TO: 1.1, CONSUMED_BY: 1.0, // Standard for data flow PRODUCES: 1.0, DELIVERS_TO: 0.9, USES: 0.8, // Thinner for tool usage REQUIRED_BY: 0.8, SUBTASK_OF: 0.7, // Thinnest for workflow NEXT: 0.7, INTERVENES: 1.1, // Prominent for human actions REVIEWS: 1.0, }; // Importance multipliers const importanceMultipliers: Record = { CRITICAL: 1.5, HIGH: 1.2, MEDIUM: 1.0, LOW: 0.8, NEGLIGIBLE: 0.6, }; const baseWidth = typeWidths[type || "Unknown"] || 1.0; const importanceMultiplier = importanceMultipliers[importance || "MEDIUM"] || 1.0; return baseWidth * importanceMultiplier; } /** * Get node shape based on type for better visual differentiation */ export function getNodeShape(_type?: string): string { return "ellipse"; } /** * Get border style based on entity importance */ export function getNodeBorderStyle(importance?: string): string { const styles: Record = { CRITICAL: "solid", // Thick solid border HIGH: "dashed", // Dashed border MEDIUM: "solid", // Normal solid border LOW: "dotted", // Dotted border NEGLIGIBLE: "dotted", // Light dotted border }; return styles[importance || "MEDIUM"] || "solid"; } /** * Get border width based on importance */ export function getNodeBorderWidth(importance?: string): number { const widths: Record = { CRITICAL: 4, // Very thick HIGH: 3, // Thick MEDIUM: 2, // Normal LOW: 1, // Thin NEGLIGIBLE: 1, // Very thin }; return widths[importance || "MEDIUM"] || 2; } /** * Get text styling based on node type */ export function getNodeTextStyle(type?: string): { weight: string; transform: string; decoration: string; } { const styles: Record< string, { weight: string; transform: string; decoration: string } > = { Agent: { weight: "bold", transform: "uppercase", decoration: "none" }, Human: { weight: "bold", transform: "capitalize", decoration: "none" }, Task: { weight: "600", transform: "none", decoration: "none" }, Tool: { weight: "500", transform: "lowercase", decoration: "none" }, Input: { weight: "400", transform: "none", decoration: "none" }, Output: { weight: "600", transform: "none", decoration: "underline" }, }; return ( styles[type || "Unknown"] || { weight: "500", transform: "none", decoration: "none", } ); } /** * Get shadow configuration based on node type */ export function getNodeShadow(type?: string): { blur: number; color: string; offsetX: number; offsetY: number; opacity: number; } { const shadows: Record = { Agent: { blur: 16, color: "rgba(0, 0, 0, 0.25)", offsetX: 0, offsetY: 6, opacity: 0.4, }, Human: { blur: 20, color: "rgba(236, 72, 153, 0.3)", offsetX: 0, offsetY: 8, opacity: 0.5, }, Task: { blur: 12, color: "rgba(0, 0, 0, 0.15)", offsetX: 0, offsetY: 4, opacity: 0.3, }, Tool: { blur: 8, color: "rgba(245, 158, 11, 0.3)", offsetX: 2, offsetY: 2, opacity: 0.4, }, Input: { blur: 6, color: "rgba(139, 92, 246, 0.2)", offsetX: -2, offsetY: 2, opacity: 0.3, }, Output: { blur: 14, color: "rgba(239, 68, 68, 0.3)", offsetX: 2, offsetY: 6, opacity: 0.4, }, }; return ( shadows[type || "Unknown"] || { blur: 12, color: "rgba(0, 0, 0, 0.15)", offsetX: 0, offsetY: 4, opacity: 0.3, } ); } /** * Get high-contrast text color for relation labels */ export function getRelationTextColor(type?: string): string { const textColors: Record = { // Data Flow Relations - Dark blue family for good contrast CONSUMED_BY: "#1e3a8a", // Dark blue PRODUCES: "#1e2a5e", // Very dark blue DELIVERS_TO: "#1e40af", // Medium dark blue // Execution Relations - Dark green family PERFORMS: "#047857", // Dark emerald ASSIGNED_TO: "#064e3b", // Very dark emerald // Tool Relations - Dark orange/amber family USES: "#b45309", // Dark amber REQUIRED_BY: "#92400e", // Very dark amber // Workflow Relations - Dark purple family SUBTASK_OF: "#6b21a8", // Dark violet NEXT: "#581c87", // Very dark violet // Human Relations - Dark pink family INTERVENES: "#be185d", // Dark pink REVIEWS: "#9d174d", // Very dark pink }; return textColors[type || "Unknown"] || "#374151"; // Default dark gray }