@decido/plugin-agent-manager 1.0.0
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.
- package/.turbo/turbo-build.log +9 -0
- package/errors.log +29 -0
- package/package.json +32 -0
- package/src/index.ts +122 -0
- package/src/intents/agentManagerIntents.ts +70 -0
- package/src/store/usePromptHubStore.ts +50 -0
- package/src/widgets/AdvancedCanvasWidget.tsx +198 -0
- package/src/widgets/MindExplorerWidget.tsx +177 -0
- package/src/widgets/MobileLinkWidget.tsx +41 -0
- package/src/widgets/Node3DWidget.tsx +114 -0
- package/src/widgets/PromptHubWidget.tsx +156 -0
- package/src/widgets/SystemSentinelWidget.tsx +55 -0
- package/src/widgets/TelemetryHUDWidget.tsx +100 -0
- package/src/widgets/VaultWidget.tsx +99 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
|
|
2
|
+
Debugger listening on ws://127.0.0.1:53305/d3f610b6-810b-42b6-8437-6de49c8ffc0d
|
|
3
|
+
For help, see: https://nodejs.org/en/docs/inspector
|
|
4
|
+
Debugger attached.
|
|
5
|
+
|
|
6
|
+
> @macia/agent-manager-plugin@1.0.0 build /Users/julioramirez/dev/active/Decido/packages/plugins/agent-manager-plugin
|
|
7
|
+
> tsc
|
|
8
|
+
|
|
9
|
+
Waiting for the debugger to disconnect...
|
package/errors.log
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Debugger listening on ws://127.0.0.1:65474/5960d5f4-2baf-4d97-bf89-c5111ed8ec77
|
|
2
|
+
For help, see: https://nodejs.org/en/docs/inspector
|
|
3
|
+
Debugger attached.
|
|
4
|
+
|
|
5
|
+
> @macia/agent-manager-plugin@1.0.0 build /Users/julioramirez/dev/active/Decido/packages/agent-manager-plugin
|
|
6
|
+
> tsc
|
|
7
|
+
|
|
8
|
+
Debugger listening on ws://127.0.0.1:65476/b733e089-f2f2-4870-9f07-eae60becbf3a
|
|
9
|
+
For help, see: https://nodejs.org/en/docs/inspector
|
|
10
|
+
Debugger attached.
|
|
11
|
+
src/widgets/VaultWidget.tsx(1,8): error TS6133: 'React' is declared but its value is never read.
|
|
12
|
+
src/widgets/VaultWidget.tsx(12,27): error TS2345: Argument of type 'unknown' is not assignable to parameter of type 'SetStateAction<any[]>'.
|
|
13
|
+
../plugins/madefront-ai/ui/src/components/GlobalErrorBoundary.tsx(1,8): error TS6133: 'React' is declared but its value is never read.
|
|
14
|
+
../plugins/madefront-ai/ui/src/components/SkeletonCard.tsx(1,1): error TS6133: 'React' is declared but its value is never read.
|
|
15
|
+
../plugins/madefront-ai/ui/src/components/widgets/ThreeDRoomWidget.tsx(1,8): error TS6133: 'React' is declared but its value is never read.
|
|
16
|
+
../plugins/madefront-ai/ui/src/components/widgets/ThreeDRoomWidget.tsx(16,15): error TS6133: 'state' is declared but its value is never read.
|
|
17
|
+
../plugins/madefront-ai/ui/src/graph-engine/BaseNode.tsx(1,17): error TS6133: 'memo' is declared but its value is never read.
|
|
18
|
+
../plugins/madefront-ai/ui/src/graph-engine/GraphCanvas.tsx(1,17): error TS6133: 'useCallback' is declared but its value is never read.
|
|
19
|
+
../plugins/madefront-ai/ui/src/graph-engine/GraphCanvas.tsx(7,5): error TS6133: 'useNodesState' is declared but its value is never read.
|
|
20
|
+
../plugins/madefront-ai/ui/src/graph-engine/GraphCanvas.tsx(8,5): error TS6133: 'useEdgesState' is declared but its value is never read.
|
|
21
|
+
../plugins/madefront-ai/ui/src/graph-engine/GraphCanvas.tsx(13,5): error TS6133: 'addEdge' is declared but its value is never read.
|
|
22
|
+
../plugins/madefront-ai/ui/src/graph-engine/nodes/GatewayNode.tsx(1,8): error TS6133: 'React' is declared but its value is never read.
|
|
23
|
+
../plugins/madefront-ai/ui/src/graph-engine/nodes/MasterBrainNode.tsx(1,8): error TS6133: 'React' is declared but its value is never read.
|
|
24
|
+
../plugins/madefront-ai/ui/src/graph-engine/SmartEdge.tsx(10,5): error TS6133: 'id' is declared but its value is never read.
|
|
25
|
+
../plugins/madefront-ai/ui/src/graph-engine/SmartEdge.tsx(20,5): error TS6133: 'label' is declared but its value is never read.
|
|
26
|
+
../plugins/madefront-ai/ui/src/graph-engine/SmartEdge.tsx(34,9): error TS6133: 'animated' is declared but its value is never read.
|
|
27
|
+
Waiting for the debugger to disconnect...
|
|
28
|
+
ELIFECYCLE Command failed with exit code 2.
|
|
29
|
+
Waiting for the debugger to disconnect...
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@decido/plugin-agent-manager",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AgentManager Plugin for Decido OS with Advanced Canvas",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@tauri-apps/api": "^2.0.0-rc.0",
|
|
9
|
+
"@xyflow/react": "^12.0.0",
|
|
10
|
+
"lucide-react": "^0.575.0",
|
|
11
|
+
"qrcode.react": "^4.1.0",
|
|
12
|
+
"recharts": "^3.7.0",
|
|
13
|
+
"zustand": "^5.0.11"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/react": "^18.2.0",
|
|
17
|
+
"@types/react-dom": "^18.2.0",
|
|
18
|
+
"typescript": "^5.0.0"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"react": "^18.3.1",
|
|
22
|
+
"react-dom": "^18.3.1",
|
|
23
|
+
"@decido/canvas-core": "0.1.0",
|
|
24
|
+
"@decido/kernel-bridge": "1.0.0",
|
|
25
|
+
"@decido/sdk": "1.0.0"
|
|
26
|
+
},
|
|
27
|
+
"license": "UNLICENSED",
|
|
28
|
+
"scripts": {
|
|
29
|
+
"lint": "eslint \"src/**/*.ts*\"",
|
|
30
|
+
"build": "tsup src/index.ts --format esm --dts --minify --clean"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { definePlugin } from '@decido/sdk';
|
|
2
|
+
import type { DecidoApp } from '@decido/sdk';
|
|
3
|
+
import { TelemetryHUDWidget } from './widgets/TelemetryHUDWidget';
|
|
4
|
+
import { AdvancedCanvasWidget } from './widgets/AdvancedCanvasWidget';
|
|
5
|
+
import { MindExplorerWidget } from './widgets/MindExplorerWidget';
|
|
6
|
+
import { PromptHubWidget } from './widgets/PromptHubWidget';
|
|
7
|
+
import { MobileLinkWidget } from './widgets/MobileLinkWidget';
|
|
8
|
+
import { SystemSentinelWidget } from './widgets/SystemSentinelWidget';
|
|
9
|
+
|
|
10
|
+
export const AgentManagerPlugin = definePlugin({
|
|
11
|
+
manifest: {
|
|
12
|
+
id: 'macia-agent-manager-core',
|
|
13
|
+
name: 'AgentManager 3D Core',
|
|
14
|
+
version: '2.1.0',
|
|
15
|
+
description: 'Advanced 3D visualization, Fleet telemetry and Agent orchestration.',
|
|
16
|
+
author: 'Decido',
|
|
17
|
+
icon: 'brain',
|
|
18
|
+
permissions: ['cortex:view', 'shell:layout'],
|
|
19
|
+
intents: [],
|
|
20
|
+
widgets: [
|
|
21
|
+
{
|
|
22
|
+
id: 'agent-manager-telemetry',
|
|
23
|
+
name: 'Fleet Telemetry',
|
|
24
|
+
defaultZone: 'ide-panel',
|
|
25
|
+
component: TelemetryHUDWidget,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'agent-manager-3d-canvas',
|
|
29
|
+
name: 'Structural Agent Canvas',
|
|
30
|
+
defaultZone: 'ide-editor',
|
|
31
|
+
component: AdvancedCanvasWidget,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'agent-manager-mind-explorer',
|
|
35
|
+
name: 'Vector Mind Explorer',
|
|
36
|
+
defaultZone: 'ide-panel',
|
|
37
|
+
component: MindExplorerWidget,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'agent-manager-prompt-hub',
|
|
41
|
+
name: 'Prompt Hub Framework',
|
|
42
|
+
defaultZone: 'ide-panel',
|
|
43
|
+
component: PromptHubWidget,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'agent-manager-mobile-link',
|
|
47
|
+
name: 'Thin Client Link',
|
|
48
|
+
defaultZone: 'ide-panel',
|
|
49
|
+
component: MobileLinkWidget,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'agent-manager-system-sentinel',
|
|
53
|
+
name: 'System Sentinel',
|
|
54
|
+
defaultZone: 'ide-panel',
|
|
55
|
+
component: SystemSentinelWidget,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
blueprints: [],
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
onMount: async (app: DecidoApp) => {
|
|
62
|
+
app.logger.log('Bootstrapping 3-Panel View Transition');
|
|
63
|
+
|
|
64
|
+
// Register workspace layout via kernel RPC (instead of direct shell store)
|
|
65
|
+
const canvasWorkspace = {
|
|
66
|
+
id: 'agent-manager-workspace',
|
|
67
|
+
name: 'Thought Canvas',
|
|
68
|
+
icon: null,
|
|
69
|
+
layout: {
|
|
70
|
+
id: 'root-split-horizontal',
|
|
71
|
+
type: 'split-horizontal' as const,
|
|
72
|
+
children: [
|
|
73
|
+
{
|
|
74
|
+
id: 'left-sidebar',
|
|
75
|
+
type: 'widget' as const,
|
|
76
|
+
widgetId: 'agent-manager-telemetry',
|
|
77
|
+
size: 20,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'main-center-split',
|
|
81
|
+
type: 'split-vertical' as const,
|
|
82
|
+
size: 60,
|
|
83
|
+
children: [
|
|
84
|
+
{
|
|
85
|
+
id: 'main-canvas',
|
|
86
|
+
type: 'widget' as const,
|
|
87
|
+
widgetId: 'agent-manager-3d-canvas',
|
|
88
|
+
size: 70,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: 'forge-panel',
|
|
92
|
+
type: 'widget' as const,
|
|
93
|
+
widgetId: 'forge-terminal',
|
|
94
|
+
size: 30,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'right-telemetry',
|
|
100
|
+
type: 'widget' as const,
|
|
101
|
+
widgetId: 'agent-manager-prompt-hub',
|
|
102
|
+
size: 25,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
await app.callKernel('add_workspace', canvasWorkspace);
|
|
109
|
+
await app.callKernel('set_active_workspace', { workspaceId: canvasWorkspace.id });
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
onUnmount: () => {
|
|
113
|
+
console.log('[AgentManager] Cleaned up.');
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
onError: (error, phase) => {
|
|
117
|
+
console.error(`[AgentManager] Error during ${phase}:`, error);
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export default AgentManagerPlugin;
|
|
122
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { MaciaIntent, useShellStore } from '@decido/shell';
|
|
2
|
+
import { kernel } from '@decido/kernel-bridge';
|
|
3
|
+
|
|
4
|
+
export const agentManagerIntents: MaciaIntent[] = [
|
|
5
|
+
{
|
|
6
|
+
id: 'intent-scan-fleet',
|
|
7
|
+
name: 'Scan Agent Fleet',
|
|
8
|
+
match: {
|
|
9
|
+
pattern: /(scan|lanzar escaneo).*(flota|fleet|agentes)/i,
|
|
10
|
+
description: 'Ping all active local agents via Rust backend',
|
|
11
|
+
examples: ['lanzar escaneo de flota', 'scan fleet status'],
|
|
12
|
+
},
|
|
13
|
+
handler: async (matches) => {
|
|
14
|
+
console.log(`[Intent] Scanning Fleet. Matched:`, matches[0]);
|
|
15
|
+
await kernel.notify('Fleet Scanner', 'Initiating wide-band agent ping...');
|
|
16
|
+
await kernel.vibrate('light');
|
|
17
|
+
|
|
18
|
+
// Execute simulated rust backend call
|
|
19
|
+
const result = await kernel.execute<{ ok: boolean, count: number }>('scan_fleet');
|
|
20
|
+
if (result?.ok) {
|
|
21
|
+
await kernel.notify('Scan Complete', `Detected ${result.count || 3} active agents.`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'intent-open-3d-visor',
|
|
27
|
+
name: 'Open 3D Visor',
|
|
28
|
+
match: {
|
|
29
|
+
pattern: /(abrir|open).*(visor 3d|3d visor|canvas)/i,
|
|
30
|
+
description: 'Launches the 3D Node viewer as a Transient Overlay',
|
|
31
|
+
examples: ['abrir visor 3d', 'open 3d canvas'],
|
|
32
|
+
},
|
|
33
|
+
handler: async () => {
|
|
34
|
+
console.log(`[Intent] Opening 3D Visor`);
|
|
35
|
+
const { openTransientOverlay } = useShellStore.getState();
|
|
36
|
+
|
|
37
|
+
openTransientOverlay({
|
|
38
|
+
widgetId: 'agent-manager-3d-canvas',
|
|
39
|
+
position: { x: window.innerWidth / 2 - 400, y: 150, width: 800, height: 600 }
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await kernel.vibrate('selection');
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'intent-cyber-mode',
|
|
47
|
+
name: 'Cybersecurity Mode',
|
|
48
|
+
match: {
|
|
49
|
+
pattern: /(activar|enable|start).*(modo ciberseguridad|cybersecurity mode|defensa)/i,
|
|
50
|
+
description: 'Triggers a specialized layout for threat analysis',
|
|
51
|
+
examples: ['activar modo ciberseguridad'],
|
|
52
|
+
},
|
|
53
|
+
handler: async () => {
|
|
54
|
+
console.log(`[Intent] Activating Cyber Mode`);
|
|
55
|
+
const { setLayout } = useShellStore.getState();
|
|
56
|
+
await kernel.vibrate('heavy');
|
|
57
|
+
await kernel.notify('DEFCON 2', 'Cybersecurity Mode Enabled');
|
|
58
|
+
|
|
59
|
+
// Transmute layout to emergency mode focusing entirely on Telemetry telemetry
|
|
60
|
+
setLayout({
|
|
61
|
+
id: 'cyber-layout',
|
|
62
|
+
type: 'split-vertical',
|
|
63
|
+
children: [
|
|
64
|
+
{ id: 'top-threat', type: 'widget', widgetId: 'agent-manager-telemetry', size: 30 },
|
|
65
|
+
{ id: 'bottom-canvas', type: 'widget', widgetId: 'agent-manager-3d-canvas', size: 70 },
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { invoke } from '@tauri-apps/api/core';
|
|
3
|
+
|
|
4
|
+
export interface RoleConfig {
|
|
5
|
+
system_prompt: string;
|
|
6
|
+
model_strategy: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface PromptHubState {
|
|
10
|
+
configs: Record<string, RoleConfig>;
|
|
11
|
+
isLoading: boolean;
|
|
12
|
+
error: string | null;
|
|
13
|
+
fetchConfigs: () => Promise<void>;
|
|
14
|
+
updateConfig: (role: string, systemPrompt: string, modelStrategy: string) => Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const usePromptHubStore = create<PromptHubState>()((set, get) => ({
|
|
18
|
+
configs: {},
|
|
19
|
+
isLoading: false,
|
|
20
|
+
error: null,
|
|
21
|
+
fetchConfigs: async () => {
|
|
22
|
+
set({ isLoading: true, error: null });
|
|
23
|
+
try {
|
|
24
|
+
const configs = await invoke<Record<string, RoleConfig>>('get_prompt_config');
|
|
25
|
+
set({ configs, isLoading: false });
|
|
26
|
+
} catch (err: any) {
|
|
27
|
+
set({ error: err.toString(), isLoading: false });
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
updateConfig: async (role: string, systemPrompt: string, modelStrategy: string) => {
|
|
31
|
+
try {
|
|
32
|
+
await invoke('update_prompt_config', {
|
|
33
|
+
roleKey: role,
|
|
34
|
+
systemPrompt,
|
|
35
|
+
modelStrategy
|
|
36
|
+
});
|
|
37
|
+
// Re-fetch logic or optimistic update
|
|
38
|
+
const currentConfigs = get().configs;
|
|
39
|
+
set({
|
|
40
|
+
configs: {
|
|
41
|
+
...currentConfigs,
|
|
42
|
+
[role]: { system_prompt: systemPrompt, model_strategy: modelStrategy }
|
|
43
|
+
},
|
|
44
|
+
error: null
|
|
45
|
+
});
|
|
46
|
+
} catch (err: any) {
|
|
47
|
+
set({ error: err.toString() });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}));
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { applyNodeChanges } from '@xyflow/react';
|
|
3
|
+
import { FlowCanvas } from '@decido/canvas-core';
|
|
4
|
+
import { Node3DWidget } from './Node3DWidget';
|
|
5
|
+
import { kernel } from '@decido/kernel-bridge';
|
|
6
|
+
|
|
7
|
+
const nodeTypes = {
|
|
8
|
+
dynamic3D: Node3DWidget
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const initialNodes = [
|
|
12
|
+
{ id: '1', type: 'dynamic3D', position: { x: 250, y: 100 }, data: { label: 'Genesis Agent', status: 'active', load: 45 } },
|
|
13
|
+
{ id: '2', type: 'dynamic3D', position: { x: 100, y: 400 }, data: { label: 'Data Scraper', status: 'idle', load: 0 } },
|
|
14
|
+
{ id: '3', type: 'dynamic3D', position: { x: 400, y: 400 }, data: { label: 'Analyzer Core', status: 'error', load: 99 } },
|
|
15
|
+
{ id: 'wa-gateway', type: 'dynamic3D', position: { x: 250, y: -100 }, data: { label: 'WhatsApp Gateway', status: 'active' } }
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const initialEdges = [
|
|
19
|
+
{ id: 'ewa-1', source: 'wa-gateway', target: '1', animated: true, style: { stroke: '#10b981' } },
|
|
20
|
+
{ id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#c084fc' } },
|
|
21
|
+
{ id: 'e1-3', source: '1', target: '3', animated: true, style: { stroke: '#22d3ee' } },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export const AdvancedCanvasWidget: React.FC = () => {
|
|
25
|
+
const [nodes, setNodes] = useState<any[]>(initialNodes);
|
|
26
|
+
const [edges, setEdges] = useState<any[]>(initialEdges);
|
|
27
|
+
|
|
28
|
+
const onNodesChange = useCallback((changes: any) => {
|
|
29
|
+
setNodes((nds) => applyNodeChanges(changes, nds));
|
|
30
|
+
|
|
31
|
+
changes.forEach((change: any) => {
|
|
32
|
+
if (change.type === 'position' && change.position) {
|
|
33
|
+
kernel.execute('broadcast_message', {
|
|
34
|
+
intent: "ui:node_moved",
|
|
35
|
+
payload: JSON.stringify({ id: change.id, pos: change.position })
|
|
36
|
+
}).catch(() => { });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const unsubscribe = kernel.onEvent((payload: any) => {
|
|
43
|
+
const { event_type, agent_id, data } = payload;
|
|
44
|
+
if (!event_type || !agent_id || !data) return;
|
|
45
|
+
|
|
46
|
+
if (event_type === 'agent_spawned' || event_type === 'agent_status_changed') {
|
|
47
|
+
setNodes(prev => {
|
|
48
|
+
const exists = prev.find(n => n.id === agent_id);
|
|
49
|
+
const machine = data.machine_id ? ` [${data.machine_id.slice(0, 4)}]` : '';
|
|
50
|
+
const loadVal = data.gas_max ? Math.round(((data.gas_max - (data.gas || data.gas_max)) / data.gas_max) * 100) : 0;
|
|
51
|
+
|
|
52
|
+
const newNode = {
|
|
53
|
+
id: agent_id,
|
|
54
|
+
type: 'dynamic3D',
|
|
55
|
+
// Try to maintain previous pos or random scatter for new elements
|
|
56
|
+
position: exists ? exists.position : { x: 100 + Math.random() * 300, y: 100 + Math.random() * 300 },
|
|
57
|
+
data: {
|
|
58
|
+
label: `${data.name || 'Agent'}${machine}`,
|
|
59
|
+
status: data.status ? String(data.status).toLowerCase() : 'active',
|
|
60
|
+
load: loadVal,
|
|
61
|
+
isVerified: !!data.signature
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (exists) {
|
|
66
|
+
return prev.map(n => n.id === agent_id ? newNode : n);
|
|
67
|
+
}
|
|
68
|
+
return [...prev, newNode];
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (data.parent_id) {
|
|
72
|
+
setEdges((prev: any[]) => {
|
|
73
|
+
const edgeId = `e${data.parent_id}-${agent_id}`;
|
|
74
|
+
if (!prev.find(e => e.id === edgeId)) {
|
|
75
|
+
return [...prev, { id: edgeId, source: data.parent_id, target: agent_id, animated: true, style: { stroke: '#10b981', strokeWidth: 2 } }];
|
|
76
|
+
}
|
|
77
|
+
return prev;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
} else if (event_type === 'agent_terminated') {
|
|
81
|
+
setNodes(prev => prev.filter(n => n.id !== agent_id));
|
|
82
|
+
setEdges(prev => prev.filter(e => e.source !== agent_id && e.target !== agent_id));
|
|
83
|
+
} else if (event_type === 'cognitive_frame') {
|
|
84
|
+
// Route stream of thought to the specific node
|
|
85
|
+
setNodes(prev => prev.map(n => {
|
|
86
|
+
if (n.id === agent_id) {
|
|
87
|
+
return {
|
|
88
|
+
...n,
|
|
89
|
+
data: {
|
|
90
|
+
...n.data,
|
|
91
|
+
lastThought: data.thought,
|
|
92
|
+
lastAction: data.action,
|
|
93
|
+
isThinking: true,
|
|
94
|
+
thoughtTimestamp: Date.now()
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return n;
|
|
99
|
+
}));
|
|
100
|
+
} else if (event_type === 'broadcast_event' && data.event_type === 'ui:update_node_visuals') {
|
|
101
|
+
const { nodeId, activity, isThinking } = data;
|
|
102
|
+
|
|
103
|
+
setNodes(prev => prev.map(n => {
|
|
104
|
+
if (n.id === nodeId) {
|
|
105
|
+
return {
|
|
106
|
+
...n,
|
|
107
|
+
data: {
|
|
108
|
+
...n.data,
|
|
109
|
+
load: activity,
|
|
110
|
+
status: isThinking ? 'active' : 'idle',
|
|
111
|
+
lastPulse: Date.now()
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return n;
|
|
116
|
+
}));
|
|
117
|
+
|
|
118
|
+
setEdges(prev => prev.map(e => {
|
|
119
|
+
if (e.source === nodeId || e.target === nodeId) {
|
|
120
|
+
return { ...e, animated: isThinking, style: { ...e.style, stroke: activity > 50 ? '#00f2e0' : '#c084fc' } };
|
|
121
|
+
}
|
|
122
|
+
return e;
|
|
123
|
+
}));
|
|
124
|
+
} else if (event_type === 'broadcast_event' && data.intent === 'ui:node_moved') {
|
|
125
|
+
try {
|
|
126
|
+
const { id, pos } = JSON.parse(data.payload);
|
|
127
|
+
setNodes(prev => prev.map(n => n.id === id ? { ...n, position: pos } : n));
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.error("Failed to parse node_moved payload");
|
|
130
|
+
}
|
|
131
|
+
} else if (event_type === 'broadcast_event' && data.topic === 'sim:node_activity') {
|
|
132
|
+
try {
|
|
133
|
+
const { nodeId, type } = JSON.parse(data.payload);
|
|
134
|
+
setNodes(prev => prev.map(n => {
|
|
135
|
+
if (n.id === nodeId) {
|
|
136
|
+
return {
|
|
137
|
+
...n,
|
|
138
|
+
data: {
|
|
139
|
+
...n.data,
|
|
140
|
+
activityStatus: type
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return n;
|
|
145
|
+
}));
|
|
146
|
+
|
|
147
|
+
// Reset activity after 3 seconds
|
|
148
|
+
setTimeout(() => {
|
|
149
|
+
setNodes(prev => prev.map(n => {
|
|
150
|
+
if (n.id === nodeId) {
|
|
151
|
+
return {
|
|
152
|
+
...n,
|
|
153
|
+
data: {
|
|
154
|
+
...n.data,
|
|
155
|
+
activityStatus: 'idle'
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return n;
|
|
160
|
+
}));
|
|
161
|
+
}, 3000);
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.error("Failed to parse sim:node_activity payload", e);
|
|
164
|
+
}
|
|
165
|
+
} else if (event_type === 'message_sent' || event_type === 'broadcast_event') {
|
|
166
|
+
// Check if it's hitting the gateway or coming from it
|
|
167
|
+
if (data.signature) {
|
|
168
|
+
setNodes(prev => prev.map(n => {
|
|
169
|
+
if (n.id === agent_id || n.id === 'wa-gateway') {
|
|
170
|
+
return {
|
|
171
|
+
...n,
|
|
172
|
+
data: {
|
|
173
|
+
...n.data,
|
|
174
|
+
isVerified: true
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return n;
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
return () => {
|
|
184
|
+
unsubscribe();
|
|
185
|
+
};
|
|
186
|
+
}, []);
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<div className="h-full w-full">
|
|
190
|
+
<FlowCanvas
|
|
191
|
+
nodes={nodes}
|
|
192
|
+
edges={edges}
|
|
193
|
+
onNodesChange={onNodesChange}
|
|
194
|
+
nodeTypes={nodeTypes as any}
|
|
195
|
+
/>
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { kernel } from '@decido/kernel-bridge';
|
|
3
|
+
|
|
4
|
+
interface MemoryRecord {
|
|
5
|
+
id: string;
|
|
6
|
+
payload: {
|
|
7
|
+
text?: string;
|
|
8
|
+
agent_id?: string;
|
|
9
|
+
thought?: string;
|
|
10
|
+
action?: string;
|
|
11
|
+
observation?: string;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
};
|
|
14
|
+
score: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const MindExplorerWidget: React.FC = () => {
|
|
18
|
+
const [memories, setMemories] = useState<MemoryRecord[]>([]);
|
|
19
|
+
const [query, setQuery] = useState('');
|
|
20
|
+
const [loading, setLoading] = useState(false);
|
|
21
|
+
const [error, setError] = useState<string | null>(null);
|
|
22
|
+
|
|
23
|
+
const handleSearch = async (e?: React.FormEvent) => {
|
|
24
|
+
if (e) e.preventDefault();
|
|
25
|
+
setLoading(true);
|
|
26
|
+
setError(null);
|
|
27
|
+
try {
|
|
28
|
+
const results = await kernel.fetchMemories('system', query || "experience", 20);
|
|
29
|
+
setMemories(results);
|
|
30
|
+
} catch (err: any) {
|
|
31
|
+
setError(err.toString());
|
|
32
|
+
} finally {
|
|
33
|
+
setLoading(false);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handleDelete = async (id: string) => {
|
|
38
|
+
try {
|
|
39
|
+
await kernel.deleteMemory('system', id);
|
|
40
|
+
setMemories(prev => prev.filter(m => m.id !== id));
|
|
41
|
+
kernel.notify('Trauma Eradicated', 'The selected memory has been purged from Qdrant.');
|
|
42
|
+
} catch (err: any) {
|
|
43
|
+
setError(err.toString());
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Load initial memories on mount
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
handleSearch();
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className="w-full h-full bg-black/40 backdrop-blur-2xl border border-white/10 rounded-2xl flex flex-col overflow-hidden text-slate-300 font-sans p-4 shadow-2xl relative">
|
|
54
|
+
<div className="absolute top-0 right-0 w-64 h-64 bg-emerald-500/10 rounded-full blur-3xl pointer-events-none -mr-20 -mt-20"></div>
|
|
55
|
+
|
|
56
|
+
{/* Header */}
|
|
57
|
+
<div className="flex items-center justify-between mb-6 z-10 border-b border-white/5 pb-4">
|
|
58
|
+
<div className="flex items-center gap-3">
|
|
59
|
+
<div className="w-8 h-8 rounded-lg bg-emerald-500/20 border border-emerald-500/40 flex items-center justify-center">
|
|
60
|
+
<svg className="w-4 h-4 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 002-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path></svg>
|
|
61
|
+
</div>
|
|
62
|
+
<div>
|
|
63
|
+
<h2 className="text-emerald-400 font-bold tracking-widest uppercase text-sm">Mind Explorer</h2>
|
|
64
|
+
<span className="text-[10px] text-slate-500 mono">Vector Storage (Qdrant)</span>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
{/* Search Bar */}
|
|
69
|
+
<form onSubmit={handleSearch} className="flex gap-2 relative">
|
|
70
|
+
<input
|
|
71
|
+
type="text"
|
|
72
|
+
value={query}
|
|
73
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
74
|
+
placeholder="Search semantic memory..."
|
|
75
|
+
className="bg-black/50 border border-white/10 rounded-lg px-4 py-2 text-sm text-white placeholder-white/30 outline-none focus:border-emerald-500/50 transition-colors w-64"
|
|
76
|
+
/>
|
|
77
|
+
<button type="submit" className="bg-emerald-500/20 hover:bg-emerald-500/40 border border-emerald-500/50 text-emerald-300 px-4 py-2 rounded-lg text-sm font-bold transition-all shadow-[0_0_15px_rgba(52,211,153,0.3)] hover:shadow-[0_0_20px_rgba(52,211,153,0.6)] flex items-center gap-2">
|
|
78
|
+
{loading ? (
|
|
79
|
+
<svg className="animate-spin h-4 w-4 text-emerald-300" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle><path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
|
|
80
|
+
) : 'Query'}
|
|
81
|
+
</button>
|
|
82
|
+
</form>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
{error && (
|
|
86
|
+
<div className="bg-red-500/20 border border-red-500/50 text-red-300 px-4 py-3 rounded-lg text-xs mb-4 flex items-center gap-2 z-10">
|
|
87
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
|
|
88
|
+
{error}
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
{/* Memory List */}
|
|
93
|
+
<div className="flex-1 overflow-y-auto pr-2 space-y-4 z-10 custom-scrollbar">
|
|
94
|
+
{memories.length === 0 && !loading && !error ? (
|
|
95
|
+
<div className="flex flex-col items-center justify-center h-full text-slate-500 space-y-3">
|
|
96
|
+
<svg className="w-12 h-12 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 002-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path></svg>
|
|
97
|
+
<p className="text-sm">No episodic memories found.</p>
|
|
98
|
+
</div>
|
|
99
|
+
) : (
|
|
100
|
+
memories.map(mem => {
|
|
101
|
+
const isTrauma = mem.payload?.thought?.toLowerCase().includes('trauma') || mem.payload?.action?.toLowerCase().includes('fail');
|
|
102
|
+
const borderColor = isTrauma ? 'border-red-500/30' : 'border-emerald-500/20';
|
|
103
|
+
const bgTint = isTrauma ? 'bg-red-500/5' : 'bg-white/5';
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div key={mem.id} className={`p-4 rounded-xl border ${borderColor} ${bgTint} hover:bg-white/10 transition-colors group relative`}>
|
|
107
|
+
<div className="absolute top-4 right-4 flex items-center gap-2">
|
|
108
|
+
<span className="text-[10px] text-slate-500 font-mono">Score: {mem.score.toFixed(3)}</span>
|
|
109
|
+
<button
|
|
110
|
+
onClick={() => handleDelete(mem.id)}
|
|
111
|
+
title="Purge Memory"
|
|
112
|
+
className="w-6 h-6 rounded flex items-center justify-center bg-red-500/10 text-red-400 hover:bg-red-500 hover:text-white transition-colors border border-red-500/30 opacity-0 group-hover:opacity-100"
|
|
113
|
+
>
|
|
114
|
+
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
<div className="text-xs text-slate-400 mb-1 flex items-center gap-2">
|
|
118
|
+
<span className="text-white/50">{mem.id.substring(0, 8)}</span>
|
|
119
|
+
{mem.payload?.agent_id && (
|
|
120
|
+
<span className="text-emerald-400 font-mono bg-emerald-500/10 px-1 py-0.5 rounded">
|
|
121
|
+
{mem.payload.agent_id}
|
|
122
|
+
</span>
|
|
123
|
+
)}
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{mem.payload?.thought && (
|
|
127
|
+
<div className="mt-2 text-sm text-slate-200">
|
|
128
|
+
<span className="text-white/40 block text-[10px] uppercase font-bold mb-1 tracking-wider">Thought</span>
|
|
129
|
+
{mem.payload.thought}
|
|
130
|
+
</div>
|
|
131
|
+
)}
|
|
132
|
+
|
|
133
|
+
<div className="grid grid-cols-2 gap-4 mt-3">
|
|
134
|
+
{mem.payload?.action && (
|
|
135
|
+
<div className="bg-black/30 rounded p-2 border border-white/5">
|
|
136
|
+
<span className="text-purple-400 block text-[10px] uppercase font-bold mb-1 tracking-wider">Action</span>
|
|
137
|
+
<p className="text-xs text-slate-300 font-mono break-all">{mem.payload.action}</p>
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
{mem.payload?.observation && (
|
|
141
|
+
<div className="bg-black/30 rounded p-2 border border-white/5">
|
|
142
|
+
<span className="text-cyan-400 block text-[10px] uppercase font-bold mb-1 tracking-wider">Observation</span>
|
|
143
|
+
<p className="text-xs text-slate-300 break-words">{mem.payload.observation}</p>
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
{(!mem.payload?.thought && !mem.payload?.action) && (
|
|
149
|
+
<div className="mt-2 text-xs text-slate-400 font-mono break-all">
|
|
150
|
+
{mem.payload?.text || JSON.stringify(mem.payload)}
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
})
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<style>{`
|
|
160
|
+
.custom-scrollbar::-webkit-scrollbar {
|
|
161
|
+
width: 6px;
|
|
162
|
+
}
|
|
163
|
+
.custom-scrollbar::-webkit-scrollbar-track {
|
|
164
|
+
background: rgba(255, 255, 255, 0.02);
|
|
165
|
+
border-radius: 4px;
|
|
166
|
+
}
|
|
167
|
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
|
168
|
+
background: rgba(52, 211, 153, 0.2);
|
|
169
|
+
border-radius: 4px;
|
|
170
|
+
}
|
|
171
|
+
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
172
|
+
background: rgba(52, 211, 153, 0.4);
|
|
173
|
+
}
|
|
174
|
+
`}</style>
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { QRCodeSVG } from 'qrcode.react';
|
|
3
|
+
import * as Lucide from 'lucide-react';
|
|
4
|
+
import { kernel } from '@decido/kernel-bridge';
|
|
5
|
+
|
|
6
|
+
export const MobileLinkWidget = () => {
|
|
7
|
+
const [machineId, setMachineId] = useState("MAC-OS-1");
|
|
8
|
+
// TODO: Phase 4: Retrieve dynamic local IP from Tauri instead of hardcoding
|
|
9
|
+
const localIp = "192.168.1.15";
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const fetchId = async () => {
|
|
13
|
+
try {
|
|
14
|
+
const id = await kernel.execute('get_machine_id');
|
|
15
|
+
if (id) setMachineId(id as string);
|
|
16
|
+
} catch (e) { }
|
|
17
|
+
};
|
|
18
|
+
fetchId();
|
|
19
|
+
}, []);
|
|
20
|
+
|
|
21
|
+
const connectionString = JSON.stringify({
|
|
22
|
+
host: localIp,
|
|
23
|
+
port: 6379,
|
|
24
|
+
token: "SECURE_AUTH_TOKEN_GEN_BY_RUST",
|
|
25
|
+
masterId: machineId
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="p-6 bg-zinc-900 border border-white/5 rounded-3xl text-center w-full h-full flex flex-col justify-center items-center">
|
|
30
|
+
<h3 className="text-lg font-bold text-white mb-4 flex items-center justify-center gap-2">
|
|
31
|
+
<Lucide.Smartphone className="text-cyan-400" /> Vincular Dispositivo
|
|
32
|
+
</h3>
|
|
33
|
+
<div className="bg-white p-4 rounded-2xl inline-block mb-4 shadow-[0_0_30px_rgba(255,255,255,0.1)]">
|
|
34
|
+
<QRCodeSVG value={connectionString} size={180} />
|
|
35
|
+
</div>
|
|
36
|
+
<p className="text-[10px] text-zinc-500 uppercase tracking-widest font-mono">
|
|
37
|
+
Escanea para activar el modo Thin Client
|
|
38
|
+
</p>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Handle, Position } from '@xyflow/react';
|
|
3
|
+
import { ShieldCheck } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
interface Node3DProps {
|
|
6
|
+
data: {
|
|
7
|
+
label: string;
|
|
8
|
+
status: 'idle' | 'active' | 'error';
|
|
9
|
+
load: number;
|
|
10
|
+
isRemote?: boolean;
|
|
11
|
+
lastThought?: string;
|
|
12
|
+
lastAction?: string;
|
|
13
|
+
isThinking?: boolean;
|
|
14
|
+
thoughtTimestamp?: number;
|
|
15
|
+
isVerified?: boolean;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const Node3DWidget: React.FC<Node3DProps> = ({ data }) => {
|
|
20
|
+
const [showThought, setShowThought] = React.useState(false);
|
|
21
|
+
|
|
22
|
+
// Calculamos el nivel de "Glow" basado en el load (0-100)
|
|
23
|
+
const glowIntensity = (data.load / 100) * 20;
|
|
24
|
+
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
if (data.lastThought && data.thoughtTimestamp) {
|
|
27
|
+
setShowThought(true);
|
|
28
|
+
const timeout = setTimeout(() => {
|
|
29
|
+
setShowThought(false);
|
|
30
|
+
}, 5000); // Hide thought after 5 seconds of inactivity
|
|
31
|
+
return () => clearTimeout(timeout);
|
|
32
|
+
}
|
|
33
|
+
}, [data.lastThought, data.thoughtTimestamp]);
|
|
34
|
+
|
|
35
|
+
// Advanced visual CSS representations mimicking 3D WebGL structs
|
|
36
|
+
const statusColors = {
|
|
37
|
+
idle: 'from-gray-700 to-gray-900 border-gray-500',
|
|
38
|
+
active: 'from-purple-600 to-cyan-500 border-cyan-400',
|
|
39
|
+
error: 'from-red-600 to-red-900 border-red-500'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const shadowGlow = data.isRemote
|
|
43
|
+
? 'shadow-[0_0_30px_rgba(34,211,238,0.6)] border-cyan-400'
|
|
44
|
+
: (data.status === 'active'
|
|
45
|
+
? 'shadow-[0_0_30px_rgba(192,132,252,0.4)]'
|
|
46
|
+
: 'shadow-2xl');
|
|
47
|
+
|
|
48
|
+
const remoteBadge = data.isRemote ? (
|
|
49
|
+
<div className="absolute -top-3 -right-3 bg-cyan-500 text-black text-[10px] uppercase font-bold px-2 py-0.5 rounded-full shadow-lg border border-cyan-200">
|
|
50
|
+
REMOTE
|
|
51
|
+
</div>
|
|
52
|
+
) : null;
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className={`relative px-6 py-4 rounded-xl border border-white/20 bg-gradient-to-br ${statusColors[data.status]} ${shadowGlow} transform transition-all hover:scale-105 backdrop-blur-md`}>
|
|
56
|
+
{remoteBadge}
|
|
57
|
+
|
|
58
|
+
{/* ANILLO DE ACTIVIDAD EXTERNO */}
|
|
59
|
+
{data.status === 'active' && (
|
|
60
|
+
<div
|
|
61
|
+
className="absolute -inset-2 rounded-xl opacity-50 animate-pulse pointer-events-none"
|
|
62
|
+
style={{
|
|
63
|
+
border: `2px solid #22d3ee`,
|
|
64
|
+
boxShadow: `0 0 ${glowIntensity}px #22d3ee`,
|
|
65
|
+
transition: 'all 0.3s ease'
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
{/* Cognitive Speech Bubble */}
|
|
71
|
+
<div className={`absolute -top-16 left-1/2 -translate-x-1/2 min-w-[200px] max-w-[250px] p-2.5 rounded-lg bg-black/80 backdrop-blur-xl border border-white/10 shadow-2xl transition-all duration-300 origin-bottom pointer-events-none z-50 ${showThought ? 'opacity-100 scale-100 translate-y-0' : 'opacity-0 scale-95 translate-y-2'}`}>
|
|
72
|
+
<div className="absolute -bottom-2 left-1/2 -translate-x-1/2 w-0 h-0 border-l-[6px] border-r-[6px] border-t-[8px] border-transparent border-t-white/10"></div>
|
|
73
|
+
<div className="absolute -bottom-[7px] left-1/2 -translate-x-1/2 w-0 h-0 border-l-[5px] border-r-[5px] border-t-[7px] border-transparent border-t-black/80"></div>
|
|
74
|
+
<div className="text-[10px] text-cyan-400 font-bold mb-1 flex items-center gap-1">
|
|
75
|
+
<div className="w-1.5 h-1.5 bg-cyan-400 rounded-full animate-pulse"></div>
|
|
76
|
+
{data.lastAction || 'THINKING...'}
|
|
77
|
+
</div>
|
|
78
|
+
<div className="text-xs text-white/90 leading-tight line-clamp-3">
|
|
79
|
+
{data.lastThought || 'Initialising cognitive cycle...'}
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<Handle type="target" position={Position.Top} className="w-3 h-3 bg-white border-none" />
|
|
84
|
+
|
|
85
|
+
<div className="flex flex-col items-center">
|
|
86
|
+
{/* Pseudo-3D Core indicator */}
|
|
87
|
+
<div className="w-12 h-12 mb-3 rounded-full bg-black/50 border border-white/30 flex items-center justify-center relative overflow-hidden">
|
|
88
|
+
<div className="absolute inset-0 bg-white/10 rotate-45 transform origin-center"></div>
|
|
89
|
+
<div className={`w-4 h-4 rounded-full ${data.status === 'active' || showThought ? 'bg-cyan-400 animate-pulse shadow-[0_0_15px_#22d3ee]' : 'bg-white/20'}`}></div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div className="font-bold text-white tracking-wide flex items-center gap-2">
|
|
93
|
+
{data.label}
|
|
94
|
+
{data.isVerified && (
|
|
95
|
+
<div title="Verificado Criptográficamente">
|
|
96
|
+
<ShieldCheck className="w-3.5 h-3.5 text-green-400" />
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{/* BARRA DE PROCESAMIENTO INTERNA */}
|
|
102
|
+
<div className="w-full h-1.5 bg-white/10 rounded-full mt-2 overflow-hidden shadow-inner">
|
|
103
|
+
<div
|
|
104
|
+
className="h-full bg-cyan-400 transition-all duration-300 shadow-[0_0_10px_#22d3ee]"
|
|
105
|
+
style={{ width: `${data.load}%` }}
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
<div className="text-[10px] text-white/50 mt-1 uppercase font-bold tracking-widest">{data.load}% LOAD</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<Handle type="source" position={Position.Bottom} className="w-3 h-3 bg-white border-none" />
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { usePromptHubStore } from '../store/usePromptHubStore';
|
|
3
|
+
import { Save, RefreshCw, Cpu, BrainCircuit, TerminalSquare, AlertCircle } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
export const PromptHubWidget: React.FC = () => {
|
|
6
|
+
const { configs, isLoading, error, fetchConfigs, updateConfig } = usePromptHubStore();
|
|
7
|
+
const [selectedRole, setSelectedRole] = useState<string>('default');
|
|
8
|
+
const [localPrompt, setLocalPrompt] = useState<string>('');
|
|
9
|
+
const [localStrategy, setLocalStrategy] = useState<string>('');
|
|
10
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
fetchConfigs();
|
|
14
|
+
}, [fetchConfigs]);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (configs[selectedRole]) {
|
|
18
|
+
setLocalPrompt(configs[selectedRole].system_prompt);
|
|
19
|
+
setLocalStrategy(configs[selectedRole].model_strategy);
|
|
20
|
+
} else if (Object.keys(configs).length > 0) {
|
|
21
|
+
const firstKey = Object.keys(configs)[0];
|
|
22
|
+
setSelectedRole(firstKey);
|
|
23
|
+
}
|
|
24
|
+
}, [selectedRole, configs]);
|
|
25
|
+
|
|
26
|
+
const handleSave = async () => {
|
|
27
|
+
setIsSaving(true);
|
|
28
|
+
await updateConfig(selectedRole, localPrompt, localStrategy);
|
|
29
|
+
setIsSaving(false);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const modelOptions = [
|
|
33
|
+
{ value: 'gemini:gemini-3-flash-preview', label: 'Gemini 3 Flash (Cloud)' },
|
|
34
|
+
{ value: 'gemini:gemini-3.1-pro-preview', label: 'Gemini 3 Pro (Cloud)' },
|
|
35
|
+
{ value: 'ollama:llama3.1:latest', label: 'LLaMA 3.1 8B (Local)' },
|
|
36
|
+
{ value: 'ollama:qwen2.5:14b', label: 'Qwen 2.5 14B (Local)' }
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="flex w-full h-full bg-[#0a0f12]/80 backdrop-blur-xl border border-white/5 rounded-2xl overflow-hidden shadow-2xl text-slate-300 font-sans">
|
|
41
|
+
{/* Sidebar Roles */}
|
|
42
|
+
<div className="w-64 border-r border-white/5 bg-black/20 flex flex-col">
|
|
43
|
+
<div className="p-4 border-b border-white/5 flex items-center justify-between">
|
|
44
|
+
<h2 className="text-sm font-semibold tracking-wider text-white/90 flex items-center gap-2">
|
|
45
|
+
<BrainCircuit className="w-4 h-4 text-emerald-400" />
|
|
46
|
+
AGENT ROLES
|
|
47
|
+
</h2>
|
|
48
|
+
<button
|
|
49
|
+
onClick={() => fetchConfigs()}
|
|
50
|
+
className="p-1 hover:bg-white/10 rounded-md transition-colors"
|
|
51
|
+
title="Refresh Configs"
|
|
52
|
+
>
|
|
53
|
+
<RefreshCw className={`w-3.5 h-3.5 ${isLoading ? 'animate-spin opacity-50' : 'text-slate-400 hover:text-white'}`} />
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div className="flex-1 overflow-y-auto p-2 space-y-1 custom-scrollbar">
|
|
58
|
+
{Object.keys(configs).map((role) => (
|
|
59
|
+
<button
|
|
60
|
+
key={role}
|
|
61
|
+
onClick={() => setSelectedRole(role)}
|
|
62
|
+
className={`w-full text-left px-3 py-2 rounded-lg text-xs transition-all duration-200 flex items-center gap-2
|
|
63
|
+
${selectedRole === role
|
|
64
|
+
? 'bg-emerald-500/10 text-emerald-300 border border-emerald-500/20'
|
|
65
|
+
: 'text-slate-400 hover:bg-white/5 hover:text-slate-200 border border-transparent'
|
|
66
|
+
}`}
|
|
67
|
+
>
|
|
68
|
+
<TerminalSquare className={`w-3.5 h-3.5 ${selectedRole === role ? 'text-emerald-400' : 'opacity-60'}`} />
|
|
69
|
+
<span className="capitalize">{role.replace('_', ' ')}</span>
|
|
70
|
+
</button>
|
|
71
|
+
))}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
{/* Editor Main Canvas */}
|
|
76
|
+
<div className="flex-1 flex flex-col bg-gradient-to-br from-[#0f1519] to-[#080b0d]">
|
|
77
|
+
{error && (
|
|
78
|
+
<div className="m-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg flex items-center gap-3 text-red-400 text-xs">
|
|
79
|
+
<AlertCircle className="w-4 h-4" />
|
|
80
|
+
{error}
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
{configs[selectedRole] ? (
|
|
85
|
+
<div className="flex-1 p-6 flex flex-col gap-6 overflow-y-auto">
|
|
86
|
+
|
|
87
|
+
{/* Header / Strategy Select */}
|
|
88
|
+
<div className="flex flex-col gap-2">
|
|
89
|
+
<label className="text-[10px] font-bold tracking-widest text-slate-500 uppercase">
|
|
90
|
+
Model Strategy Engine
|
|
91
|
+
</label>
|
|
92
|
+
<div className="relative group">
|
|
93
|
+
<select
|
|
94
|
+
value={localStrategy}
|
|
95
|
+
onChange={(e) => setLocalStrategy(e.target.value)}
|
|
96
|
+
className="w-full bg-[#131a20] border border-white/10 hover:border-white/20 rounded-xl px-4 py-3 text-sm text-white appearance-none transition-all outline-none focus:border-emerald-500/50 shadow-inner"
|
|
97
|
+
>
|
|
98
|
+
{modelOptions.map(opt => (
|
|
99
|
+
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
100
|
+
))}
|
|
101
|
+
{/* Dynamic fallback if config has unknown strategy */}
|
|
102
|
+
{!modelOptions.find(o => o.value === localStrategy) && (
|
|
103
|
+
<option value={localStrategy}>{localStrategy} (Custom)</option>
|
|
104
|
+
)}
|
|
105
|
+
</select>
|
|
106
|
+
<Cpu className="absolute right-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-500 pointer-events-none group-hover:text-emerald-400 transition-colors" />
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
{/* System Prompt Editor */}
|
|
111
|
+
<div className="flex-1 flex flex-col gap-2 relative">
|
|
112
|
+
<label className="text-[10px] font-bold tracking-widest text-slate-500 uppercase flex items-center justify-between">
|
|
113
|
+
<span>Core System Directive</span>
|
|
114
|
+
<span className="text-emerald-500/50 normal-case tracking-normal">Markdown Supported</span>
|
|
115
|
+
</label>
|
|
116
|
+
|
|
117
|
+
<div className="relative flex-1 group">
|
|
118
|
+
{/* Glowing border effect */}
|
|
119
|
+
<div className="absolute -inset-[1px] bg-gradient-to-b from-emerald-500/20 to-transparent rounded-xl opacity-0 group-focus-within:opacity-100 transition-opacity pointer-events-none" />
|
|
120
|
+
|
|
121
|
+
<textarea
|
|
122
|
+
value={localPrompt}
|
|
123
|
+
onChange={(e) => setLocalPrompt(e.target.value)}
|
|
124
|
+
className="w-full h-full min-h-[300px] bg-[#0c1013] border border-white/5 hover:border-white/10 rounded-xl p-5 text-sm font-mono text-emerald-50/80 leading-relaxed outline-none focus:ring-0 resize-none transition-all custom-scrollbar shadow-inner"
|
|
125
|
+
spellCheck="false"
|
|
126
|
+
placeholder="Insert the Agent's architectural persona here..."
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
{/* Action Bar */}
|
|
132
|
+
<div className="flex justify-end pt-2 border-t border-white/5">
|
|
133
|
+
<button
|
|
134
|
+
onClick={handleSave}
|
|
135
|
+
disabled={isSaving || (localPrompt === configs[selectedRole].system_prompt && localStrategy === configs[selectedRole].model_strategy)}
|
|
136
|
+
className="group relative px-6 py-2.5 bg-emerald-500 hover:bg-emerald-400 disabled:bg-slate-800 disabled:text-slate-500 rounded-lg text-sm font-medium text-black transition-all flex items-center gap-2 disabled:cursor-not-allowed overflow-hidden shadow-[0_0_20px_rgba(16,185,129,0.1)] hover:shadow-[0_0_30px_rgba(16,185,129,0.2)]"
|
|
137
|
+
>
|
|
138
|
+
{/* Shine effect */}
|
|
139
|
+
<div className="absolute inset-0 -translate-x-full group-hover:animate-[shimmer_1.5s_infinite] bg-gradient-to-r from-transparent via-white/30 to-transparent skew-x-12" />
|
|
140
|
+
|
|
141
|
+
<Save className={`w-4 h-4 ${isSaving ? 'animate-pulse' : ''}`} />
|
|
142
|
+
{isSaving ? 'Deploying to Swarm...' : 'Commit Protocol'}
|
|
143
|
+
</button>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
</div>
|
|
147
|
+
) : (
|
|
148
|
+
<div className="flex-1 flex flex-col items-center justify-center opacity-30">
|
|
149
|
+
<BrainCircuit className="w-16 h-16 text-slate-600 mb-4" />
|
|
150
|
+
<p className="text-sm">Initiating connection to Omni-Swarm or No configurations found.</p>
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { LineChart, Line, XAxis, YAxis, Tooltip, CartesianGrid, ResponsiveContainer } from 'recharts';
|
|
3
|
+
import * as Lucide from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
export const SystemSentinelWidget = ({ data }: any) => {
|
|
6
|
+
// This widget simulates observing the telemetry data injected via props
|
|
7
|
+
const [liveData, setLiveData] = useState({ history: [], current: { cpu: 0, gas: 0, mem: 0 } });
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (data && data.current) {
|
|
11
|
+
setLiveData(data);
|
|
12
|
+
}
|
|
13
|
+
}, [data]);
|
|
14
|
+
|
|
15
|
+
const history = liveData?.history || [];
|
|
16
|
+
const current = liveData?.current || { cpu: 0, gas: 0, mem: 0 };
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="p-4 bg-black/40 border border-cyan-500/20 rounded-2xl text-white font-sans h-full w-full flex flex-col">
|
|
20
|
+
<div className="flex justify-between items-center mb-4">
|
|
21
|
+
<h3 className="text-xs font-black tracking-widest text-cyan-400 uppercase flex items-center gap-2">
|
|
22
|
+
<Lucide.Activity size={14} /> System Sentinel Live
|
|
23
|
+
</h3>
|
|
24
|
+
<div className="flex gap-4">
|
|
25
|
+
<span className="text-[10px] text-white/50">CPU: <b className="text-white">{current.cpu?.toFixed(1)}%</b></span>
|
|
26
|
+
<span className="text-[10px] text-white/50">GAS: <b className="text-white">{current.gas?.toFixed(1)}</b></span>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div className="flex-1 min-h-[150px]">
|
|
31
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
32
|
+
<LineChart data={history}>
|
|
33
|
+
<CartesianGrid strokeDasharray="3 3" stroke="#222" />
|
|
34
|
+
<XAxis dataKey="time" hide />
|
|
35
|
+
<YAxis hide domain={[0, 100]} />
|
|
36
|
+
<Tooltip contentStyle={{ backgroundColor: '#111', border: '1px solid #333', fontSize: '10px' }} />
|
|
37
|
+
<Line type="monotone" dataKey="cpu" stroke="#06b6d4" strokeWidth={2} dot={false} isAnimationActive={false} />
|
|
38
|
+
<Line type="monotone" dataKey="gas" stroke="#f59e0b" strokeWidth={2} dot={false} isAnimationActive={false} />
|
|
39
|
+
</LineChart>
|
|
40
|
+
</ResponsiveContainer>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div className="grid grid-cols-2 gap-2 mt-4">
|
|
44
|
+
<div className="bg-white/5 p-2 rounded-lg border border-white/5">
|
|
45
|
+
<div className="text-[9px] text-cyan-400 uppercase font-bold">Inference Load</div>
|
|
46
|
+
<div className="text-lg font-mono tracking-tighter">{(current.cpu * 1.2).toFixed(0)} MHz</div>
|
|
47
|
+
</div>
|
|
48
|
+
<div className="bg-white/5 p-2 rounded-lg border border-white/5">
|
|
49
|
+
<div className="text-[9px] text-amber-500 uppercase font-bold">Economy Balance</div>
|
|
50
|
+
<div className="text-lg font-mono tracking-tighter">{Math.max(0, 1000 - current.gas).toFixed(0)} G</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { AreaChart, Area, ResponsiveContainer, YAxis, Tooltip } from 'recharts';
|
|
3
|
+
|
|
4
|
+
interface TelemetryPoint {
|
|
5
|
+
time: string;
|
|
6
|
+
cpu: number;
|
|
7
|
+
memory: number;
|
|
8
|
+
intents: number;
|
|
9
|
+
latency: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const TelemetryHUDWidget: React.FC = () => {
|
|
13
|
+
const [history, setHistory] = useState<TelemetryPoint[]>([]);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
// Pre-fill some empty data to make chart look continuous at start
|
|
17
|
+
const initial = Array(20).fill(null).map((_, i) => ({
|
|
18
|
+
time: new Date(Date.now() - (20 - i) * 1000).toLocaleTimeString(),
|
|
19
|
+
cpu: 0, memory: 0, intents: 0, latency: 0
|
|
20
|
+
}));
|
|
21
|
+
setHistory(initial);
|
|
22
|
+
|
|
23
|
+
const interval = setInterval(() => {
|
|
24
|
+
setHistory(prev => {
|
|
25
|
+
const now = new Date();
|
|
26
|
+
const newPoint = {
|
|
27
|
+
time: now.toLocaleTimeString(),
|
|
28
|
+
cpu: Math.floor(Math.random() * 60) + 10, // Simulated 10-70% CPU
|
|
29
|
+
memory: Math.floor(Math.random() * 500) + 2000, // Simulated 2-2.5GB
|
|
30
|
+
intents: Math.floor(Math.random() * 5),
|
|
31
|
+
latency: Math.floor(Math.random() * 50) + 20, // 20-70ms
|
|
32
|
+
};
|
|
33
|
+
const next = [...prev, newPoint];
|
|
34
|
+
if (next.length > 20) next.shift();
|
|
35
|
+
return next;
|
|
36
|
+
});
|
|
37
|
+
}, 1000);
|
|
38
|
+
|
|
39
|
+
return () => clearInterval(interval);
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
const current = history[history.length - 1] || { cpu: 0, memory: 0, intents: 0, latency: 0 };
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="h-full w-full p-6 text-sm flex flex-col gap-4 font-mono text-cyan-500 overflow-y-auto">
|
|
46
|
+
<h3 className="text-white/80 font-bold mb-2">Fleet Telemetry HUD</h3>
|
|
47
|
+
|
|
48
|
+
<div className="grid grid-cols-2 gap-4 flex-none">
|
|
49
|
+
<div className="bg-black/40 border border-cyan-500/30 p-4 rounded-xl shadow-[0_0_15px_rgba(34,211,238,0.1)]">
|
|
50
|
+
<span className="block text-white/50 mb-1">CPU Core Load</span>
|
|
51
|
+
<span className="text-2xl font-bold">{current.cpu}%</span>
|
|
52
|
+
</div>
|
|
53
|
+
<div className="bg-black/40 border border-cyan-500/30 p-4 rounded-xl shadow-[0_0_15px_rgba(34,211,238,0.1)]">
|
|
54
|
+
<span className="block text-white/50 mb-1">Heap Memory</span>
|
|
55
|
+
<span className="text-2xl font-bold">{(current.memory / 1024).toFixed(2)} GB</span>
|
|
56
|
+
</div>
|
|
57
|
+
<div className="bg-black/40 border border-purple-500/30 p-4 rounded-xl shadow-[0_0_15px_rgba(192,132,252,0.1)]">
|
|
58
|
+
<span className="block text-white/50 mb-1">Active Intents</span>
|
|
59
|
+
<span className="text-2xl font-bold text-purple-400">{current.intents}</span>
|
|
60
|
+
</div>
|
|
61
|
+
<div className="bg-black/40 border border-cyan-500/30 p-4 rounded-xl shadow-[0_0_15px_rgba(34,211,238,0.1)]">
|
|
62
|
+
<span className="block text-white/50 mb-1">Net Latency</span>
|
|
63
|
+
<span className="text-2xl font-bold">{current.latency} ms</span>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{/* Recharts Area */}
|
|
68
|
+
<div className="flex-1 mt-4 min-h-[200px] bg-black/40 border border-white/5 rounded-xl p-4 relative">
|
|
69
|
+
<span className="absolute top-4 left-4 text-xs font-bold text-white/30 tracking-widest uppercase z-10">Resource Usage History</span>
|
|
70
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
71
|
+
<AreaChart data={history} margin={{ top: 20, right: 0, left: -20, bottom: 0 }}>
|
|
72
|
+
<defs>
|
|
73
|
+
<linearGradient id="colorCpu" x1="0" y1="0" x2="0" y2="1">
|
|
74
|
+
<stop offset="5%" stopColor="#22d3ee" stopOpacity={0.3} />
|
|
75
|
+
<stop offset="95%" stopColor="#22d3ee" stopOpacity={0} />
|
|
76
|
+
</linearGradient>
|
|
77
|
+
<linearGradient id="colorMem" x1="0" y1="0" x2="0" y2="1">
|
|
78
|
+
<stop offset="5%" stopColor="#c084fc" stopOpacity={0.3} />
|
|
79
|
+
<stop offset="95%" stopColor="#c084fc" stopOpacity={0} />
|
|
80
|
+
</linearGradient>
|
|
81
|
+
</defs>
|
|
82
|
+
<YAxis stroke="#ffffff22" tick={{ fill: '#ffffff66', fontSize: 10 }} />
|
|
83
|
+
<Tooltip
|
|
84
|
+
contentStyle={{ backgroundColor: '#000000cc', borderColor: '#ffffff22', borderRadius: '8px', fontSize: '12px' }}
|
|
85
|
+
itemStyle={{ color: '#fff' }}
|
|
86
|
+
labelStyle={{ color: '#aaa', marginBottom: '4px' }}
|
|
87
|
+
/>
|
|
88
|
+
<Area type="monotone" dataKey="memory" stackId="1" stroke="#c084fc" fillOpacity={1} fill="url(#colorMem)" name="Memory (MB)" />
|
|
89
|
+
<Area type="monotone" dataKey="cpu" stackId="2" stroke="#22d3ee" fillOpacity={1} fill="url(#colorCpu)" name="CPU (%)" />
|
|
90
|
+
</AreaChart>
|
|
91
|
+
</ResponsiveContainer>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div className="mt-2 pt-4 border-t border-white/10 text-[10px] uppercase tracking-widest text-white/30 flex justify-between">
|
|
95
|
+
<span>Kernel-Bridge Open</span>
|
|
96
|
+
<span>Tauri Native FFI</span>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { kernel } from '@decido/kernel-bridge';
|
|
3
|
+
|
|
4
|
+
export const VaultWidget = () => {
|
|
5
|
+
const [constructs, setConstructs] = useState<any[]>([]);
|
|
6
|
+
const [loading, setLoading] = useState(true);
|
|
7
|
+
|
|
8
|
+
const loadConstructs = async () => {
|
|
9
|
+
try {
|
|
10
|
+
setLoading(true);
|
|
11
|
+
const data = await kernel.execute('list_constructs', {});
|
|
12
|
+
setConstructs(data as any[]);
|
|
13
|
+
} catch (e) {
|
|
14
|
+
console.error("Error loading constructs:", e);
|
|
15
|
+
} finally {
|
|
16
|
+
setLoading(false);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
loadConstructs();
|
|
22
|
+
|
|
23
|
+
// Listen to newly materialized constructs
|
|
24
|
+
const unsub = kernel.onEvent((payload: any) => {
|
|
25
|
+
if (payload.event_type === 'notification' && payload.data?.title === '💎 Constructo Materializado') {
|
|
26
|
+
loadConstructs();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return () => unsub();
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
const spawnConstruct = (c: any) => {
|
|
34
|
+
// Enviar evento global que PlaygroundEngine interceptará
|
|
35
|
+
kernel.execute('broadcast_message', {
|
|
36
|
+
intent: "ui:spawn_dynamic_widget",
|
|
37
|
+
payload: JSON.stringify({
|
|
38
|
+
code: c.code,
|
|
39
|
+
name: c.title,
|
|
40
|
+
missionData: {}
|
|
41
|
+
})
|
|
42
|
+
}).catch(e => console.error(e));
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="p-6 h-full text-white bg-black/50 overflow-y-auto">
|
|
47
|
+
<div className="flex justify-between items-center mb-6 border-b border-white/10 pb-4">
|
|
48
|
+
<h2 className="text-2xl font-black tracking-tight bg-gradient-to-r from-emerald-400 to-cyan-400 bg-clip-text text-transparent">
|
|
49
|
+
Construct Vault
|
|
50
|
+
</h2>
|
|
51
|
+
<button
|
|
52
|
+
onClick={loadConstructs}
|
|
53
|
+
className="bg-white/5 border border-white/10 px-4 py-1.5 rounded-full text-xs font-bold hover:bg-white/10 transition-colors"
|
|
54
|
+
>
|
|
55
|
+
REFRESCAR BÓVEDA
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{loading ? (
|
|
60
|
+
<div className="animate-pulse flex space-x-4">
|
|
61
|
+
<div className="flex-1 space-y-4 py-1">
|
|
62
|
+
<div className="h-2 bg-slate-700/50 rounded w-1/4"></div>
|
|
63
|
+
<div className="h-2 bg-slate-700/50 rounded w-1/2"></div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
) : constructs.length === 0 ? (
|
|
67
|
+
<div className="flex flex-col items-center justify-center text-white/40 h-64 border-2 border-dashed border-white/10 rounded-2xl bg-white/5 p-8 text-center">
|
|
68
|
+
<span className="text-4xl mb-4">🗄️</span>
|
|
69
|
+
<h3 className="text-lg font-bold mb-2">Bóveda Vacía</h3>
|
|
70
|
+
<p className="text-sm max-w-sm">No hay constructos materializados todavía. Pídele al agente que diseñe uno y guárdalo usando el botón "MATERIALIZAR CONSTRUCTO".</p>
|
|
71
|
+
</div>
|
|
72
|
+
) : (
|
|
73
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
|
74
|
+
{constructs.map((c, i) => (
|
|
75
|
+
<div
|
|
76
|
+
key={i}
|
|
77
|
+
className="group bg-gradient-to-br from-white/5 to-transparent border border-white/10 p-5 rounded-2xl flex flex-col justify-between hover:border-emerald-500/50 transition-all hover:scale-[1.02] shadow-xl backdrop-blur-sm"
|
|
78
|
+
>
|
|
79
|
+
<div>
|
|
80
|
+
<div className="flex justify-between items-start mb-3">
|
|
81
|
+
<h3 className="text-lg font-black text-emerald-400 leading-tight">{c.title}</h3>
|
|
82
|
+
<span className="text-[10px] text-white/30 font-mono tracking-widest">{new Date(c.timestamp).toLocaleDateString()}</span>
|
|
83
|
+
</div>
|
|
84
|
+
<p className="text-xs text-white/60 mb-6 line-clamp-4 leading-relaxed">{c.description}</p>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<button
|
|
88
|
+
onClick={() => spawnConstruct(c)}
|
|
89
|
+
className="w-full bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 font-bold py-2.5 rounded-xl hover:bg-emerald-500 hover:text-black transition-all shadow-[0_0_15px_rgba(16,185,129,0.1)] group-hover:shadow-[0_0_20px_rgba(16,185,129,0.3)] text-xs uppercase tracking-wider"
|
|
90
|
+
>
|
|
91
|
+
Instanciar Constructo
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
};
|