rax-flow 0.1.7 → 0.1.9
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/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +43 -3
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/components/ChatPanel.d.ts +2 -1
- package/dist/tui/components/ChatPanel.d.ts.map +1 -1
- package/dist/tui/components/ChatPanel.js +3 -2
- package/dist/tui/components/ChatPanel.js.map +1 -1
- package/dist/tui/components/DAGPanel.d.ts +24 -0
- package/dist/tui/components/DAGPanel.d.ts.map +1 -0
- package/dist/tui/components/DAGPanel.js +30 -0
- package/dist/tui/components/DAGPanel.js.map +1 -0
- package/dist/tui/components/Header.d.ts +3 -1
- package/dist/tui/components/Header.d.ts.map +1 -1
- package/dist/tui/components/Header.js +15 -3
- package/dist/tui/components/Header.js.map +1 -1
- package/dist/tui/components/HelpOverlay.d.ts +6 -0
- package/dist/tui/components/HelpOverlay.d.ts.map +1 -0
- package/dist/tui/components/HelpOverlay.js +42 -0
- package/dist/tui/components/HelpOverlay.js.map +1 -0
- package/dist/tui/components/LogsPanel.d.ts +7 -0
- package/dist/tui/components/LogsPanel.d.ts.map +1 -0
- package/dist/tui/components/LogsPanel.js +7 -0
- package/dist/tui/components/LogsPanel.js.map +1 -0
- package/dist/tui/components/MemoryPanel.d.ts +19 -0
- package/dist/tui/components/MemoryPanel.d.ts.map +1 -0
- package/dist/tui/components/MemoryPanel.js +24 -0
- package/dist/tui/components/MemoryPanel.js.map +1 -0
- package/dist/tui/components/MetricsPanel.d.ts +13 -0
- package/dist/tui/components/MetricsPanel.d.ts.map +1 -0
- package/dist/tui/components/MetricsPanel.js +14 -0
- package/dist/tui/components/MetricsPanel.js.map +1 -0
- package/dist/tui/components/StatusPanel.d.ts +2 -1
- package/dist/tui/components/StatusPanel.d.ts.map +1 -1
- package/dist/tui/components/StatusPanel.js +7 -6
- package/dist/tui/components/StatusPanel.js.map +1 -1
- package/dist/tui/hooks/useAppState.d.ts +24 -0
- package/dist/tui/hooks/useAppState.d.ts.map +1 -1
- package/dist/tui/hooks/useAppState.js +194 -43
- package/dist/tui/hooks/useAppState.js.map +1 -1
- package/dist/tui/services/orchestrator.d.ts +16 -0
- package/dist/tui/services/orchestrator.d.ts.map +1 -0
- package/dist/tui/services/orchestrator.js +108 -0
- package/dist/tui/services/orchestrator.js.map +1 -0
- package/package.json +1 -1
- package/src/tui/App.tsx +104 -19
- package/src/tui/components/ChatPanel.tsx +8 -4
- package/src/tui/components/DAGPanel.tsx +116 -0
- package/src/tui/components/Header.tsx +51 -19
- package/src/tui/components/HelpOverlay.tsx +83 -0
- package/src/tui/components/LogsPanel.tsx +37 -0
- package/src/tui/components/MemoryPanel.tsx +99 -0
- package/src/tui/components/MetricsPanel.tsx +108 -0
- package/src/tui/components/StatusPanel.tsx +13 -8
- package/src/tui/hooks/useAppState.ts +259 -44
- package/src/tui/services/orchestrator.ts +142 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Box, Text
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
3
|
|
|
4
4
|
interface Message {
|
|
5
5
|
id: string;
|
|
@@ -12,6 +12,7 @@ interface Message {
|
|
|
12
12
|
interface ChatPanelProps {
|
|
13
13
|
messages: Message[];
|
|
14
14
|
isProcessing: boolean;
|
|
15
|
+
isActive: boolean;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
function formatTime(date: Date): string {
|
|
@@ -22,7 +23,7 @@ function formatTime(date: Date): string {
|
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
function MessageItem({ message }: { message: Message
|
|
26
|
+
function MessageItem({ message }: { message: Message }) {
|
|
26
27
|
const typeColors: Record<string, string> = {
|
|
27
28
|
user: "cyan",
|
|
28
29
|
system: "gray",
|
|
@@ -51,18 +52,21 @@ function MessageItem({ message }: { message: Message; key?: string }) {
|
|
|
51
52
|
);
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
export function ChatPanel({ messages, isProcessing }: ChatPanelProps) {
|
|
55
|
+
export function ChatPanel({ messages, isProcessing, isActive }: ChatPanelProps) {
|
|
56
|
+
const borderColor = isActive ? "#f97316" : "gray";
|
|
57
|
+
|
|
55
58
|
return (
|
|
56
59
|
<Box
|
|
57
60
|
flexDirection="column"
|
|
58
61
|
flexGrow={2}
|
|
59
62
|
borderStyle="single"
|
|
60
|
-
borderColor=
|
|
63
|
+
borderColor={borderColor}
|
|
61
64
|
paddingX={1}
|
|
62
65
|
>
|
|
63
66
|
<Box borderStyle="single" borderColor="#f97316" marginBottom={1}>
|
|
64
67
|
<Text color="#f97316" bold>CHAT</Text>
|
|
65
68
|
<Text color="gray"> — Tapez /help pour les commandes</Text>
|
|
69
|
+
{isActive && <Text color="yellow"> [ACTIF]</Text>}
|
|
66
70
|
</Box>
|
|
67
71
|
<Box flexDirection="column" flexGrow={1} overflow="hidden">
|
|
68
72
|
{messages.slice(-15).map((msg) => (
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
|
|
4
|
+
interface DAGNode {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
status: "pending" | "running" | "done" | "error";
|
|
8
|
+
agent?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface DAGLevel {
|
|
12
|
+
name: string;
|
|
13
|
+
progress: number;
|
|
14
|
+
nodes: DAGNode[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface WorkflowState {
|
|
18
|
+
levels: DAGLevel[];
|
|
19
|
+
currentLevel: number;
|
|
20
|
+
totalProgress: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface DAGPanelProps {
|
|
24
|
+
workflowState: WorkflowState;
|
|
25
|
+
tick: number;
|
|
26
|
+
isActive: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const statusIcons: Record<string, { icon: string; color: string }> = {
|
|
30
|
+
pending: { icon: "○", color: "gray" },
|
|
31
|
+
running: { icon: "▶", color: "yellow" },
|
|
32
|
+
done: { icon: "●", color: "green" },
|
|
33
|
+
error: { icon: "✗", color: "red" },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const spinnerFrames = ["◐", "◓", "◑", "◒"];
|
|
37
|
+
|
|
38
|
+
function ProgressBar({ progress, width = 20 }: { progress: number; width?: number }) {
|
|
39
|
+
const filled = Math.round((progress / 100) * width);
|
|
40
|
+
const empty = width - filled;
|
|
41
|
+
const bar = "█".repeat(filled) + "░".repeat(empty);
|
|
42
|
+
return (
|
|
43
|
+
<Text color={progress >= 100 ? "green" : progress > 0 ? "yellow" : "gray"}>
|
|
44
|
+
{bar}
|
|
45
|
+
</Text>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function DAGNodeItem({ node, tick }: { node: DAGNode; tick: number }) {
|
|
50
|
+
const { icon, color } = statusIcons[node.status];
|
|
51
|
+
const animatedIcon = node.status === "running"
|
|
52
|
+
? spinnerFrames[tick % spinnerFrames.length]
|
|
53
|
+
: icon;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Box flexDirection="row" marginLeft={2}>
|
|
57
|
+
<Text color={color}>{animatedIcon}</Text>
|
|
58
|
+
<Text color="white"> {node.name}</Text>
|
|
59
|
+
{node.agent && <Text color="gray"> [{node.agent}]</Text>}
|
|
60
|
+
</Box>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function DAGLevelView({ level, tick, isActive }: { level: DAGLevel; tick: number; isActive: boolean }) {
|
|
65
|
+
return (
|
|
66
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
67
|
+
<Box flexDirection="row">
|
|
68
|
+
<Text color={isActive ? "#f97316" : "gray"} bold={isActive}>
|
|
69
|
+
{level.name}
|
|
70
|
+
</Text>
|
|
71
|
+
<Text color="gray"> </Text>
|
|
72
|
+
<ProgressBar progress={level.progress} width={12} />
|
|
73
|
+
<Text color="gray"> {level.progress}%</Text>
|
|
74
|
+
</Box>
|
|
75
|
+
{level.nodes.map((node) => (
|
|
76
|
+
<DAGNodeItem key={node.id} node={node} tick={tick} />
|
|
77
|
+
))}
|
|
78
|
+
</Box>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function DAGPanel({ workflowState, tick, isActive }: DAGPanelProps) {
|
|
83
|
+
const borderColor = isActive ? "#f97316" : "gray";
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Box
|
|
87
|
+
flexDirection="column"
|
|
88
|
+
width={40}
|
|
89
|
+
borderStyle="single"
|
|
90
|
+
borderColor={borderColor}
|
|
91
|
+
paddingX={1}
|
|
92
|
+
>
|
|
93
|
+
<Box borderStyle="single" borderColor="#f97316" marginBottom={1}>
|
|
94
|
+
<Text color="#f97316" bold>DAG</Text>
|
|
95
|
+
<Text color="gray"> EXECUTION</Text>
|
|
96
|
+
</Box>
|
|
97
|
+
|
|
98
|
+
<Box flexDirection="column" flexGrow={1}>
|
|
99
|
+
{workflowState.levels.map((level, index) => (
|
|
100
|
+
<DAGLevelView
|
|
101
|
+
key={level.name}
|
|
102
|
+
level={level}
|
|
103
|
+
tick={tick}
|
|
104
|
+
isActive={index === workflowState.currentLevel}
|
|
105
|
+
/>
|
|
106
|
+
))}
|
|
107
|
+
</Box>
|
|
108
|
+
|
|
109
|
+
<Box borderStyle="single" borderColor="gray" marginTop={1}>
|
|
110
|
+
<Text color="gray">Total: </Text>
|
|
111
|
+
<ProgressBar progress={workflowState.totalProgress} width={10} />
|
|
112
|
+
<Text color="white"> {workflowState.totalProgress}%</Text>
|
|
113
|
+
</Box>
|
|
114
|
+
</Box>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -6,9 +6,21 @@ interface HeaderProps {
|
|
|
6
6
|
agentCount: number;
|
|
7
7
|
provider: string;
|
|
8
8
|
status: "ready" | "running" | "error";
|
|
9
|
+
tick: number;
|
|
10
|
+
activePanel: string;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
const spinnerFrames = ["◐", "◓", "◑", "◒"];
|
|
14
|
+
const panelNames: Record<string, string> = {
|
|
15
|
+
chat: "CHAT",
|
|
16
|
+
dag: "DAG",
|
|
17
|
+
status: "STATUS",
|
|
18
|
+
logs: "LOGS",
|
|
19
|
+
metrics: "METRICS",
|
|
20
|
+
memory: "MEMORY",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function Header({ projectName, agentCount, provider, status, tick, activePanel }: HeaderProps) {
|
|
12
24
|
const statusColors = {
|
|
13
25
|
ready: "green",
|
|
14
26
|
running: "yellow",
|
|
@@ -21,32 +33,52 @@ export function Header({ projectName, agentCount, provider, status }: HeaderProp
|
|
|
21
33
|
error: "ERROR",
|
|
22
34
|
};
|
|
23
35
|
|
|
24
|
-
const
|
|
36
|
+
const animatedStatus = status === "running"
|
|
37
|
+
? spinnerFrames[tick % spinnerFrames.length]
|
|
38
|
+
: status === "error" ? "✗" : "●";
|
|
39
|
+
|
|
40
|
+
const panels = ["chat", "dag", "logs", "metrics", "memory"] as const;
|
|
25
41
|
|
|
26
42
|
return (
|
|
27
43
|
<Box
|
|
28
|
-
flexDirection="
|
|
29
|
-
justifyContent="space-between"
|
|
30
|
-
alignItems="center"
|
|
31
|
-
paddingX={1}
|
|
44
|
+
flexDirection="column"
|
|
32
45
|
borderStyle="single"
|
|
33
46
|
borderColor="gray"
|
|
34
47
|
width="100%"
|
|
35
48
|
>
|
|
36
|
-
<Box
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
<Box
|
|
50
|
+
flexDirection="row"
|
|
51
|
+
justifyContent="space-between"
|
|
52
|
+
alignItems="center"
|
|
53
|
+
paddingX={1}
|
|
54
|
+
>
|
|
55
|
+
<Box flexDirection="row" alignItems="center">
|
|
56
|
+
<Text color="#f97316" bold>■</Text>
|
|
57
|
+
<Text color="white" bold> RAX-FLOW</Text>
|
|
58
|
+
<Text color="gray"> HUB</Text>
|
|
59
|
+
</Box>
|
|
60
|
+
<Box flexDirection="row" alignItems="center">
|
|
61
|
+
<Text color="gray">Project:</Text>
|
|
62
|
+
<Text color="cyan"> {projectName}</Text>
|
|
63
|
+
<Text color="gray"> │ Agents:</Text>
|
|
64
|
+
<Text color="green"> {agentCount}/12</Text>
|
|
65
|
+
<Text color="gray"> │ [{provider}]</Text>
|
|
66
|
+
<Text color={statusColors[status]}> {animatedStatus} {statusText[status]}</Text>
|
|
67
|
+
</Box>
|
|
40
68
|
</Box>
|
|
41
|
-
<Box flexDirection="row"
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
69
|
+
<Box flexDirection="row" paddingX={1} justifyContent="space-between">
|
|
70
|
+
<Box flexDirection="row">
|
|
71
|
+
{panels.map((panel) => (
|
|
72
|
+
<Box key={panel} marginRight={1}>
|
|
73
|
+
<Text color={activePanel === panel ? "#f97316" : "gray"} bold={activePanel === panel}>
|
|
74
|
+
[{panelNames[panel]}]
|
|
75
|
+
</Text>
|
|
76
|
+
</Box>
|
|
77
|
+
))}
|
|
78
|
+
</Box>
|
|
79
|
+
<Box flexDirection="row">
|
|
80
|
+
<Text color="gray">Tab: Switch │ ←: Toggle view │ ?: Help │ Ctrl+C: Quit</Text>
|
|
81
|
+
</Box>
|
|
50
82
|
</Box>
|
|
51
83
|
</Box>
|
|
52
84
|
);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
|
|
4
|
+
interface HelpOverlayProps {
|
|
5
|
+
onDismiss: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function HelpOverlay({ onDismiss }: HelpOverlayProps) {
|
|
9
|
+
useInput((input, key) => {
|
|
10
|
+
if (key.escape || input === "q") {
|
|
11
|
+
onDismiss();
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const sections = [
|
|
16
|
+
{
|
|
17
|
+
title: "COMMANDES",
|
|
18
|
+
items: [
|
|
19
|
+
["/run <prompt>", "Exécuter un workflow"],
|
|
20
|
+
["/status", "État du système"],
|
|
21
|
+
["/agents", "Liste des agents"],
|
|
22
|
+
["/providers", "Liste des providers"],
|
|
23
|
+
["/workflows", "Blueprints disponibles"],
|
|
24
|
+
["/help, ?", "Cette aide"],
|
|
25
|
+
["/exit", "Quitter"],
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
title: "NAVIGATION",
|
|
30
|
+
items: [
|
|
31
|
+
["Tab", "Changer de panel actif"],
|
|
32
|
+
["F1-F8", "Aller au panel spécifique"],
|
|
33
|
+
["Ctrl+C", "Quitter"],
|
|
34
|
+
["Esc", "Fermer l'aide"],
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
title: "PANELS",
|
|
39
|
+
items: [
|
|
40
|
+
["CHAT", "Messages et interaction"],
|
|
41
|
+
["DAG", "Visualisation du workflow"],
|
|
42
|
+
["STATUS", "Agents et providers"],
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Box
|
|
49
|
+
flexDirection="column"
|
|
50
|
+
flexGrow={1}
|
|
51
|
+
borderStyle="double"
|
|
52
|
+
borderColor="#f97316"
|
|
53
|
+
paddingX={2}
|
|
54
|
+
>
|
|
55
|
+
<Box marginBottom={1}>
|
|
56
|
+
<Text color="#f97316" bold>■ RAX-FLOW HELP</Text>
|
|
57
|
+
<Text color="gray"> — [Esc] pour fermer</Text>
|
|
58
|
+
</Box>
|
|
59
|
+
|
|
60
|
+
<Box flexDirection="row" flexGrow={1}>
|
|
61
|
+
{sections.map((section) => (
|
|
62
|
+
<Box key={section.title} flexDirection="column" width={30}>
|
|
63
|
+
<Text color="gray" bold underline>
|
|
64
|
+
{section.title}
|
|
65
|
+
</Text>
|
|
66
|
+
<Box flexDirection="column" marginTop={1}>
|
|
67
|
+
{section.items.map(([cmd, desc]) => (
|
|
68
|
+
<Box key={cmd} flexDirection="row" marginBottom={1}>
|
|
69
|
+
<Text color="cyan">{cmd.padEnd(16)}</Text>
|
|
70
|
+
<Text color="white">{desc}</Text>
|
|
71
|
+
</Box>
|
|
72
|
+
))}
|
|
73
|
+
</Box>
|
|
74
|
+
</Box>
|
|
75
|
+
))}
|
|
76
|
+
</Box>
|
|
77
|
+
|
|
78
|
+
<Box borderStyle="single" borderColor="gray" marginTop={1}>
|
|
79
|
+
<Text color="gray">Pour exécuter un workflow, tapez simplement votre prompt ou /run "..."</Text>
|
|
80
|
+
</Box>
|
|
81
|
+
</Box>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
|
|
4
|
+
interface LogsPanelProps {
|
|
5
|
+
logs: string[];
|
|
6
|
+
isActive: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function LogsPanel({ logs, isActive }: LogsPanelProps) {
|
|
10
|
+
const borderColor = isActive ? "#f97316" : "gray";
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<Box
|
|
14
|
+
flexDirection="column"
|
|
15
|
+
flexGrow={1}
|
|
16
|
+
borderStyle="single"
|
|
17
|
+
borderColor={borderColor}
|
|
18
|
+
paddingX={1}
|
|
19
|
+
>
|
|
20
|
+
<Box borderStyle="single" borderColor="#f97316" marginBottom={1}>
|
|
21
|
+
<Text color="#f97316" bold>LOGS</Text>
|
|
22
|
+
<Text color="gray"> STREAM</Text>
|
|
23
|
+
{isActive && <Text color="yellow"> [ACTIF]</Text>}
|
|
24
|
+
</Box>
|
|
25
|
+
<Box flexDirection="column" flexGrow={1} overflow="hidden">
|
|
26
|
+
{logs.slice(-12).map((log, index) => (
|
|
27
|
+
<Text key={index} color="white" wrap="truncate">
|
|
28
|
+
{log}
|
|
29
|
+
</Text>
|
|
30
|
+
))}
|
|
31
|
+
{logs.length === 0 && (
|
|
32
|
+
<Text color="gray">Aucun log. Lancez un workflow pour voir les logs.</Text>
|
|
33
|
+
)}
|
|
34
|
+
</Box>
|
|
35
|
+
</Box>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
|
|
4
|
+
interface MemoryNode {
|
|
5
|
+
id: string;
|
|
6
|
+
type: string;
|
|
7
|
+
label: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface MemoryEdge {
|
|
11
|
+
from: string;
|
|
12
|
+
to: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface MemoryPanelProps {
|
|
16
|
+
nodes: MemoryNode[];
|
|
17
|
+
edges: MemoryEdge[];
|
|
18
|
+
nodeCount: number;
|
|
19
|
+
cacheHitRate: number;
|
|
20
|
+
isActive: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const DEFAULT_NODES: MemoryNode[] = [
|
|
24
|
+
{ id: "1", type: "action", label: "code_gen" },
|
|
25
|
+
{ id: "2", type: "file", label: "auth.ts" },
|
|
26
|
+
{ id: "3", type: "task", label: "login" },
|
|
27
|
+
{ id: "4", type: "test", label: "auth.test" },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const DEFAULT_EDGES: MemoryEdge[] = [
|
|
31
|
+
{ from: "1", to: "2" },
|
|
32
|
+
{ from: "2", to: "3" },
|
|
33
|
+
{ from: "3", to: "4" },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
export function MemoryPanel({ nodes, edges, nodeCount, cacheHitRate, isActive }: MemoryPanelProps) {
|
|
37
|
+
const borderColor = isActive ? "#f97316" : "gray";
|
|
38
|
+
const displayNodes = nodes.length > 0 ? nodes : DEFAULT_NODES;
|
|
39
|
+
const displayEdges = edges.length > 0 ? edges : DEFAULT_EDGES;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Box
|
|
43
|
+
flexDirection="column"
|
|
44
|
+
flexGrow={1}
|
|
45
|
+
borderStyle="single"
|
|
46
|
+
borderColor={borderColor}
|
|
47
|
+
paddingX={1}
|
|
48
|
+
>
|
|
49
|
+
<Box borderStyle="single" borderColor="#f97316" marginBottom={1}>
|
|
50
|
+
<Text color="#f97316" bold>MEMORY</Text>
|
|
51
|
+
<Text color="gray"> QSGM</Text>
|
|
52
|
+
{isActive && <Text color="yellow"> [ACTIF]</Text>}
|
|
53
|
+
</Box>
|
|
54
|
+
|
|
55
|
+
<Box flexDirection="column">
|
|
56
|
+
<Text color="gray" bold>QUANTUM SEMANTIC GRAPH</Text>
|
|
57
|
+
<Box flexDirection="row" marginTop={1}>
|
|
58
|
+
<Text color="white">Nodes: </Text>
|
|
59
|
+
<Text color="cyan" bold>{nodeCount}</Text>
|
|
60
|
+
<Text color="gray"> │ Cache: </Text>
|
|
61
|
+
<Text color="green">{cacheHitRate}%</Text>
|
|
62
|
+
</Box>
|
|
63
|
+
</Box>
|
|
64
|
+
|
|
65
|
+
<Box flexDirection="column" marginTop={1}>
|
|
66
|
+
<Text color="gray" bold>RECENT NODES</Text>
|
|
67
|
+
<Box flexDirection="column" marginTop={1}>
|
|
68
|
+
{displayNodes.slice(0, 5).map((node) => (
|
|
69
|
+
<Box key={node.id} flexDirection="row">
|
|
70
|
+
<Text color="gray">[{node.type}]</Text>
|
|
71
|
+
<Text color="white"> {node.label}</Text>
|
|
72
|
+
</Box>
|
|
73
|
+
))}
|
|
74
|
+
</Box>
|
|
75
|
+
</Box>
|
|
76
|
+
|
|
77
|
+
<Box flexDirection="column" marginTop={1}>
|
|
78
|
+
<Text color="gray" bold>CONNECTIONS</Text>
|
|
79
|
+
<Box flexDirection="column" marginTop={1}>
|
|
80
|
+
{displayEdges.slice(0, 4).map((edge, i) => {
|
|
81
|
+
const fromNode = displayNodes.find((n) => n.id === edge.from);
|
|
82
|
+
const toNode = displayNodes.find((n) => n.id === edge.to);
|
|
83
|
+
return (
|
|
84
|
+
<Box key={i} flexDirection="row">
|
|
85
|
+
<Text color="white">{fromNode?.label || "?"}</Text>
|
|
86
|
+
<Text color="#f97316"> ──▶ </Text>
|
|
87
|
+
<Text color="white">{toNode?.label || "?"}</Text>
|
|
88
|
+
</Box>
|
|
89
|
+
);
|
|
90
|
+
})}
|
|
91
|
+
</Box>
|
|
92
|
+
</Box>
|
|
93
|
+
|
|
94
|
+
<Box marginTop={1}>
|
|
95
|
+
<Text color="gray" dimColor>Mutations this session: +{Math.floor(Math.random() * 4)}</Text>
|
|
96
|
+
</Box>
|
|
97
|
+
</Box>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
|
|
4
|
+
interface MetricsPanelProps {
|
|
5
|
+
metrics: {
|
|
6
|
+
sessions: number;
|
|
7
|
+
avgDuration: number;
|
|
8
|
+
successRate: number;
|
|
9
|
+
totalCost: number;
|
|
10
|
+
};
|
|
11
|
+
fitness: number;
|
|
12
|
+
isActive: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function ProgressBar({ value, max, width = 16 }: { value: number; max: number; width?: number }) {
|
|
16
|
+
const percent = Math.min((value / max) * 100, 100);
|
|
17
|
+
const filled = Math.round((percent / 100) * width);
|
|
18
|
+
const empty = width - filled;
|
|
19
|
+
const bar = "█".repeat(filled) + "░".repeat(empty);
|
|
20
|
+
return (
|
|
21
|
+
<Text color={percent >= 80 ? "green" : percent >= 50 ? "yellow" : "red"}>
|
|
22
|
+
{bar}
|
|
23
|
+
</Text>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function MetricsPanel({ metrics, fitness, isActive }: MetricsPanelProps) {
|
|
28
|
+
const borderColor = isActive ? "#f97316" : "gray";
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Box
|
|
32
|
+
flexDirection="column"
|
|
33
|
+
width={30}
|
|
34
|
+
borderStyle="single"
|
|
35
|
+
borderColor={borderColor}
|
|
36
|
+
paddingX={1}
|
|
37
|
+
>
|
|
38
|
+
<Box borderStyle="single" borderColor="#f97316" marginBottom={1}>
|
|
39
|
+
<Text color="#f97316" bold>METRICS</Text>
|
|
40
|
+
{isActive && <Text color="yellow"> [ACTIF]</Text>}
|
|
41
|
+
</Box>
|
|
42
|
+
|
|
43
|
+
<Box flexDirection="column">
|
|
44
|
+
<Text color="gray" bold>PERFORMANCE</Text>
|
|
45
|
+
|
|
46
|
+
<Box flexDirection="row" marginTop={1}>
|
|
47
|
+
<Text color="white">Sessions: </Text>
|
|
48
|
+
<Text color="cyan" bold>{metrics.sessions}</Text>
|
|
49
|
+
</Box>
|
|
50
|
+
|
|
51
|
+
<Box flexDirection="row">
|
|
52
|
+
<Text color="white">Duration: </Text>
|
|
53
|
+
<Text color="cyan">{metrics.avgDuration}ms</Text>
|
|
54
|
+
</Box>
|
|
55
|
+
|
|
56
|
+
<Box flexDirection="row">
|
|
57
|
+
<Text color="white">Success: </Text>
|
|
58
|
+
<Text color="green">{metrics.successRate}%</Text>
|
|
59
|
+
<Text color="gray"> </Text>
|
|
60
|
+
<ProgressBar value={metrics.successRate} max={100} width={8} />
|
|
61
|
+
</Box>
|
|
62
|
+
|
|
63
|
+
<Box flexDirection="row">
|
|
64
|
+
<Text color="white">Cost: </Text>
|
|
65
|
+
<Text color="yellow">${metrics.totalCost.toFixed(4)}</Text>
|
|
66
|
+
</Box>
|
|
67
|
+
</Box>
|
|
68
|
+
|
|
69
|
+
<Box flexDirection="column" marginTop={1}>
|
|
70
|
+
<Text color="gray" bold>FITNESS</Text>
|
|
71
|
+
<Box flexDirection="row">
|
|
72
|
+
<Text color={fitness >= 0.9 ? "green" : fitness >= 0.7 ? "yellow" : "red"} bold>
|
|
73
|
+
{fitness.toFixed(2)}
|
|
74
|
+
</Text>
|
|
75
|
+
<Text color="gray"> </Text>
|
|
76
|
+
<ProgressBar value={fitness} max={1} width={12} />
|
|
77
|
+
</Box>
|
|
78
|
+
<Text color="gray">
|
|
79
|
+
{fitness >= 0.9 ? "[OPTIMAL]" : fitness >= 0.7 ? "[GOOD]" : "[IMPROVING]"}
|
|
80
|
+
</Text>
|
|
81
|
+
</Box>
|
|
82
|
+
|
|
83
|
+
<Box flexDirection="column" marginTop={1}>
|
|
84
|
+
<Text color="gray" bold>TOP AGENTS</Text>
|
|
85
|
+
<Box flexDirection="column" marginTop={1}>
|
|
86
|
+
<Box flexDirection="row">
|
|
87
|
+
<Text color="white">CodeGenerator</Text>
|
|
88
|
+
<Text color="gray"> </Text>
|
|
89
|
+
<ProgressBar value={45} max={100} width={8} />
|
|
90
|
+
<Text color="gray"> 45%</Text>
|
|
91
|
+
</Box>
|
|
92
|
+
<Box flexDirection="row">
|
|
93
|
+
<Text color="white">TestAgent</Text>
|
|
94
|
+
<Text color="gray"> </Text>
|
|
95
|
+
<ProgressBar value={32} max={100} width={8} />
|
|
96
|
+
<Text color="gray"> 32%</Text>
|
|
97
|
+
</Box>
|
|
98
|
+
<Box flexDirection="row">
|
|
99
|
+
<Text color="white">FixAgent</Text>
|
|
100
|
+
<Text color="gray"> </Text>
|
|
101
|
+
<ProgressBar value={18} max={100} width={8} />
|
|
102
|
+
<Text color="gray"> 18%</Text>
|
|
103
|
+
</Box>
|
|
104
|
+
</Box>
|
|
105
|
+
</Box>
|
|
106
|
+
</Box>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -19,6 +19,7 @@ interface StatusPanelProps {
|
|
|
19
19
|
providers: Provider[];
|
|
20
20
|
fitness: number;
|
|
21
21
|
currentWorkflow: string | null;
|
|
22
|
+
isActive: boolean;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
const statusIcons: Record<string, { icon: string; color: string }> = {
|
|
@@ -28,10 +29,10 @@ const statusIcons: Record<string, { icon: string; color: string }> = {
|
|
|
28
29
|
done: { icon: "●", color: "green" },
|
|
29
30
|
};
|
|
30
31
|
|
|
31
|
-
function AgentRow({ agent
|
|
32
|
+
function AgentRow({ agent }: { agent: Agent }) {
|
|
32
33
|
const { icon, color } = statusIcons[agent.status];
|
|
33
34
|
return (
|
|
34
|
-
<Box
|
|
35
|
+
<Box flexDirection="row" justifyContent="space-between">
|
|
35
36
|
<Text color={color}>{icon}</Text>
|
|
36
37
|
<Text color="white">{agent.name.slice(0, 12).padEnd(12)}</Text>
|
|
37
38
|
<Text color="gray">[{agent.provider}]</Text>
|
|
@@ -39,9 +40,9 @@ function AgentRow({ agent, key }: { agent: Agent; key?: string }) {
|
|
|
39
40
|
);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
function ProviderRow({ provider
|
|
43
|
+
function ProviderRow({ provider }: { provider: Provider }) {
|
|
43
44
|
return (
|
|
44
|
-
<Box
|
|
45
|
+
<Box flexDirection="row" justifyContent="space-between">
|
|
45
46
|
<Text color={provider.status === "active" ? "green" : "gray"}>
|
|
46
47
|
{provider.status === "active" ? "●" : "○"}
|
|
47
48
|
</Text>
|
|
@@ -51,20 +52,24 @@ function ProviderRow({ provider, key }: { provider: Provider; key?: string }) {
|
|
|
51
52
|
);
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
export function StatusPanel({ agents, providers, fitness, currentWorkflow }: StatusPanelProps) {
|
|
55
|
+
export function StatusPanel({ agents, providers, fitness, currentWorkflow, isActive }: StatusPanelProps) {
|
|
55
56
|
const fitnessColor = fitness >= 0.9 ? "green" : fitness >= 0.7 ? "yellow" : "red";
|
|
57
|
+
const borderColor = isActive ? "#f97316" : "gray";
|
|
56
58
|
|
|
57
59
|
return (
|
|
58
|
-
<Box flexDirection="column" width={
|
|
60
|
+
<Box flexDirection="column" width={30} borderStyle="single" borderColor={borderColor}>
|
|
59
61
|
<Box borderStyle="single" borderColor="#f97316" marginBottom={1}>
|
|
60
62
|
<Text color="#f97316" bold>STATUS</Text>
|
|
63
|
+
{isActive && <Text color="yellow"> [ACTIF]</Text>}
|
|
61
64
|
</Box>
|
|
62
65
|
|
|
63
66
|
<Box flexDirection="column" paddingX={1}>
|
|
64
67
|
<Text color="gray" bold>WORKFLOW</Text>
|
|
65
68
|
<Text color="white">{currentWorkflow || "Aucun"}</Text>
|
|
66
|
-
<
|
|
67
|
-
|
|
69
|
+
<Box flexDirection="row">
|
|
70
|
+
<Text color="gray">Fitness: </Text>
|
|
71
|
+
<Text color={fitnessColor} bold>{fitness.toFixed(2)}</Text>
|
|
72
|
+
</Box>
|
|
68
73
|
</Box>
|
|
69
74
|
|
|
70
75
|
<Box flexDirection="column" paddingX={1} marginTop={1}>
|