mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-01 09:29:38 +02:00
24 KiB
24 KiB
Flow Class Visual Editor Technical Specification
Overview
A React-based visual editor for creating and modifying TrustGraph flow class definitions using a node-and-edge graph interface. Built with React Flow, this component allows users to visually design dataflow patterns by dragging processors onto a canvas and connecting them with queues.
Core Technologies
- React Flow - Node-based editor framework
- TypeScript - Type safety for flow definitions
- Chakra UI v3 - UI components and theming
- Zustand - Editor state management
- Zod - Schema validation for flow class structure
Component Architecture
Directory Structure
src/components/flow-editor/
├── FlowClassEditor.tsx # Main editor component
├── nodes/ # Custom node components
│ ├── ClassProcessorNode.tsx # Shared service nodes
│ ├── FlowProcessorNode.tsx # Flow-specific nodes
│ └── InterfaceNode.tsx # Entry/exit point nodes
├── edges/ # Custom edge components
│ ├── PersistentQueueEdge.tsx # Persistent queue connections
│ └── RequestResponseEdge.tsx # Request/response pairs
├── panels/ # Editor UI panels
│ ├── NodePalette.tsx # Drag-and-drop processor library
│ ├── PropertiesPanel.tsx # Node/edge configuration
│ └── ValidationPanel.tsx # Real-time validation feedback
├── hooks/
│ ├── useFlowValidation.ts # Validation logic
│ ├── useFlowExport.ts # JSON export/import
│ └── useAutoLayout.ts # Automatic graph layout
└── types/
└── flowEditorTypes.ts # TypeScript definitions
Visual Design
Node Types
1. Class Processor Node ({class})
{
type: 'classProcessor',
data: {
processorName: string, // e.g., "embeddings"
queues: {
request?: string, // Queue pattern
response?: string, // Queue pattern
[key: string]: string // Additional queues
}
},
style: {
background: 'accent.subtle', // Shared service color
border: '2px solid accent.solid',
icon: <Share2 /> // Lucide icon indicating shared
}
}
2. Flow Processor Node ({id})
{
type: 'flowProcessor',
data: {
processorName: string, // e.g., "chunker"
queues: {
input?: string, // Input queue
output?: string, // Output queue
[key: string]: string // Additional queues
}
},
style: {
background: 'primary.subtle', // Flow-specific color
border: '2px solid primary.solid',
icon: <Box /> // Lucide icon for isolated
}
}
3. Interface Node
{
type: 'interfaceNode',
data: {
interfaceName: string, // e.g., "document-load"
interfaceType: 'fire-and-forget' | 'request-response',
queuePattern?: string, // For fire-and-forget
request?: string, // For request-response
response?: string // For request-response
},
style: {
background: 'bg.muted',
border: '2px dashed border.muted',
icon: <Plug /> // Entry/exit point indicator
}
}
Edge Types
1. Persistent Queue Edge
- Visual: Solid line with arrow
- Color: Based on namespace (flow: green, request: blue, response: purple)
- Label: Queue name displayed on hover
- Validation: Source/target compatibility checking
2. Non-Persistent Queue Edge
- Visual: Dashed line with arrow
- Color: Lighter variant of namespace colors
- Label: Queue name with non-persistent indicator
- Validation: Request/response pairing validation
User Edit Operations
1. Processor Management
Add Processor
- Drag from palette: Drag processor type from categorized library
- Double-click canvas: Quick-add with processor type selector
- Context menu: Right-click → Add Processor → Select type
- Keyboard shortcut:
Akey opens add processor dialog
Configure Processor
- Rename: Click processor name to edit inline
- Change type: Toggle between
{class}and{id}via properties panel - Queue management:
// Add new queue to processor addQueue(processorId, { name: "custom-queue", direction: "input" | "output" | "bidirectional", pattern: "persistent://tg/flow/custom:{id}" }); // Remove queue removeQueue(processorId, queueName); // Edit queue pattern updateQueue(processorId, queueName, newPattern);
Delete Processor
- Single: Select + Delete key
- Multiple: Multi-select + Delete key
- Context menu: Right-click → Delete
- Validation: Warn if processor has connections
2. Connection Management
Create Connection
- Drag connection: From output handle to input handle
- Validation rules:
- Persistence compatibility (persistent ↔ persistent preferred)
- Namespace compatibility (flow/request/response)
- Template variable consistency ({class} ↔ {class}, {id} ↔ {id})
- No self-connections
- No duplicate connections
Configure Connection
- Auto-naming: Generate queue name from source/target processors
- Custom naming: Override auto-generated queue name
- Persistence mode: Toggle persistent/non-persistent
- Queue pattern template:
generateQueuePattern({ persistence: "persistent" | "non-persistent", tenant: "tg", namespace: "flow" | "request" | "response", topic: "document-embeddings", template: "{id}" | "{class}" }); // Result: "persistent://tg/flow/document-embeddings:{id}"
Delete Connection
- Click to select + Delete key
- Context menu on edge
- Disconnect handle: Drag connection away from handle
3. Interface Operations
Add Interface
- Entry points: Document load, text input, etc.
- Exit points: Response outputs, storage endpoints
- Service interfaces: Request/response pairs
Configure Interface Type
// Fire-and-forget pattern
interface FireAndForgetInterface {
type: "fire-and-forget";
queue: string; // Single queue pattern
}
// Request/response pattern
interface RequestResponseInterface {
type: "request-response";
request: string; // Request queue pattern
response: string; // Response queue pattern
}
4. Bulk Operations
Multi-select Actions
- Box select: Click and drag to select multiple nodes
- Shift-click: Add to selection
- Cmd-click: Toggle selection
- Select all: Cmd+A
Group Operations
- Move together: Drag any selected node moves all
- Delete together: Delete key removes all selected
- Duplicate: Cmd+D duplicates selection
- Copy/paste: Cmd+C/Cmd+V for cross-flow copying
5. Layout Operations
Auto-layout
const layoutStrategies = {
hierarchical: {
direction: "LR" | "TB", // Left-right or top-bottom
nodeSpacing: 150,
levelSpacing: 200
},
force: {
strength: -1000,
distance: 150
},
circular: {
radius: 300,
startAngle: 0
}
};
Manual Arrangement
- Snap to grid: Optional grid snapping (toggle with G key)
- Alignment tools: Align selected nodes (top/bottom/left/right/center)
- Distribution: Distribute nodes evenly (horizontal/vertical)
6. Template Operations
Apply Template
const templates = {
"document-rag": {
description: "Document processing with RAG",
processors: [
{ type: "pdf-decoder", id: "{id}" },
{ type: "chunker", id: "{id}" },
{ type: "embeddings", id: "{class}" },
{ type: "de-write", id: "{id}" }
],
connections: [
{ from: "pdf-decoder.output", to: "chunker.input" },
{ from: "chunker.output", to: "embeddings.input" },
{ from: "embeddings.output", to: "de-write.input" }
]
}
};
Create Template
- Select nodes/edges → Right-click → "Save as template"
- Provide template name and description
- Template saved to library for reuse
7. Validation Operations
Real-time Validation
interface ValidationRule {
id: string;
severity: "error" | "warning" | "info";
check: (flowClass: FlowClass) => ValidationResult;
}
const validationRules = [
{
id: "no-orphans",
severity: "warning",
check: (flow) => findOrphanedNodes(flow)
},
{
id: "queue-consistency",
severity: "error",
check: (flow) => validateQueuePatterns(flow)
},
{
id: "template-consistency",
severity: "error",
check: (flow) => validateTemplateVariables(flow)
}
];
Fix Suggestions
- Auto-fix: One-click fixes for common issues
- Quick actions: Context-aware suggestions
- Validation overlay: Visual indicators on invalid elements
8. History Management
Undo/Redo Stack
interface HistoryAction {
type: "add" | "delete" | "update" | "connect" | "disconnect";
before: FlowState;
after: FlowState;
timestamp: number;
}
const historyStack: HistoryAction[] = [];
const redoStack: HistoryAction[] = [];
// Track all operations
const executeOperation = (operation: Operation) => {
const before = getCurrentState();
performOperation(operation);
const after = getCurrentState();
historyStack.push({
type: operation.type,
before,
after,
timestamp: Date.now()
});
redoStack.length = 0; // Clear redo on new operation
};
9. Import/Export Operations
Import Flow Class
- From JSON file: Upload or paste JSON
- From Config API: Select from existing flow classes
- Validation: Verify structure before import
- Merge options: Replace or merge with existing
Export Flow Class
- To JSON: Download as .json file
- To Config API: Save directly to backend
- To clipboard: Copy JSON for sharing
- Format options: Minified or pretty-printed
10. Metadata Operations
Edit Flow Properties
interface FlowMetadata {
id: string; // Kebab-case identifier
name: string; // Human-readable name
description: string; // Detailed description
tags: string[]; // Categorization tags
version: string; // Semantic version
author: string; // Creator identity
created: Date; // Creation timestamp
modified: Date; // Last modification
}
Tag Management
- Add tags: Type or select from existing
- Remove tags: Click X on tag chips
- Tag suggestions: Based on processors used
- Tag categories: System tags vs user tags
Core Features
1. Auto-Layout
const handleAutoLayout = () => {
const layoutedElements = getLayoutedElements(nodes, edges, {
direction: 'LR', // Left to right
nodeSpacing: 150,
levelSpacing: 200,
animate: true
});
setNodes(layoutedElements.nodes);
setEdges(layoutedElements.edges);
};
2. Import/Export
// Export to flow class JSON
const exportFlowClass = () => {
const flowClass = {
class: extractClassProcessors(nodes),
flow: extractFlowProcessors(nodes),
interfaces: extractInterfaces(nodes),
description: metadata.description,
tags: metadata.tags
};
return JSON.stringify(flowClass, null, 2);
};
// Import from JSON
const importFlowClass = (json: string) => {
const flowClass = JSON.parse(json);
const { nodes, edges } = convertToReactFlow(flowClass);
setNodes(nodes);
setEdges(edges);
};
3. Connection Validation
const isValidConnection = (connection: Connection) => {
const sourceNode = getNode(connection.source);
const targetNode = getNode(connection.target);
// Validate queue compatibility
if (!areQueuesCompatible(sourceNode, targetNode)) {
return false;
}
// Prevent circular dependencies
if (createsCircularDependency(connection)) {
return false;
}
return true;
};
4. Smart Templates
Pre-built flow patterns users can instantiate:
- Document RAG Pipeline: PDF → Chunker → Embeddings → Storage
- Graph RAG Pipeline: Knowledge extraction → Graph embeddings → Query
- Simple Q&A: Prompt → LLM → Response
- Custom Template: User-defined reusable patterns
State Management
interface FlowEditorState {
// React Flow state
nodes: Node[];
edges: Edge[];
// Editor state
selectedElement: Node | Edge | null;
validationErrors: ValidationError[];
isDirty: boolean;
// Metadata
flowClassName: string;
description: string;
tags: string[];
// Actions
addNode: (type: NodeType, position: XYPosition) => void;
updateNode: (nodeId: string, data: NodeData) => void;
deleteNode: (nodeId: string) => void;
addEdge: (edge: Edge) => void;
deleteEdge: (edgeId: string) => void;
validateFlow: () => ValidationResult;
exportFlow: () => FlowClassDefinition;
importFlow: (definition: FlowClassDefinition) => void;
}
Visual Indicators
Node States
- Normal: Default appearance
- Selected: Blue glow/border
- Invalid: Red border with error icon
- Connecting: Pulse animation on handles
- Hover: Slight scale increase
Edge States
- Normal: Default appearance
- Selected: Highlighted with thicker stroke
- Invalid: Red dashed line
- Animated: Flow animation for active connections
Queue Handle Types
- Input: Left side of node, inward arrow
- Output: Right side of node, outward arrow
- Bidirectional: Both sides, for request/response
Keyboard Shortcuts
Delete- Delete selected elementsCmd+Z- UndoCmd+Shift+Z- RedoCmd+S- Save flow classCmd+O- Open flow classCmd+E- Export to JSONSpace- Pan modeCmd+A- Select all nodesCmd+D- Duplicate selected nodes
Integration Points
Config API Integration
The flow class editor uses the existing Config API for all flow class operations:
State Management Hook
// src/state/flow-classes.ts
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useSocket } from "./socket";
export const useFlowClasses = () => {
const socket = useSocket();
return useQuery({
queryKey: ["flow-classes"],
queryFn: async () => {
const response = await socket.request({
operation: "get-config",
path: "flow-classes"
});
return response.configuration as FlowClassDefinition[];
}
});
};
export const useFlowClass = (flowClassId: string) => {
const socket = useSocket();
return useQuery({
queryKey: ["flow-class", flowClassId],
queryFn: async () => {
const response = await socket.request({
operation: "get-config",
path: `flow-classes/${flowClassId}`
});
return response.configuration as FlowClassDefinition;
}
});
};
export const useUpdateFlowClass = () => {
const socket = useSocket();
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id, flowClass }: {
id: string;
flowClass: FlowClassDefinition
}) => {
return await socket.request({
operation: "set-config",
path: `flow-classes/${id}`,
configuration: flowClass
});
},
onSuccess: (_, variables) => {
queryClient.invalidateQueries(["flow-class", variables.id]);
queryClient.invalidateQueries(["flow-classes"]);
}
});
};
export const useDeleteFlowClass = () => {
const socket = useSocket();
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (id: string) => {
return await socket.request({
operation: "delete-config",
path: `flow-classes/${id}`
});
},
onSuccess: () => {
queryClient.invalidateQueries(["flow-classes"]);
}
});
};
Editor Component Integration
// src/components/flow-editor/FlowClassEditor.tsx
import { useFlowClass, useUpdateFlowClass } from "../../state/flow-classes";
import { useActivity } from "../../state/activity";
import { useNotification } from "../../state/notify";
export const FlowClassEditor = ({ flowClassId }: { flowClassId?: string }) => {
const notify = useNotification();
// Load existing flow class if ID provided
const { data: flowClass, isLoading } = useFlowClass(flowClassId);
const updateMutation = useUpdateFlowClass();
// Track loading state
useActivity(isLoading, "Loading flow class");
useActivity(updateMutation.isPending, "Saving flow class");
// Initialize React Flow with loaded data
useEffect(() => {
if (flowClass) {
const { nodes, edges } = convertFlowClassToReactFlow(flowClass);
setNodes(nodes);
setEdges(edges);
setMetadata({
description: flowClass.description,
tags: flowClass.tags
});
}
}, [flowClass]);
// Save handler
const handleSave = async () => {
const flowClassData = exportFlowClass();
try {
await updateMutation.mutateAsync({
id: flowClassId || generateFlowClassId(),
flowClass: flowClassData
});
notify.success("Flow class saved successfully");
} catch (error) {
notify.error("Failed to save flow class");
}
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
// ... rest of React Flow config
/>
);
};
Flow Class List Integration
// src/components/flow-editor/FlowClassList.tsx
import { useFlowClasses, useDeleteFlowClass } from "../../state/flow-classes";
export const FlowClassList = () => {
const { data: flowClasses, isLoading } = useFlowClasses();
const deleteMutation = useDeleteFlowClass();
useActivity(isLoading, "Loading flow classes");
useActivity(deleteMutation.isPending, "Deleting flow class");
return (
<VStack>
{flowClasses?.map(flowClass => (
<Card key={flowClass.id}>
<HStack>
<Text>{flowClass.description}</Text>
<Button onClick={() => openEditor(flowClass.id)}>
Edit
</Button>
<Button onClick={() => deleteMutation.mutate(flowClass.id)}>
Delete
</Button>
</HStack>
</Card>
))}
</VStack>
);
};
Config API Request/Response Format
// Request to get all flow classes
{
operation: "get-config",
path: "flow-classes"
}
// Response
{
configuration: [
{
id: "document-rag-flow",
class: { /* class processors */ },
flow: { /* flow processors */ },
interfaces: { /* interfaces */ },
description: "Document RAG pipeline",
tags: ["rag", "documents"]
}
]
}
// Request to update flow class
{
operation: "set-config",
path: "flow-classes/document-rag-flow",
configuration: {
class: { /* updated class processors */ },
flow: { /* updated flow processors */ },
interfaces: { /* updated interfaces */ },
description: "Updated description",
tags: ["rag", "documents", "v2"]
}
}
Real-time Updates via WebSocket
The editor subscribes to configuration changes to handle external updates:
useEffect(() => {
const subscription = socket.subscribe(
`config/flow-classes/${flowClassId}`,
(update) => {
// Handle external updates to the flow class
if (update.source !== currentSessionId) {
notify.warning("Flow class updated externally. Refreshing...");
queryClient.invalidateQueries(["flow-class", flowClassId]);
}
}
);
return () => subscription.unsubscribe();
}, [flowClassId]);
With Existing UI
Page Integration
The Flow Class Editor is a separate page in the workbench, controlled by a feature toggle:
// src/components/settings/FeatureSwitchesSection.tsx
// Add to existing feature switches:
<HStack justify="space-between" align="center">
<VStack gap={1} align="start">
<Text fontWeight="medium">Flow Class Editor</Text>
<HStack gap={2} align="center">
<Text fontSize="sm" color="fg.muted">
Enable the visual flow class editor for creating and modifying dataflow patterns
</Text>
<Tag.Root colorPalette="accent" size="sm">
<Tag.Label>experimental</Tag.Label>
</Tag.Root>
</HStack>
</VStack>
<Switch.Root
checked={flowClassEditor}
onCheckedChange={(details) =>
onFlowClassEditorChange(details.checked)
}
>
<Switch.HiddenInput />
<Switch.Control>
<Switch.Thumb />
</Switch.Control>
</Switch.Root>
</HStack>
Sidebar Navigation
// src/components/Sidebar.tsx
// Add conditional menu item based on feature switch:
{settings.featureSwitches.flowClassEditor && (
<SidebarItem
icon={<GitBranch />}
label="Flow Class Editor"
path="/flow-class-editor"
isActive={location.pathname === "/flow-class-editor"}
/>
)}
Route Configuration
// src/App.tsx
// Add route for the editor page:
{settings.featureSwitches.flowClassEditor && (
<Route path="/flow-class-editor" element={<FlowClassEditorPage />} />
)}
Page Component
// src/pages/FlowClassEditorPage.tsx
import React from "react";
import PageHeader from "../components/common/PageHeader";
import FlowClassEditor from "../components/flow-editor/FlowClassEditor";
import { GitBranch } from "lucide-react";
const FlowClassEditorPage: React.FC = () => {
return (
<>
<PageHeader
icon={<GitBranch />}
title="Flow Class Editor"
description="Visual editor for creating and modifying TrustGraph dataflow patterns"
/>
<FlowClassEditor />
</>
);
};
export default FlowClassEditorPage;
Settings State Update
// src/state/settings.ts
interface FeatureSwitches {
ontologyEditor: boolean;
submissions: boolean;
agentTools: boolean;
mcpTools: boolean;
schemas: boolean;
tokenCost: boolean;
flowClasses: boolean; // Existing flow classes management
flowClassEditor: boolean; // New visual editor
structuredQuery: boolean;
}
Integration Features
- Uses consistent Chakra UI theming
- Integrates with notification system via
useNotification - Progress indicators via
useActivity - Follows existing Config API patterns
- Respects user's feature toggle preferences
Responsive Design
Desktop (Primary)
- Full editor with all panels visible
- Optimal canvas size for complex flows
- Properties panel as sidebar
Tablet
- Collapsible panels to maximize canvas
- Touch-friendly node manipulation
- Simplified toolbar
Mobile (View-only)
- Read-only flow visualization
- Pan and zoom only
- Export functionality retained
Performance Considerations
Optimizations
- Virtualization for large flows (100+ nodes)
- Debounced validation during editing
- Memoized node/edge components
- Lazy loading of processor templates
- Web Workers for layout calculations
Limits
- Max 500 nodes per flow class
- Max 1000 edges per flow class
- Auto-layout for flows under 100 nodes
- Real-time validation for flows under 50 nodes
Error Handling
Validation Errors
- Inline error indicators on invalid nodes/edges
- Validation panel with detailed error list
- Prevent export/save when errors exist
Runtime Errors
- Connection rejection with toast notification
- Import failure with detailed parsing errors
- Auto-save recovery for browser crashes
Future Enhancements
Phase 2
- Processor library management - Add custom processors
- Collaborative editing - Real-time multi-user support
- Version control - Flow class versioning and diff view
- Simulation mode - Visualize data flow through the graph
Phase 3
- AI assistance - Suggest connections and optimizations
- Performance profiling - Visualize bottlenecks
- Template marketplace - Share flow patterns
- Code generation - Generate processor stubs from flow
Testing Strategy
Unit Tests
- Node/edge component rendering
- Validation logic
- Import/export transformations
- State management actions
Integration Tests
- Full editor workflow
- Save/load operations
- Template instantiation
- Keyboard shortcuts
E2E Tests
- Create flow from scratch
- Import and modify existing flow
- Export and validate JSON
- Deploy flow instance