flock-core 0.5.0b50__py3-none-any.whl → 0.5.0b52__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/dashboard/launcher.py +1 -1
- flock/frontend/README.md +678 -0
- flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
- flock/frontend/index.html +12 -0
- flock/frontend/package-lock.json +4347 -0
- flock/frontend/package.json +48 -0
- flock/frontend/src/App.tsx +79 -0
- flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +587 -0
- flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +387 -0
- flock/frontend/src/__tests__/integration/graph-rendering.test.tsx +640 -0
- flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
- flock/frontend/src/components/common/BuildInfo.tsx +39 -0
- flock/frontend/src/components/common/EmptyState.module.css +115 -0
- flock/frontend/src/components/common/EmptyState.tsx +128 -0
- flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
- flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
- flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
- flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
- flock/frontend/src/components/controls/PublishControl.css +547 -0
- flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
- flock/frontend/src/components/controls/PublishControl.tsx +432 -0
- flock/frontend/src/components/details/DetailWindowContainer.tsx +62 -0
- flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
- flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
- flock/frontend/src/components/details/MessageHistoryTab.tsx +299 -0
- flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
- flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
- flock/frontend/src/components/details/RunStatusTab.tsx +307 -0
- flock/frontend/src/components/details/tabs.test.tsx +1015 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
- flock/frontend/src/components/filters/FilterBar.module.css +29 -0
- flock/frontend/src/components/filters/FilterBar.test.tsx +133 -0
- flock/frontend/src/components/filters/FilterBar.tsx +33 -0
- flock/frontend/src/components/filters/FilterPills.module.css +79 -0
- flock/frontend/src/components/filters/FilterPills.test.tsx +173 -0
- flock/frontend/src/components/filters/FilterPills.tsx +67 -0
- flock/frontend/src/components/filters/TimeRangeFilter.module.css +91 -0
- flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
- flock/frontend/src/components/filters/TimeRangeFilter.tsx +105 -0
- flock/frontend/src/components/graph/AgentNode.test.tsx +75 -0
- flock/frontend/src/components/graph/AgentNode.tsx +322 -0
- flock/frontend/src/components/graph/GraphCanvas.tsx +406 -0
- flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
- flock/frontend/src/components/graph/MessageNode.test.tsx +62 -0
- flock/frontend/src/components/graph/MessageNode.tsx +116 -0
- flock/frontend/src/components/graph/MiniMap.tsx +47 -0
- flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
- flock/frontend/src/components/layout/DashboardLayout.css +407 -0
- flock/frontend/src/components/layout/DashboardLayout.tsx +300 -0
- flock/frontend/src/components/layout/Header.module.css +88 -0
- flock/frontend/src/components/layout/Header.tsx +52 -0
- flock/frontend/src/components/modules/EventLogModule.test.tsx +401 -0
- flock/frontend/src/components/modules/EventLogModule.tsx +396 -0
- flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +17 -0
- flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
- flock/frontend/src/components/modules/ModuleRegistry.ts +85 -0
- flock/frontend/src/components/modules/ModuleWindow.tsx +155 -0
- flock/frontend/src/components/modules/registerModules.ts +20 -0
- flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
- flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
- flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
- flock/frontend/src/components/settings/SettingsPanel.css +327 -0
- flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
- flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
- flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
- flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
- flock/frontend/src/hooks/useModulePersistence.ts +154 -0
- flock/frontend/src/hooks/useModules.ts +139 -0
- flock/frontend/src/hooks/usePersistence.ts +139 -0
- flock/frontend/src/main.tsx +13 -0
- flock/frontend/src/services/api.ts +213 -0
- flock/frontend/src/services/indexeddb.test.ts +793 -0
- flock/frontend/src/services/indexeddb.ts +794 -0
- flock/frontend/src/services/layout.test.ts +437 -0
- flock/frontend/src/services/layout.ts +146 -0
- flock/frontend/src/services/themeApplicator.ts +140 -0
- flock/frontend/src/services/themeService.ts +77 -0
- flock/frontend/src/services/websocket.test.ts +595 -0
- flock/frontend/src/services/websocket.ts +685 -0
- flock/frontend/src/store/filterStore.test.ts +242 -0
- flock/frontend/src/store/filterStore.ts +103 -0
- flock/frontend/src/store/graphStore.test.ts +186 -0
- flock/frontend/src/store/graphStore.ts +414 -0
- flock/frontend/src/store/moduleStore.test.ts +253 -0
- flock/frontend/src/store/moduleStore.ts +57 -0
- flock/frontend/src/store/settingsStore.ts +188 -0
- flock/frontend/src/store/streamStore.ts +68 -0
- flock/frontend/src/store/uiStore.test.ts +54 -0
- flock/frontend/src/store/uiStore.ts +110 -0
- flock/frontend/src/store/wsStore.ts +34 -0
- flock/frontend/src/styles/index.css +15 -0
- flock/frontend/src/styles/scrollbar.css +47 -0
- flock/frontend/src/styles/variables.css +488 -0
- flock/frontend/src/test/setup.ts +1 -0
- flock/frontend/src/types/filters.ts +14 -0
- flock/frontend/src/types/graph.ts +55 -0
- flock/frontend/src/types/modules.ts +7 -0
- flock/frontend/src/types/theme.ts +55 -0
- flock/frontend/src/utils/mockData.ts +85 -0
- flock/frontend/src/utils/performance.ts +16 -0
- flock/frontend/src/utils/transforms.test.ts +860 -0
- flock/frontend/src/utils/transforms.ts +323 -0
- flock/frontend/src/vite-env.d.ts +17 -0
- flock/frontend/tsconfig.json +27 -0
- flock/frontend/tsconfig.node.json +11 -0
- flock/frontend/vite.config.ts +25 -0
- flock/frontend/vitest.config.ts +11 -0
- flock/helper/cli_helper.py +1 -1
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/METADATA +1 -1
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/RECORD +117 -7
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/WHEEL +0 -0
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { devtools } from 'zustand/middleware';
|
|
3
|
+
import { Node, Edge } from '@xyflow/react';
|
|
4
|
+
import { Agent, Message, AgentNodeData, MessageNodeData } from '../types/graph';
|
|
5
|
+
import { deriveAgentViewEdges, deriveBlackboardViewEdges, Artifact, Run, DashboardState } from '../utils/transforms';
|
|
6
|
+
import { useFilterStore } from './filterStore';
|
|
7
|
+
|
|
8
|
+
interface GraphState {
|
|
9
|
+
// Core data
|
|
10
|
+
agents: Map<string, Agent>;
|
|
11
|
+
messages: Map<string, Message>;
|
|
12
|
+
events: Message[];
|
|
13
|
+
runs: Map<string, Run>;
|
|
14
|
+
|
|
15
|
+
// Phase 11 Bug Fix: Track actual consumption (artifact_id -> consumer_ids[])
|
|
16
|
+
// Updated by agent_activated events to reflect filtering and actual consumption
|
|
17
|
+
consumptions: Map<string, string[]>;
|
|
18
|
+
|
|
19
|
+
// Message node positions (message_id -> {x, y})
|
|
20
|
+
// Messages don't have position in their data model, so we track it separately
|
|
21
|
+
messagePositions: Map<string, { x: number; y: number }>;
|
|
22
|
+
|
|
23
|
+
// Graph representation
|
|
24
|
+
nodes: Node[];
|
|
25
|
+
edges: Edge[];
|
|
26
|
+
|
|
27
|
+
// Actions
|
|
28
|
+
addAgent: (agent: Agent) => void;
|
|
29
|
+
updateAgent: (id: string, updates: Partial<Agent>) => void;
|
|
30
|
+
removeAgent: (id: string) => void;
|
|
31
|
+
|
|
32
|
+
addMessage: (message: Message) => void;
|
|
33
|
+
updateMessage: (id: string, updates: Partial<Message>) => void;
|
|
34
|
+
addRun: (run: Run) => void;
|
|
35
|
+
|
|
36
|
+
// Phase 11 Bug Fix: Track actual consumption from agent_activated events
|
|
37
|
+
recordConsumption: (artifactIds: string[], consumerId: string) => void;
|
|
38
|
+
|
|
39
|
+
// Transform streaming message to final message (changes ID)
|
|
40
|
+
finalizeStreamingMessage: (oldId: string, newMessage: Message) => void;
|
|
41
|
+
|
|
42
|
+
updateNodePosition: (nodeId: string, position: { x: number; y: number }) => void;
|
|
43
|
+
|
|
44
|
+
// Mode-specific graph generation
|
|
45
|
+
generateAgentViewGraph: () => void;
|
|
46
|
+
generateBlackboardViewGraph: () => void;
|
|
47
|
+
|
|
48
|
+
// Filter application
|
|
49
|
+
applyFilters: () => void;
|
|
50
|
+
|
|
51
|
+
// Bulk updates
|
|
52
|
+
batchUpdate: (update: { agents?: Agent[]; messages?: Message[]; runs?: Run[] }) => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Helper function to convert Message to Artifact
|
|
56
|
+
function messageToArtifact(message: Message, consumptions: Map<string, string[]>): Artifact {
|
|
57
|
+
// BUG FIX: Use ACTUAL consumption data from consumptions Map, not inferred from subscriptions!
|
|
58
|
+
// This ensures edges reflect what actually happened, not what "should" happen based on current subscriptions.
|
|
59
|
+
const actualConsumers = consumptions.get(message.id) || [];
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
artifact_id: message.id,
|
|
63
|
+
artifact_type: message.type,
|
|
64
|
+
produced_by: message.producedBy,
|
|
65
|
+
consumed_by: actualConsumers, // Use actual consumption data
|
|
66
|
+
published_at: new Date(message.timestamp).toISOString(),
|
|
67
|
+
payload: message.payload,
|
|
68
|
+
correlation_id: message.correlationId,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Helper function to convert store state to DashboardState
|
|
73
|
+
function toDashboardState(
|
|
74
|
+
messages: Map<string, Message>,
|
|
75
|
+
runs: Map<string, Run>,
|
|
76
|
+
consumptions: Map<string, string[]>
|
|
77
|
+
): DashboardState {
|
|
78
|
+
const artifacts = new Map<string, Artifact>();
|
|
79
|
+
|
|
80
|
+
messages.forEach((message) => {
|
|
81
|
+
artifacts.set(message.id, messageToArtifact(message, consumptions));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
artifacts,
|
|
86
|
+
runs,
|
|
87
|
+
consumptions, // Phase 11: Pass actual consumption data for filtered count calculation
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const useGraphStore = create<GraphState>()(
|
|
92
|
+
devtools(
|
|
93
|
+
(set, get) => ({
|
|
94
|
+
agents: new Map(),
|
|
95
|
+
messages: new Map(),
|
|
96
|
+
events: [],
|
|
97
|
+
runs: new Map(),
|
|
98
|
+
consumptions: new Map(), // Phase 11: Track actual artifact consumption
|
|
99
|
+
messagePositions: new Map(), // Track message node positions
|
|
100
|
+
nodes: [],
|
|
101
|
+
edges: [],
|
|
102
|
+
|
|
103
|
+
addAgent: (agent) =>
|
|
104
|
+
set((state) => {
|
|
105
|
+
const agents = new Map(state.agents);
|
|
106
|
+
agents.set(agent.id, agent);
|
|
107
|
+
return { agents };
|
|
108
|
+
}),
|
|
109
|
+
|
|
110
|
+
updateAgent: (id, updates) =>
|
|
111
|
+
set((state) => {
|
|
112
|
+
const agents = new Map(state.agents);
|
|
113
|
+
const agent = agents.get(id);
|
|
114
|
+
if (agent) {
|
|
115
|
+
agents.set(id, { ...agent, ...updates });
|
|
116
|
+
}
|
|
117
|
+
return { agents };
|
|
118
|
+
}),
|
|
119
|
+
|
|
120
|
+
removeAgent: (id) =>
|
|
121
|
+
set((state) => {
|
|
122
|
+
const agents = new Map(state.agents);
|
|
123
|
+
agents.delete(id);
|
|
124
|
+
return { agents };
|
|
125
|
+
}),
|
|
126
|
+
|
|
127
|
+
addMessage: (message) =>
|
|
128
|
+
set((state) => {
|
|
129
|
+
const messages = new Map(state.messages);
|
|
130
|
+
messages.set(message.id, message);
|
|
131
|
+
|
|
132
|
+
// Only add to events if this is a NEW message (not already in the array)
|
|
133
|
+
// This prevents streaming token updates from flooding the Event Log
|
|
134
|
+
const isDuplicate = state.events.some(e => e.id === message.id);
|
|
135
|
+
const events = isDuplicate
|
|
136
|
+
? state.events // Skip if already in events array
|
|
137
|
+
: [message, ...state.events].slice(0, 100); // Add new message
|
|
138
|
+
|
|
139
|
+
return { messages, events };
|
|
140
|
+
}),
|
|
141
|
+
|
|
142
|
+
updateMessage: (id, updates) =>
|
|
143
|
+
set((state) => {
|
|
144
|
+
const messages = new Map(state.messages);
|
|
145
|
+
const message = messages.get(id);
|
|
146
|
+
if (message) {
|
|
147
|
+
messages.set(id, { ...message, ...updates });
|
|
148
|
+
}
|
|
149
|
+
// Note: updateMessage does NOT touch the events array
|
|
150
|
+
// This allows streaming updates without flooding the Event Log
|
|
151
|
+
return { messages };
|
|
152
|
+
}),
|
|
153
|
+
|
|
154
|
+
addRun: (run) =>
|
|
155
|
+
set((state) => {
|
|
156
|
+
const runs = new Map(state.runs);
|
|
157
|
+
runs.set(run.run_id, run);
|
|
158
|
+
return { runs };
|
|
159
|
+
}),
|
|
160
|
+
|
|
161
|
+
// Phase 11 Bug Fix: Record actual consumption from agent_activated events
|
|
162
|
+
recordConsumption: (artifactIds, consumerId) =>
|
|
163
|
+
set((state) => {
|
|
164
|
+
const consumptions = new Map(state.consumptions);
|
|
165
|
+
artifactIds.forEach((artifactId) => {
|
|
166
|
+
const existing = consumptions.get(artifactId) || [];
|
|
167
|
+
if (!existing.includes(consumerId)) {
|
|
168
|
+
consumptions.set(artifactId, [...existing, consumerId]);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
return { consumptions };
|
|
172
|
+
}),
|
|
173
|
+
|
|
174
|
+
finalizeStreamingMessage: (oldId, newMessage) =>
|
|
175
|
+
set((state) => {
|
|
176
|
+
// Remove old streaming message, add final message with new ID
|
|
177
|
+
const messages = new Map(state.messages);
|
|
178
|
+
messages.delete(oldId);
|
|
179
|
+
messages.set(newMessage.id, newMessage);
|
|
180
|
+
|
|
181
|
+
// Transfer position from old ID to new ID
|
|
182
|
+
const messagePositions = new Map(state.messagePositions);
|
|
183
|
+
const oldPos = messagePositions.get(oldId);
|
|
184
|
+
if (oldPos) {
|
|
185
|
+
messagePositions.delete(oldId);
|
|
186
|
+
messagePositions.set(newMessage.id, oldPos);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Update events array: replace streaming ID with final message ID
|
|
190
|
+
const events = state.events.map(e =>
|
|
191
|
+
e.id === oldId ? newMessage : e
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
return { messages, messagePositions, events };
|
|
195
|
+
}),
|
|
196
|
+
|
|
197
|
+
updateNodePosition: (nodeId, position) =>
|
|
198
|
+
set((state) => {
|
|
199
|
+
const agents = new Map(state.agents);
|
|
200
|
+
const agent = agents.get(nodeId);
|
|
201
|
+
if (agent) {
|
|
202
|
+
// Update agent position
|
|
203
|
+
agents.set(nodeId, { ...agent, position });
|
|
204
|
+
return { agents };
|
|
205
|
+
} else {
|
|
206
|
+
// Must be a message node - update message position
|
|
207
|
+
const messagePositions = new Map(state.messagePositions);
|
|
208
|
+
messagePositions.set(nodeId, position);
|
|
209
|
+
return { messagePositions };
|
|
210
|
+
}
|
|
211
|
+
}),
|
|
212
|
+
|
|
213
|
+
generateAgentViewGraph: () => {
|
|
214
|
+
const { agents, messages, runs, consumptions, nodes: currentNodes } = get();
|
|
215
|
+
|
|
216
|
+
// Create a map of current node positions to preserve them during regeneration
|
|
217
|
+
const currentPositions = new Map<string, { x: number; y: number }>();
|
|
218
|
+
currentNodes.forEach(node => {
|
|
219
|
+
currentPositions.set(node.id, node.position);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const nodes: Node<AgentNodeData>[] = [];
|
|
223
|
+
|
|
224
|
+
// Create nodes from agents
|
|
225
|
+
agents.forEach((agent) => {
|
|
226
|
+
// Preserve position priority: saved position > current React Flow position > default
|
|
227
|
+
const position = agent.position
|
|
228
|
+
|| currentPositions.get(agent.id)
|
|
229
|
+
|| { x: 400 + Math.random() * 200, y: 300 + Math.random() * 200 };
|
|
230
|
+
|
|
231
|
+
nodes.push({
|
|
232
|
+
id: agent.id,
|
|
233
|
+
type: 'agent',
|
|
234
|
+
position,
|
|
235
|
+
data: {
|
|
236
|
+
name: agent.name,
|
|
237
|
+
status: agent.status,
|
|
238
|
+
subscriptions: agent.subscriptions,
|
|
239
|
+
outputTypes: agent.outputTypes,
|
|
240
|
+
sentCount: agent.sentCount,
|
|
241
|
+
recvCount: agent.recvCount,
|
|
242
|
+
receivedByType: agent.receivedByType,
|
|
243
|
+
sentByType: agent.sentByType,
|
|
244
|
+
streamingTokens: agent.streamingTokens,
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Derive edges using transform algorithm
|
|
250
|
+
const dashboardState = toDashboardState(messages, runs, consumptions);
|
|
251
|
+
const edges = deriveAgentViewEdges(dashboardState);
|
|
252
|
+
|
|
253
|
+
set({ nodes, edges });
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
generateBlackboardViewGraph: () => {
|
|
257
|
+
const { messages, runs, consumptions, messagePositions, nodes: currentNodes } = get();
|
|
258
|
+
|
|
259
|
+
// Create a map of current node positions to preserve them during regeneration
|
|
260
|
+
const currentPositions = new Map<string, { x: number; y: number }>();
|
|
261
|
+
currentNodes.forEach(node => {
|
|
262
|
+
currentPositions.set(node.id, node.position);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const nodes: Node<MessageNodeData>[] = [];
|
|
266
|
+
|
|
267
|
+
// Create nodes from messages
|
|
268
|
+
messages.forEach((message) => {
|
|
269
|
+
const payloadStr = JSON.stringify(message.payload);
|
|
270
|
+
|
|
271
|
+
// BUG FIX: Use ACTUAL consumption data from consumptions Map, not inferred from subscriptions!
|
|
272
|
+
const consumedBy = consumptions.get(message.id) || [];
|
|
273
|
+
|
|
274
|
+
// Preserve position priority: saved position > current React Flow position > default
|
|
275
|
+
const position = messagePositions.get(message.id)
|
|
276
|
+
|| currentPositions.get(message.id)
|
|
277
|
+
|| { x: 400 + Math.random() * 200, y: 300 + Math.random() * 200 };
|
|
278
|
+
|
|
279
|
+
nodes.push({
|
|
280
|
+
id: message.id,
|
|
281
|
+
type: 'message',
|
|
282
|
+
position,
|
|
283
|
+
data: {
|
|
284
|
+
artifactType: message.type,
|
|
285
|
+
payloadPreview: payloadStr.slice(0, 100),
|
|
286
|
+
payload: message.payload, // Full payload for display
|
|
287
|
+
producedBy: message.producedBy,
|
|
288
|
+
consumedBy, // Use actual consumption data
|
|
289
|
+
timestamp: message.timestamp,
|
|
290
|
+
isStreaming: message.isStreaming || false,
|
|
291
|
+
streamingText: message.streamingText || '',
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// Derive edges using transform algorithm
|
|
297
|
+
const dashboardState = toDashboardState(messages, runs, consumptions);
|
|
298
|
+
const edges = deriveBlackboardViewEdges(dashboardState);
|
|
299
|
+
|
|
300
|
+
set({ nodes, edges });
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
batchUpdate: (update) =>
|
|
304
|
+
set((state) => {
|
|
305
|
+
const newState: Partial<GraphState> = {};
|
|
306
|
+
|
|
307
|
+
if (update.agents) {
|
|
308
|
+
const agents = new Map(state.agents);
|
|
309
|
+
update.agents.forEach((a) => agents.set(a.id, a));
|
|
310
|
+
newState.agents = agents;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (update.messages) {
|
|
314
|
+
const messages = new Map(state.messages);
|
|
315
|
+
update.messages.forEach((m) => messages.set(m.id, m));
|
|
316
|
+
newState.messages = messages;
|
|
317
|
+
newState.events = [...update.messages, ...state.events].slice(0, 100);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (update.runs) {
|
|
321
|
+
const runs = new Map(state.runs);
|
|
322
|
+
update.runs.forEach((r) => runs.set(r.run_id, r));
|
|
323
|
+
newState.runs = runs;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return newState;
|
|
327
|
+
}),
|
|
328
|
+
|
|
329
|
+
applyFilters: () => {
|
|
330
|
+
const { nodes, edges, messages } = get();
|
|
331
|
+
const { correlationId, timeRange } = useFilterStore.getState();
|
|
332
|
+
|
|
333
|
+
// Helper to calculate time range boundaries
|
|
334
|
+
const getTimeRangeBoundaries = (): { start: number; end: number } => {
|
|
335
|
+
const now = Date.now();
|
|
336
|
+
if (timeRange.preset === 'last5min') {
|
|
337
|
+
return { start: now - 5 * 60 * 1000, end: now };
|
|
338
|
+
} else if (timeRange.preset === 'last10min') {
|
|
339
|
+
return { start: now - 10 * 60 * 1000, end: now };
|
|
340
|
+
} else if (timeRange.preset === 'last1hour') {
|
|
341
|
+
return { start: now - 60 * 60 * 1000, end: now };
|
|
342
|
+
} else if (timeRange.preset === 'custom' && timeRange.start && timeRange.end) {
|
|
343
|
+
return { start: timeRange.start, end: timeRange.end };
|
|
344
|
+
}
|
|
345
|
+
return { start: now - 10 * 60 * 1000, end: now };
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const { start: timeStart, end: timeEnd } = getTimeRangeBoundaries();
|
|
349
|
+
|
|
350
|
+
// Filter messages based on correlation ID and time range
|
|
351
|
+
const visibleMessageIds = new Set<string>();
|
|
352
|
+
messages.forEach((message) => {
|
|
353
|
+
let visible = true;
|
|
354
|
+
|
|
355
|
+
// Apply correlation ID filter (selective)
|
|
356
|
+
if (correlationId && message.correlationId !== correlationId) {
|
|
357
|
+
visible = false;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Apply time range filter (in-memory)
|
|
361
|
+
if (visible && (message.timestamp < timeStart || message.timestamp > timeEnd)) {
|
|
362
|
+
visible = false;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (visible) {
|
|
366
|
+
visibleMessageIds.add(message.id);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// Update nodes visibility
|
|
371
|
+
const updatedNodes = nodes.map((node) => {
|
|
372
|
+
if (node.type === 'message') {
|
|
373
|
+
// For message nodes, check if message is visible
|
|
374
|
+
return {
|
|
375
|
+
...node,
|
|
376
|
+
hidden: !visibleMessageIds.has(node.id),
|
|
377
|
+
};
|
|
378
|
+
} else if (node.type === 'agent') {
|
|
379
|
+
// For agent nodes, show if any visible messages involve this agent
|
|
380
|
+
let hasVisibleMessages = false;
|
|
381
|
+
messages.forEach((message) => {
|
|
382
|
+
if (visibleMessageIds.has(message.id)) {
|
|
383
|
+
if (message.producedBy === node.id) {
|
|
384
|
+
hasVisibleMessages = true;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
return {
|
|
389
|
+
...node,
|
|
390
|
+
hidden: !hasVisibleMessages,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
return node;
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Update edges visibility
|
|
397
|
+
const updatedEdges = edges.map((edge) => {
|
|
398
|
+
// Hide edge if either source or target node is hidden
|
|
399
|
+
const sourceNode = updatedNodes.find((n) => n.id === edge.source);
|
|
400
|
+
const targetNode = updatedNodes.find((n) => n.id === edge.target);
|
|
401
|
+
const hidden = sourceNode?.hidden || targetNode?.hidden || false;
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
...edge,
|
|
405
|
+
hidden,
|
|
406
|
+
};
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
set({ nodes: updatedNodes, edges: updatedEdges });
|
|
410
|
+
},
|
|
411
|
+
}),
|
|
412
|
+
{ name: 'graphStore' }
|
|
413
|
+
)
|
|
414
|
+
);
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { useModuleStore } from './moduleStore';
|
|
3
|
+
import type { ModuleInstance } from '../types/modules';
|
|
4
|
+
|
|
5
|
+
describe('moduleStore', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
// Reset store before each test
|
|
8
|
+
useModuleStore.setState({
|
|
9
|
+
instances: new Map(),
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should have an empty Map as initial state', () => {
|
|
14
|
+
const instances = useModuleStore.getState().instances;
|
|
15
|
+
expect(instances.size).toBe(0);
|
|
16
|
+
expect(instances instanceof Map).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should add a new module instance', () => {
|
|
20
|
+
const module: ModuleInstance = {
|
|
21
|
+
id: 'module-1',
|
|
22
|
+
type: 'eventlog',
|
|
23
|
+
position: { x: 100, y: 100 },
|
|
24
|
+
size: { width: 600, height: 400 },
|
|
25
|
+
visible: true,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
useModuleStore.getState().addModule(module);
|
|
29
|
+
|
|
30
|
+
const instances = useModuleStore.getState().instances;
|
|
31
|
+
expect(instances.size).toBe(1);
|
|
32
|
+
expect(instances.get('module-1')).toEqual(module);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should add multiple module instances', () => {
|
|
36
|
+
const module1: ModuleInstance = {
|
|
37
|
+
id: 'module-1',
|
|
38
|
+
type: 'eventlog',
|
|
39
|
+
position: { x: 100, y: 100 },
|
|
40
|
+
size: { width: 600, height: 400 },
|
|
41
|
+
visible: true,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const module2: ModuleInstance = {
|
|
45
|
+
id: 'module-2',
|
|
46
|
+
type: 'eventlog',
|
|
47
|
+
position: { x: 200, y: 200 },
|
|
48
|
+
size: { width: 600, height: 400 },
|
|
49
|
+
visible: true,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
useModuleStore.getState().addModule(module1);
|
|
53
|
+
useModuleStore.getState().addModule(module2);
|
|
54
|
+
|
|
55
|
+
const instances = useModuleStore.getState().instances;
|
|
56
|
+
expect(instances.size).toBe(2);
|
|
57
|
+
expect(instances.get('module-1')).toEqual(module1);
|
|
58
|
+
expect(instances.get('module-2')).toEqual(module2);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should update module position', () => {
|
|
62
|
+
const module: ModuleInstance = {
|
|
63
|
+
id: 'module-1',
|
|
64
|
+
type: 'eventlog',
|
|
65
|
+
position: { x: 100, y: 100 },
|
|
66
|
+
size: { width: 600, height: 400 },
|
|
67
|
+
visible: true,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
useModuleStore.getState().addModule(module);
|
|
71
|
+
useModuleStore.getState().updateModule('module-1', {
|
|
72
|
+
position: { x: 300, y: 300 },
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const updated = useModuleStore.getState().instances.get('module-1');
|
|
76
|
+
expect(updated?.position).toEqual({ x: 300, y: 300 });
|
|
77
|
+
expect(updated?.size).toEqual({ width: 600, height: 400 }); // Other properties unchanged
|
|
78
|
+
expect(updated?.visible).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should update module size', () => {
|
|
82
|
+
const module: ModuleInstance = {
|
|
83
|
+
id: 'module-1',
|
|
84
|
+
type: 'eventlog',
|
|
85
|
+
position: { x: 100, y: 100 },
|
|
86
|
+
size: { width: 600, height: 400 },
|
|
87
|
+
visible: true,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
useModuleStore.getState().addModule(module);
|
|
91
|
+
useModuleStore.getState().updateModule('module-1', {
|
|
92
|
+
size: { width: 800, height: 600 },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const updated = useModuleStore.getState().instances.get('module-1');
|
|
96
|
+
expect(updated?.size).toEqual({ width: 800, height: 600 });
|
|
97
|
+
expect(updated?.position).toEqual({ x: 100, y: 100 }); // Other properties unchanged
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should update multiple module properties at once', () => {
|
|
101
|
+
const module: ModuleInstance = {
|
|
102
|
+
id: 'module-1',
|
|
103
|
+
type: 'eventlog',
|
|
104
|
+
position: { x: 100, y: 100 },
|
|
105
|
+
size: { width: 600, height: 400 },
|
|
106
|
+
visible: true,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
useModuleStore.getState().addModule(module);
|
|
110
|
+
useModuleStore.getState().updateModule('module-1', {
|
|
111
|
+
position: { x: 300, y: 300 },
|
|
112
|
+
size: { width: 800, height: 600 },
|
|
113
|
+
visible: false,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const updated = useModuleStore.getState().instances.get('module-1');
|
|
117
|
+
expect(updated?.position).toEqual({ x: 300, y: 300 });
|
|
118
|
+
expect(updated?.size).toEqual({ width: 800, height: 600 });
|
|
119
|
+
expect(updated?.visible).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should toggle module visibility', () => {
|
|
123
|
+
const module: ModuleInstance = {
|
|
124
|
+
id: 'module-1',
|
|
125
|
+
type: 'eventlog',
|
|
126
|
+
position: { x: 100, y: 100 },
|
|
127
|
+
size: { width: 600, height: 400 },
|
|
128
|
+
visible: true,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
useModuleStore.getState().addModule(module);
|
|
132
|
+
|
|
133
|
+
// Toggle visibility to false
|
|
134
|
+
useModuleStore.getState().toggleVisibility('module-1');
|
|
135
|
+
expect(useModuleStore.getState().instances.get('module-1')?.visible).toBe(false);
|
|
136
|
+
|
|
137
|
+
// Toggle visibility back to true
|
|
138
|
+
useModuleStore.getState().toggleVisibility('module-1');
|
|
139
|
+
expect(useModuleStore.getState().instances.get('module-1')?.visible).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should remove a module instance', () => {
|
|
143
|
+
const module1: ModuleInstance = {
|
|
144
|
+
id: 'module-1',
|
|
145
|
+
type: 'eventlog',
|
|
146
|
+
position: { x: 100, y: 100 },
|
|
147
|
+
size: { width: 600, height: 400 },
|
|
148
|
+
visible: true,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const module2: ModuleInstance = {
|
|
152
|
+
id: 'module-2',
|
|
153
|
+
type: 'eventlog',
|
|
154
|
+
position: { x: 200, y: 200 },
|
|
155
|
+
size: { width: 600, height: 400 },
|
|
156
|
+
visible: true,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
useModuleStore.getState().addModule(module1);
|
|
160
|
+
useModuleStore.getState().addModule(module2);
|
|
161
|
+
|
|
162
|
+
expect(useModuleStore.getState().instances.size).toBe(2);
|
|
163
|
+
|
|
164
|
+
useModuleStore.getState().removeModule('module-1');
|
|
165
|
+
|
|
166
|
+
const instances = useModuleStore.getState().instances;
|
|
167
|
+
expect(instances.size).toBe(1);
|
|
168
|
+
expect(instances.get('module-1')).toBeUndefined();
|
|
169
|
+
expect(instances.get('module-2')).toEqual(module2);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should get all module instances', () => {
|
|
173
|
+
const module1: ModuleInstance = {
|
|
174
|
+
id: 'module-1',
|
|
175
|
+
type: 'eventlog',
|
|
176
|
+
position: { x: 100, y: 100 },
|
|
177
|
+
size: { width: 600, height: 400 },
|
|
178
|
+
visible: true,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const module2: ModuleInstance = {
|
|
182
|
+
id: 'module-2',
|
|
183
|
+
type: 'eventlog',
|
|
184
|
+
position: { x: 200, y: 200 },
|
|
185
|
+
size: { width: 600, height: 400 },
|
|
186
|
+
visible: false,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
useModuleStore.getState().addModule(module1);
|
|
190
|
+
useModuleStore.getState().addModule(module2);
|
|
191
|
+
|
|
192
|
+
const instances = useModuleStore.getState().instances;
|
|
193
|
+
const allInstances = Array.from(instances.values());
|
|
194
|
+
|
|
195
|
+
expect(allInstances.length).toBe(2);
|
|
196
|
+
expect(allInstances).toContainEqual(module1);
|
|
197
|
+
expect(allInstances).toContainEqual(module2);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should handle updating non-existent module gracefully', () => {
|
|
201
|
+
useModuleStore.getState().updateModule('non-existent', {
|
|
202
|
+
position: { x: 100, y: 100 },
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Should not throw error and instances should remain empty
|
|
206
|
+
expect(useModuleStore.getState().instances.size).toBe(0);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should handle removing non-existent module gracefully', () => {
|
|
210
|
+
const module: ModuleInstance = {
|
|
211
|
+
id: 'module-1',
|
|
212
|
+
type: 'eventlog',
|
|
213
|
+
position: { x: 100, y: 100 },
|
|
214
|
+
size: { width: 600, height: 400 },
|
|
215
|
+
visible: true,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
useModuleStore.getState().addModule(module);
|
|
219
|
+
useModuleStore.getState().removeModule('non-existent');
|
|
220
|
+
|
|
221
|
+
// Should not affect existing modules
|
|
222
|
+
expect(useModuleStore.getState().instances.size).toBe(1);
|
|
223
|
+
expect(useModuleStore.getState().instances.get('module-1')).toEqual(module);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should handle toggling visibility of non-existent module gracefully', () => {
|
|
227
|
+
useModuleStore.getState().toggleVisibility('non-existent');
|
|
228
|
+
|
|
229
|
+
// Should not throw error
|
|
230
|
+
expect(useModuleStore.getState().instances.size).toBe(0);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should maintain Map state immutability', () => {
|
|
234
|
+
const module: ModuleInstance = {
|
|
235
|
+
id: 'module-1',
|
|
236
|
+
type: 'eventlog',
|
|
237
|
+
position: { x: 100, y: 100 },
|
|
238
|
+
size: { width: 600, height: 400 },
|
|
239
|
+
visible: true,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
useModuleStore.getState().addModule(module);
|
|
243
|
+
const instancesBefore = useModuleStore.getState().instances;
|
|
244
|
+
|
|
245
|
+
useModuleStore.getState().updateModule('module-1', {
|
|
246
|
+
position: { x: 200, y: 200 },
|
|
247
|
+
});
|
|
248
|
+
const instancesAfter = useModuleStore.getState().instances;
|
|
249
|
+
|
|
250
|
+
// New Map instance should be created (immutability)
|
|
251
|
+
expect(instancesBefore).not.toBe(instancesAfter);
|
|
252
|
+
});
|
|
253
|
+
});
|