AgentGraph / frontend /src /lib /graph-data-utils.ts
wu981526092's picture
🚀 Deploy AgentGraph: Complete agent monitoring and knowledge graph system
c2ea5ed
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<string>
): 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<string, string> = {
// 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<string, TemporalLink[]>();
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<string, string> = {
// 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<string, string> = {
// 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<string, number> = {
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<string, number> = {
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<string, string> = {
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<string, number> = {
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<string, any> = {
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<string, string> = {
// 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
}