Spaces:
Running
Running
| import { | |
| UniversalGraphData, | |
| UniversalNode, | |
| UniversalLink, | |
| GraphDataAdapter, | |
| GraphVisualizationConfig, | |
| GraphVisualizationType, | |
| } from "@/types/graph-visualization"; | |
| import { TemporalNode, TemporalLink, GraphFrame } from "@/types/temporal"; | |
| import { Entity, Relation } from "@/types"; | |
| import { | |
| detectSchemaType, | |
| getSchemaCapabilities, | |
| } from "@/lib/schema-detection"; | |
| // Temporal Data Adapter | |
| export class TemporalDataAdapter implements GraphDataAdapter<GraphFrame> { | |
| adapt(data: GraphFrame): UniversalGraphData { | |
| const nodes: UniversalNode[] = data.nodes.map((node: TemporalNode) => ({ | |
| id: node.id, | |
| name: node.name || node.id, | |
| label: node.name || node.id, | |
| type: node.type || "entity", | |
| raw_prompt: node.raw_prompt, | |
| importance: (node as any).importance, | |
| x: node.x, | |
| y: node.y, | |
| fx: node.fx, | |
| fy: node.fy, | |
| properties: { ...node }, | |
| })); | |
| const links: UniversalLink[] = data.links.map((link: TemporalLink) => ({ | |
| id: link.id, | |
| source: typeof link.source === "string" ? link.source : link.source.id, | |
| target: typeof link.target === "string" ? link.target : link.target.id, | |
| type: link.type || "relation", | |
| label: link.type || "relation", | |
| relationship: link.type, | |
| interaction_prompt: link.interaction_prompt, | |
| importance: (link as any).importance, | |
| value: link.value || 1, | |
| weight: link.value || 1, | |
| properties: { ...link }, | |
| })); | |
| return { | |
| nodes, | |
| links, | |
| metadata: data.metadata, | |
| }; | |
| } | |
| validate(data: GraphFrame): boolean { | |
| return ( | |
| data && | |
| Array.isArray(data.nodes) && | |
| Array.isArray(data.links) && | |
| data.nodes.every((node) => node.id) && | |
| data.links.every((link) => link.id && link.source && link.target) | |
| ); | |
| } | |
| getDefaultConfig(): Partial<GraphVisualizationConfig> { | |
| return { | |
| type: GraphVisualizationType.TEMPORAL_GRAPH, | |
| nodeRadius: 8, | |
| linkDistance: 100, | |
| chargeStrength: -300, | |
| showNodeLabels: true, | |
| showLinkLabels: true, | |
| showDirectionalArrows: true, | |
| useCurvedLinks: true, | |
| enableZoom: true, | |
| enablePan: true, | |
| enableDrag: true, | |
| enableSelection: true, | |
| layoutType: "force", | |
| animationDuration: 500, | |
| }; | |
| } | |
| } | |
| // Knowledge Graph Data Adapter | |
| export class KnowledgeGraphDataAdapter | |
| implements | |
| GraphDataAdapter<{ | |
| entities: Entity[]; | |
| relations: Relation[]; | |
| metadata?: Record<string, any>; | |
| }> | |
| { | |
| adapt(data: { | |
| entities: Entity[]; | |
| relations: Relation[]; | |
| metadata?: Record<string, any>; | |
| }): UniversalGraphData { | |
| console.log("π§ KnowledgeGraphDataAdapter.adapt() called with:", { | |
| entities: data.entities?.length || 0, | |
| relations: data.relations?.length || 0, | |
| hasMetadata: !!data.metadata, | |
| }); | |
| const nodes: UniversalNode[] = data.entities.map( | |
| (entity: Entity, index) => { | |
| // Debug logging to check entity data | |
| console.log(`Entity ${index}:`, { | |
| id: entity.id, | |
| name: entity.name, | |
| type: entity.type, | |
| hasName: !!entity.name, | |
| nameLength: entity.name?.length || 0, | |
| }); | |
| // Ensure we have a proper label - fallback to ID if name is missing | |
| const label = entity.name || entity.id || `Node ${index}`; | |
| return { | |
| id: entity.id, | |
| name: entity.name, | |
| label: label, | |
| type: entity.type, | |
| raw_prompt: (entity as any).raw_prompt, | |
| raw_prompt_ref: (entity as any).raw_prompt_ref, | |
| raw_text_ref: (entity as any).raw_text_ref, | |
| importance: (entity as any).importance, | |
| group: this.getTypeGroup(entity.type), | |
| properties: entity.properties, | |
| // Simple grid layout for initial positioning | |
| x: 100 + (index % 6) * 120, | |
| y: 100 + Math.floor(index / 6) * 100, | |
| }; | |
| } | |
| ); | |
| // Create set of valid node IDs for relation validation | |
| const validNodeIds = new Set(nodes.map((node) => node.id)); | |
| // Filter relations to only include those with valid source and target nodes | |
| const validRelations = data.relations.filter((relation: Relation) => { | |
| const hasValidSource = validNodeIds.has(relation.source); | |
| const hasValidTarget = validNodeIds.has(relation.target); | |
| if (!hasValidSource || !hasValidTarget) { | |
| console.warn( | |
| `Skipping relation ${relation.id}: ` + | |
| `source '${relation.source}' ${ | |
| hasValidSource ? "exists" : "missing" | |
| }, ` + | |
| `target '${relation.target}' ${ | |
| hasValidTarget ? "exists" : "missing" | |
| }` | |
| ); | |
| return false; | |
| } | |
| return true; | |
| }); | |
| // Log filtering stats | |
| const filteredCount = data.relations.length - validRelations.length; | |
| if (filteredCount > 0) { | |
| console.warn( | |
| `Filtered out ${filteredCount} invalid relations out of ${data.relations.length} total` | |
| ); | |
| } | |
| const links: UniversalLink[] = validRelations.map((relation: Relation) => ({ | |
| id: relation.id, | |
| source: relation.source, | |
| target: relation.target, | |
| type: relation.type, | |
| label: relation.type, | |
| relationship: relation.type, | |
| interaction_prompt: relation.interaction_prompt, | |
| interaction_prompt_ref: (relation as any).interaction_prompt_ref, | |
| importance: (relation as any).importance, | |
| value: 1, | |
| weight: 1, | |
| properties: relation.properties, | |
| })); | |
| // Detect schema type and add capabilities | |
| const schemaType = detectSchemaType(data); | |
| const schemaCapabilities = getSchemaCapabilities(schemaType); | |
| return { | |
| nodes, | |
| links, | |
| failures: (data as any).failures || [], | |
| metadata: { | |
| ...data.metadata, | |
| schema_type: schemaType, | |
| schema_capabilities: schemaCapabilities, | |
| }, | |
| }; | |
| } | |
| validate(data: { | |
| entities: Entity[]; | |
| relations: Relation[]; | |
| metadata?: Record<string, any>; | |
| }): boolean { | |
| return ( | |
| data && | |
| Array.isArray(data.entities) && | |
| Array.isArray(data.relations) && | |
| data.entities.every((entity) => entity.id && entity.name) && | |
| data.relations.every( | |
| (relation) => | |
| relation.id && relation.source && relation.target && relation.type | |
| ) | |
| ); | |
| } | |
| getDefaultConfig(): Partial<GraphVisualizationConfig> { | |
| return { | |
| type: GraphVisualizationType.KNOWLEDGE_GRAPH, | |
| nodeRadius: 15, | |
| linkDistance: 100, | |
| chargeStrength: -300, | |
| showNodeLabels: true, | |
| showLinkLabels: true, | |
| showDirectionalArrows: false, | |
| useCurvedLinks: false, | |
| enableZoom: true, | |
| enablePan: true, | |
| enableDrag: true, | |
| enableSelection: true, | |
| layoutType: "force", | |
| animationDuration: 500, | |
| nodeColorScheme: [ | |
| "#ff6b6b", | |
| "#4ecdc4", | |
| "#45b7d1", | |
| "#96ceb4", | |
| "#feca57", | |
| "#ff9ff3", | |
| ], | |
| }; | |
| } | |
| private getTypeGroup(type: string): number { | |
| const typeGroups: Record<string, number> = { | |
| Person: 1, | |
| Organization: 2, | |
| Location: 3, | |
| Event: 4, | |
| Concept: 5, | |
| Product: 6, | |
| }; | |
| return typeGroups[type] || 0; | |
| } | |
| } | |
| // Reference-Based Data Adapter (extends KnowledgeGraphDataAdapter) | |
| export class ReferenceBasedDataAdapter extends KnowledgeGraphDataAdapter { | |
| adapt(data: { | |
| entities: Entity[]; | |
| relations: Relation[]; | |
| failures?: Array<any>; | |
| metadata?: Record<string, any>; | |
| }): UniversalGraphData { | |
| console.log( | |
| "π― ReferenceBasedDataAdapter.adapt() called - will delegate to parent" | |
| ); | |
| // Use parent adapter for basic conversion | |
| const result = super.adapt(data); | |
| // Enhance with reference-based specific features | |
| result.failures = data.failures || []; | |
| // Ensure schema type is set to reference_based | |
| if (result.metadata) { | |
| result.metadata.schema_type = "reference_based"; | |
| result.metadata.schema_capabilities = | |
| getSchemaCapabilities("reference_based"); | |
| } | |
| return result; | |
| } | |
| validate(data: any): boolean { | |
| // Use parent validation plus check for reference-based features | |
| const basicValid = super.validate(data); | |
| if (!basicValid) return false; | |
| // Additional validation for reference-based features | |
| const hasReferenceFeatures = detectSchemaType(data) === "reference_based"; | |
| return hasReferenceFeatures; | |
| } | |
| } | |
| // Simple Graph Data Adapter (for backward compatibility) | |
| export class SimpleGraphDataAdapter | |
| implements | |
| GraphDataAdapter<{ | |
| nodes: Array<{ | |
| id: string; | |
| name: string; | |
| type: string; | |
| [key: string]: any; | |
| }>; | |
| links: Array<{ | |
| source: string; | |
| target: string; | |
| type: string; | |
| [key: string]: any; | |
| }>; | |
| }> | |
| { | |
| adapt(data: { | |
| nodes: Array<{ | |
| id: string; | |
| name: string; | |
| type: string; | |
| [key: string]: any; | |
| }>; | |
| links: Array<{ | |
| source: string; | |
| target: string; | |
| type: string; | |
| [key: string]: any; | |
| }>; | |
| }): UniversalGraphData { | |
| const nodes: UniversalNode[] = data.nodes.map((node, index) => ({ | |
| id: node.id, | |
| name: node.name, | |
| label: node.name, | |
| type: node.type, | |
| importance: node.importance, | |
| properties: { ...node }, | |
| // Simple grid layout | |
| x: 100 + (index % 4) * 120, | |
| y: 100 + Math.floor(index / 4) * 100, | |
| })); | |
| const links: UniversalLink[] = data.links.map((link, index) => ({ | |
| id: `link-${index}`, | |
| source: link.source, | |
| target: link.target, | |
| type: link.type, | |
| label: link.type, | |
| relationship: link.type, | |
| importance: link.importance, | |
| value: 1, | |
| weight: 1, | |
| properties: { ...link }, | |
| })); | |
| return { | |
| nodes, | |
| links, | |
| metadata: { | |
| entity_count: nodes.length, | |
| relation_count: links.length, | |
| }, | |
| }; | |
| } | |
| validate(data: { | |
| nodes: Array<{ | |
| id: string; | |
| name: string; | |
| type: string; | |
| [key: string]: any; | |
| }>; | |
| links: Array<{ | |
| source: string; | |
| target: string; | |
| type: string; | |
| [key: string]: any; | |
| }>; | |
| }): boolean { | |
| return ( | |
| data && | |
| Array.isArray(data.nodes) && | |
| Array.isArray(data.links) && | |
| data.nodes.every((node) => node.id && node.name && node.type) && | |
| data.links.every((link) => link.source && link.target && link.type) | |
| ); | |
| } | |
| getDefaultConfig(): Partial<GraphVisualizationConfig> { | |
| return { | |
| type: GraphVisualizationType.SIMPLE_GRAPH, | |
| nodeRadius: 20, | |
| linkDistance: 80, | |
| chargeStrength: -200, | |
| showNodeLabels: true, | |
| showLinkLabels: true, | |
| showDirectionalArrows: false, | |
| useCurvedLinks: false, | |
| enableZoom: false, | |
| enablePan: false, | |
| enableDrag: false, | |
| enableSelection: false, | |
| layoutType: "grid", | |
| animationDuration: 0, | |
| }; | |
| } | |
| } | |
| // Factory function to get appropriate adapter | |
| export function getGraphDataAdapter( | |
| type?: GraphVisualizationType, | |
| data?: any | |
| ): GraphDataAdapter { | |
| console.log("π getGraphDataAdapter called with:", { type, hasData: !!data }); | |
| // Auto-detect schema if data provided but no type specified | |
| if (!type && data) { | |
| try { | |
| const schemaType = detectSchemaType(data); | |
| console.log( | |
| "π Auto-detecting adapter type based on schema:", | |
| schemaType | |
| ); | |
| if (schemaType === "reference_based") { | |
| type = GraphVisualizationType.KNOWLEDGE_GRAPH; | |
| } else { | |
| type = GraphVisualizationType.SIMPLE_GRAPH; | |
| } | |
| } catch (error) { | |
| console.warn( | |
| "Schema detection failed in adapter factory, using default KNOWLEDGE_GRAPH:", | |
| error | |
| ); | |
| type = GraphVisualizationType.KNOWLEDGE_GRAPH; | |
| } | |
| } | |
| // Default to KNOWLEDGE_GRAPH if no type provided | |
| if (!type) { | |
| type = GraphVisualizationType.KNOWLEDGE_GRAPH; | |
| } | |
| switch (type) { | |
| case GraphVisualizationType.TEMPORAL_GRAPH: | |
| return new TemporalDataAdapter(); | |
| case GraphVisualizationType.KNOWLEDGE_GRAPH: | |
| // Use ReferenceBasedDataAdapter for reference-based data, otherwise KnowledgeGraphDataAdapter | |
| if (data) { | |
| try { | |
| if (detectSchemaType(data) === "reference_based") { | |
| console.log( | |
| "β Using ReferenceBasedDataAdapter for reference-based schema" | |
| ); | |
| return new ReferenceBasedDataAdapter(); | |
| } | |
| } catch (error) { | |
| console.warn( | |
| "Schema detection failed during adapter selection, using KnowledgeGraphDataAdapter:", | |
| error | |
| ); | |
| } | |
| } | |
| console.log( | |
| "β Using KnowledgeGraphDataAdapter for direct-based or unknown schema" | |
| ); | |
| return new KnowledgeGraphDataAdapter(); | |
| case GraphVisualizationType.SIMPLE_GRAPH: | |
| console.log("β Using SimpleGraphDataAdapter"); | |
| return new SimpleGraphDataAdapter(); | |
| default: | |
| console.log("β Using KnowledgeGraphDataAdapter (default fallback)"); | |
| return new KnowledgeGraphDataAdapter(); // Default fallback | |
| } | |
| } | |