rax-flow 0.1.9 → 0.2.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.
Files changed (223) hide show
  1. package/dist/bin.js +8 -6
  2. package/dist/bin.js.map +1 -1
  3. package/dist/hub/commands/index.d.ts.map +1 -1
  4. package/dist/hub/commands/index.js +7 -4
  5. package/dist/hub/commands/index.js.map +1 -1
  6. package/dist/hub/event-listener.d.ts +6 -0
  7. package/dist/hub/event-listener.d.ts.map +1 -1
  8. package/dist/hub/event-listener.js +10 -6
  9. package/dist/hub/event-listener.js.map +1 -1
  10. package/dist/hub/index.d.ts.map +1 -1
  11. package/dist/hub/index.js +12 -0
  12. package/dist/hub/index.js.map +1 -1
  13. package/dist/setup/components/ApiKeyInput.d.ts +25 -0
  14. package/dist/setup/components/ApiKeyInput.d.ts.map +1 -0
  15. package/dist/setup/components/ApiKeyInput.js +54 -0
  16. package/dist/setup/components/ApiKeyInput.js.map +1 -0
  17. package/dist/setup/components/AsciiBanner.d.ts +22 -0
  18. package/dist/setup/components/AsciiBanner.d.ts.map +1 -0
  19. package/dist/setup/components/AsciiBanner.js +55 -0
  20. package/dist/setup/components/AsciiBanner.js.map +1 -0
  21. package/dist/setup/components/CliDetector.d.ts +17 -0
  22. package/dist/setup/components/CliDetector.d.ts.map +1 -0
  23. package/dist/setup/components/CliDetector.js +79 -0
  24. package/dist/setup/components/CliDetector.js.map +1 -0
  25. package/dist/setup/components/ModeSelector.d.ts +8 -0
  26. package/dist/setup/components/ModeSelector.d.ts.map +1 -0
  27. package/dist/setup/components/ModeSelector.js +76 -0
  28. package/dist/setup/components/ModeSelector.js.map +1 -0
  29. package/dist/setup/components/ProviderSelector.d.ts +18 -0
  30. package/dist/setup/components/ProviderSelector.d.ts.map +1 -0
  31. package/dist/setup/components/ProviderSelector.js +97 -0
  32. package/dist/setup/components/ProviderSelector.js.map +1 -0
  33. package/dist/setup/components/SetupWizard.d.ts +2 -0
  34. package/dist/setup/components/SetupWizard.d.ts.map +1 -0
  35. package/dist/setup/components/SetupWizard.js +212 -0
  36. package/dist/setup/components/SetupWizard.js.map +1 -0
  37. package/dist/setup/components/StepIndicator.d.ts +13 -0
  38. package/dist/setup/components/StepIndicator.d.ts.map +1 -0
  39. package/dist/setup/components/StepIndicator.js +18 -0
  40. package/dist/setup/components/StepIndicator.js.map +1 -0
  41. package/dist/setup/components/SuccessScreen.d.ts +18 -0
  42. package/dist/setup/components/SuccessScreen.d.ts.map +1 -0
  43. package/dist/setup/components/SuccessScreen.js +38 -0
  44. package/dist/setup/components/SuccessScreen.js.map +1 -0
  45. package/dist/setup/index.d.ts +12 -0
  46. package/dist/setup/index.d.ts.map +1 -0
  47. package/dist/setup/index.js +29 -0
  48. package/dist/setup/index.js.map +1 -0
  49. package/dist/setup/utils/cli-detection.d.ts +12 -0
  50. package/dist/setup/utils/cli-detection.d.ts.map +1 -0
  51. package/dist/setup/utils/cli-detection.js +83 -0
  52. package/dist/setup/utils/cli-detection.js.map +1 -0
  53. package/dist/setup/utils/config-writer.d.ts +43 -0
  54. package/dist/setup/utils/config-writer.d.ts.map +1 -0
  55. package/dist/setup/utils/config-writer.js +180 -0
  56. package/dist/setup/utils/config-writer.js.map +1 -0
  57. package/dist/tui/App.d.ts +2 -1
  58. package/dist/tui/App.d.ts.map +1 -1
  59. package/dist/tui/App.js +78 -18
  60. package/dist/tui/App.js.map +1 -1
  61. package/dist/tui/components/AgentStateIcon.d.ts +18 -0
  62. package/dist/tui/components/AgentStateIcon.d.ts.map +1 -0
  63. package/dist/tui/components/AgentStateIcon.js +57 -0
  64. package/dist/tui/components/AgentStateIcon.js.map +1 -0
  65. package/dist/tui/components/AnimatedBranch.d.ts +39 -0
  66. package/dist/tui/components/AnimatedBranch.d.ts.map +1 -0
  67. package/dist/tui/components/AnimatedBranch.js +64 -0
  68. package/dist/tui/components/AnimatedBranch.js.map +1 -0
  69. package/dist/tui/components/ChatPanel.d.ts +2 -1
  70. package/dist/tui/components/ChatPanel.d.ts.map +1 -1
  71. package/dist/tui/components/ChatPanel.js +47 -28
  72. package/dist/tui/components/ChatPanel.js.map +1 -1
  73. package/dist/tui/components/DAGPanel.d.ts +14 -2
  74. package/dist/tui/components/DAGPanel.d.ts.map +1 -1
  75. package/dist/tui/components/DAGPanel.js +48 -27
  76. package/dist/tui/components/DAGPanel.js.map +1 -1
  77. package/dist/tui/components/ExecutionTimeline.d.ts +34 -0
  78. package/dist/tui/components/ExecutionTimeline.d.ts.map +1 -0
  79. package/dist/tui/components/ExecutionTimeline.js +67 -0
  80. package/dist/tui/components/ExecutionTimeline.js.map +1 -0
  81. package/dist/tui/components/Header.d.ts +2 -1
  82. package/dist/tui/components/Header.d.ts.map +1 -1
  83. package/dist/tui/components/Header.js +23 -20
  84. package/dist/tui/components/Header.js.map +1 -1
  85. package/dist/tui/components/HelpOverlay.d.ts +2 -1
  86. package/dist/tui/components/HelpOverlay.d.ts.map +1 -1
  87. package/dist/tui/components/HelpOverlay.js +59 -41
  88. package/dist/tui/components/HelpOverlay.js.map +1 -1
  89. package/dist/tui/components/InputBar.d.ts +3 -1
  90. package/dist/tui/components/InputBar.d.ts.map +1 -1
  91. package/dist/tui/components/InputBar.js +12 -7
  92. package/dist/tui/components/InputBar.js.map +1 -1
  93. package/dist/tui/components/LogsPanel.d.ts +3 -1
  94. package/dist/tui/components/LogsPanel.d.ts.map +1 -1
  95. package/dist/tui/components/LogsPanel.js +52 -3
  96. package/dist/tui/components/LogsPanel.js.map +1 -1
  97. package/dist/tui/components/MemoryPanel.d.ts +8 -6
  98. package/dist/tui/components/MemoryPanel.d.ts.map +1 -1
  99. package/dist/tui/components/MemoryPanel.js +37 -10
  100. package/dist/tui/components/MemoryPanel.js.map +1 -1
  101. package/dist/tui/components/MetricsPanel.d.ts +12 -7
  102. package/dist/tui/components/MetricsPanel.d.ts.map +1 -1
  103. package/dist/tui/components/MetricsPanel.js +24 -11
  104. package/dist/tui/components/MetricsPanel.js.map +1 -1
  105. package/dist/tui/components/StatusPanel.d.ts +3 -1
  106. package/dist/tui/components/StatusPanel.d.ts.map +1 -1
  107. package/dist/tui/components/StatusPanel.js +20 -18
  108. package/dist/tui/components/StatusPanel.js.map +1 -1
  109. package/dist/tui/components/TaskTree.d.ts +28 -0
  110. package/dist/tui/components/TaskTree.d.ts.map +1 -0
  111. package/dist/tui/components/TaskTree.js +29 -0
  112. package/dist/tui/components/TaskTree.js.map +1 -0
  113. package/dist/tui/components/animations/ProgressBar.d.ts +39 -0
  114. package/dist/tui/components/animations/ProgressBar.d.ts.map +1 -0
  115. package/dist/tui/components/animations/ProgressBar.js +39 -0
  116. package/dist/tui/components/animations/ProgressBar.js.map +1 -0
  117. package/dist/tui/components/animations/Pulse.d.ts +17 -0
  118. package/dist/tui/components/animations/Pulse.d.ts.map +1 -0
  119. package/dist/tui/components/animations/Pulse.js +47 -0
  120. package/dist/tui/components/animations/Pulse.js.map +1 -0
  121. package/dist/tui/components/animations/Spinner.d.ts +13 -0
  122. package/dist/tui/components/animations/Spinner.d.ts.map +1 -0
  123. package/dist/tui/components/animations/Spinner.js +36 -0
  124. package/dist/tui/components/animations/Spinner.js.map +1 -0
  125. package/dist/tui/components/animations/StatusAnimator.d.ts +27 -0
  126. package/dist/tui/components/animations/StatusAnimator.d.ts.map +1 -0
  127. package/dist/tui/components/animations/StatusAnimator.js +85 -0
  128. package/dist/tui/components/animations/StatusAnimator.js.map +1 -0
  129. package/dist/tui/components/animations/TypingEffect.d.ts +26 -0
  130. package/dist/tui/components/animations/TypingEffect.d.ts.map +1 -0
  131. package/dist/tui/components/animations/TypingEffect.js +59 -0
  132. package/dist/tui/components/animations/TypingEffect.js.map +1 -0
  133. package/dist/tui/components/animations/index.d.ts +8 -0
  134. package/dist/tui/components/animations/index.d.ts.map +1 -0
  135. package/dist/tui/components/animations/index.js +6 -0
  136. package/dist/tui/components/animations/index.js.map +1 -0
  137. package/dist/tui/hooks/useAnimation.d.ts +42 -0
  138. package/dist/tui/hooks/useAnimation.d.ts.map +1 -0
  139. package/dist/tui/hooks/useAnimation.js +212 -0
  140. package/dist/tui/hooks/useAnimation.js.map +1 -0
  141. package/dist/tui/hooks/useAppState.d.ts +9 -6
  142. package/dist/tui/hooks/useAppState.d.ts.map +1 -1
  143. package/dist/tui/hooks/useAppState.js +84 -69
  144. package/dist/tui/hooks/useAppState.js.map +1 -1
  145. package/dist/tui/services/orchestrator.d.ts.map +1 -1
  146. package/dist/tui/services/orchestrator.js +47 -3
  147. package/dist/tui/services/orchestrator.js.map +1 -1
  148. package/dist/tui/styles/borders.d.ts +31 -0
  149. package/dist/tui/styles/borders.d.ts.map +1 -0
  150. package/dist/tui/styles/borders.js +47 -0
  151. package/dist/tui/styles/borders.js.map +1 -0
  152. package/dist/tui/styles/colors.d.ts +18 -0
  153. package/dist/tui/styles/colors.d.ts.map +1 -0
  154. package/dist/tui/styles/colors.js +18 -0
  155. package/dist/tui/styles/colors.js.map +1 -0
  156. package/dist/tui/styles/index.d.ts +6 -0
  157. package/dist/tui/styles/index.d.ts.map +1 -0
  158. package/dist/tui/styles/index.js +6 -0
  159. package/dist/tui/styles/index.js.map +1 -0
  160. package/dist/tui/styles/indicators.d.ts +67 -0
  161. package/dist/tui/styles/indicators.d.ts.map +1 -0
  162. package/dist/tui/styles/indicators.js +42 -0
  163. package/dist/tui/styles/indicators.js.map +1 -0
  164. package/dist/tui/styles/layout.d.ts +21 -0
  165. package/dist/tui/styles/layout.d.ts.map +1 -0
  166. package/dist/tui/styles/layout.js +42 -0
  167. package/dist/tui/styles/layout.js.map +1 -0
  168. package/dist/tui/styles/providers.d.ts +77 -0
  169. package/dist/tui/styles/providers.d.ts.map +1 -0
  170. package/dist/tui/styles/providers.js +31 -0
  171. package/dist/tui/styles/providers.js.map +1 -0
  172. package/dist/tui/utils/animation.d.ts +44 -0
  173. package/dist/tui/utils/animation.d.ts.map +1 -0
  174. package/dist/tui/utils/animation.js +107 -0
  175. package/dist/tui/utils/animation.js.map +1 -0
  176. package/package.json +8 -8
  177. package/src/bin.ts +8 -5
  178. package/src/hub/commands/index.ts +7 -4
  179. package/src/hub/event-listener.ts +14 -10
  180. package/src/hub/index.ts +14 -2
  181. package/src/setup/components/ApiKeyInput.tsx +158 -0
  182. package/src/setup/components/AsciiBanner.tsx +125 -0
  183. package/src/setup/components/CliDetector.tsx +230 -0
  184. package/src/setup/components/ModeSelector.tsx +137 -0
  185. package/src/setup/components/ProviderSelector.tsx +174 -0
  186. package/src/setup/components/SetupWizard.tsx +368 -0
  187. package/src/setup/components/StepIndicator.tsx +74 -0
  188. package/src/setup/components/SuccessScreen.tsx +229 -0
  189. package/src/setup/index.ts +34 -0
  190. package/src/setup/utils/cli-detection.ts +99 -0
  191. package/src/setup/utils/config-writer.ts +249 -0
  192. package/src/tui/App.tsx +95 -64
  193. package/src/tui/components/AgentStateIcon.tsx +84 -0
  194. package/src/tui/components/AnimatedBranch.tsx +134 -0
  195. package/src/tui/components/ChatPanel.tsx +85 -43
  196. package/src/tui/components/DAGPanel.tsx +150 -58
  197. package/src/tui/components/ExecutionTimeline.tsx +225 -0
  198. package/src/tui/components/Header.tsx +67 -43
  199. package/src/tui/components/HelpOverlay.tsx +118 -61
  200. package/src/tui/components/InputBar.tsx +33 -19
  201. package/src/tui/components/LogsPanel.tsx +106 -14
  202. package/src/tui/components/MemoryPanel.tsx +106 -42
  203. package/src/tui/components/MetricsPanel.tsx +102 -61
  204. package/src/tui/components/StatusPanel.tsx +84 -37
  205. package/src/tui/components/TaskTree.tsx +159 -0
  206. package/src/tui/components/animations/ProgressBar.tsx +160 -0
  207. package/src/tui/components/animations/Pulse.tsx +73 -0
  208. package/src/tui/components/animations/Spinner.tsx +54 -0
  209. package/src/tui/components/animations/StatusAnimator.tsx +153 -0
  210. package/src/tui/components/animations/TypingEffect.tsx +119 -0
  211. package/src/tui/components/animations/index.ts +16 -0
  212. package/src/tui/hooks/useAnimation.ts +290 -0
  213. package/src/tui/hooks/useAppState.ts +52 -32
  214. package/src/tui/services/orchestrator.ts +57 -4
  215. package/src/tui/styles/borders.ts +51 -0
  216. package/src/tui/styles/colors.ts +19 -0
  217. package/src/tui/styles/index.ts +20 -0
  218. package/src/tui/styles/indicators.ts +54 -0
  219. package/src/tui/styles/layout.ts +44 -0
  220. package/src/tui/styles/providers.ts +32 -0
  221. package/src/tui/utils/animation.ts +124 -0
  222. package/LICENSE +0 -21
  223. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,290 @@
1
+ import { useState, useEffect, useCallback, useRef } from "react";
2
+
3
+ export interface AnimationOptions {
4
+ interval?: number;
5
+ loop?: boolean;
6
+ autoStart?: boolean;
7
+ totalFrames?: number;
8
+ maxFps?: number;
9
+ pauseOnHidden?: boolean;
10
+ }
11
+
12
+ export interface AnimationState {
13
+ frame: number;
14
+ tick: number;
15
+ isAnimating: boolean;
16
+ progress: number;
17
+ start: () => void;
18
+ stop: () => void;
19
+ reset: () => void;
20
+ toggle: () => void;
21
+ }
22
+
23
+ const DEFAULT_FPS_LIMIT = 24;
24
+ const FRAME_SKIP_THRESHOLD = 2;
25
+
26
+ export function useAnimation(options: AnimationOptions = {}): AnimationState {
27
+ const {
28
+ interval = 100,
29
+ loop = true,
30
+ autoStart = true,
31
+ totalFrames = 100,
32
+ maxFps = DEFAULT_FPS_LIMIT,
33
+ pauseOnHidden = true,
34
+ } = options;
35
+
36
+ const [frame, setFrame] = useState(0);
37
+ const [isAnimating, setIsAnimating] = useState(autoStart);
38
+ const intervalRef = useRef<NodeJS.Timeout | null>(null);
39
+ const lastFrameTimeRef = useRef<number>(0);
40
+ const frameCountRef = useRef<number>(0);
41
+ const isHiddenRef = useRef<boolean>(false);
42
+
43
+ useEffect(() => {
44
+ if (!pauseOnHidden || typeof document === "undefined") return;
45
+
46
+ const handleVisibility = () => {
47
+ isHiddenRef.current = document.hidden;
48
+ };
49
+
50
+ document.addEventListener("visibilitychange", handleVisibility);
51
+ return () => document.removeEventListener("visibilitychange", handleVisibility);
52
+ }, [pauseOnHidden]);
53
+
54
+ const start = useCallback(() => {
55
+ setIsAnimating(true);
56
+ }, []);
57
+
58
+ const stop = useCallback(() => {
59
+ setIsAnimating(false);
60
+ }, []);
61
+
62
+ const reset = useCallback(() => {
63
+ setFrame(0);
64
+ frameCountRef.current = 0;
65
+ lastFrameTimeRef.current = 0;
66
+ }, []);
67
+
68
+ const toggle = useCallback(() => {
69
+ setIsAnimating((prev) => !prev);
70
+ }, []);
71
+
72
+ useEffect(() => {
73
+ if (!isAnimating) {
74
+ if (intervalRef.current) {
75
+ clearInterval(intervalRef.current);
76
+ intervalRef.current = null;
77
+ }
78
+ return;
79
+ }
80
+
81
+ const minFrameTime = 1000 / maxFps;
82
+
83
+ intervalRef.current = setInterval(() => {
84
+ if (pauseOnHidden && isHiddenRef.current) return;
85
+
86
+ const now = performance.now();
87
+ const elapsed = now - lastFrameTimeRef.current;
88
+
89
+ if (elapsed < minFrameTime) return;
90
+
91
+ const framesToSkip = Math.floor(elapsed / interval) - 1;
92
+ const shouldSkipFrame = framesToSkip > FRAME_SKIP_THRESHOLD;
93
+
94
+ if (shouldSkipFrame) {
95
+ frameCountRef.current += framesToSkip;
96
+ }
97
+
98
+ lastFrameTimeRef.current = now;
99
+ frameCountRef.current++;
100
+
101
+ setFrame((prev) => {
102
+ if (loop) {
103
+ return prev + 1;
104
+ }
105
+ return prev >= totalFrames - 1 ? prev : prev + 1;
106
+ });
107
+ }, interval);
108
+
109
+ return () => {
110
+ if (intervalRef.current) {
111
+ clearInterval(intervalRef.current);
112
+ intervalRef.current = null;
113
+ }
114
+ };
115
+ }, [isAnimating, interval, loop, totalFrames, maxFps, pauseOnHidden]);
116
+
117
+ const progress = totalFrames > 0 ? (frame % totalFrames) / totalFrames : 0;
118
+
119
+ return {
120
+ frame,
121
+ tick: frame,
122
+ isAnimating,
123
+ progress,
124
+ start,
125
+ stop,
126
+ reset,
127
+ toggle,
128
+ };
129
+ }
130
+
131
+ export function usePulse(interval: number = 2000): { intensity: number; frame: number } {
132
+ const { frame } = useAnimation({ interval: 100, loop: true, maxFps: 15 });
133
+ const intensity = 0.5 + Math.sin((frame * 100 / interval) * Math.PI * 2) * 0.5;
134
+ return { intensity, frame };
135
+ }
136
+
137
+ export function useTypewriter(
138
+ text: string,
139
+ speed: number = 50,
140
+ startDelay: number = 0
141
+ ): { displayedText: string; isComplete: boolean; restart: () => void } {
142
+ const [displayedChars, setDisplayedChars] = useState(0);
143
+ const [isStarted, setIsStarted] = useState(false);
144
+ const [isComplete, setIsComplete] = useState(false);
145
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
146
+
147
+ const restart = useCallback(() => {
148
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
149
+ setDisplayedChars(0);
150
+ setIsStarted(false);
151
+ setIsComplete(false);
152
+ }, []);
153
+
154
+ useEffect(() => {
155
+ restart();
156
+ }, [text, restart]);
157
+
158
+ useEffect(() => {
159
+ if (startDelay > 0 && !isStarted) {
160
+ const timeout = setTimeout(() => setIsStarted(true), startDelay);
161
+ return () => clearTimeout(timeout);
162
+ } else if (startDelay === 0) {
163
+ setIsStarted(true);
164
+ }
165
+ }, [startDelay, isStarted]);
166
+
167
+ useEffect(() => {
168
+ if (!isStarted || isComplete) return;
169
+
170
+ if (displayedChars >= text.length) {
171
+ setIsComplete(true);
172
+ return;
173
+ }
174
+
175
+ timeoutRef.current = setTimeout(() => {
176
+ setDisplayedChars((prev) => prev + 1);
177
+ }, speed);
178
+
179
+ return () => {
180
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
181
+ };
182
+ }, [isStarted, displayedChars, text.length, speed, isComplete]);
183
+
184
+ return {
185
+ displayedText: text.slice(0, displayedChars),
186
+ isComplete,
187
+ restart,
188
+ };
189
+ }
190
+
191
+ export function useCountdown(
192
+ initialSeconds: number,
193
+ options: { autoStart?: boolean; onComplete?: () => void } = {}
194
+ ): { seconds: number; isRunning: boolean; start: () => void; pause: () => void; reset: () => void } {
195
+ const { autoStart = false, onComplete } = options;
196
+ const [seconds, setSeconds] = useState(initialSeconds);
197
+ const [isRunning, setIsRunning] = useState(autoStart);
198
+ const onCompleteRef = useRef(onComplete);
199
+ const intervalRef = useRef<NodeJS.Timeout | null>(null);
200
+ onCompleteRef.current = onComplete;
201
+
202
+ const start = useCallback(() => setIsRunning(true), []);
203
+ const pause = useCallback(() => setIsRunning(false), []);
204
+ const reset = useCallback(() => {
205
+ setSeconds(initialSeconds);
206
+ setIsRunning(false);
207
+ }, [initialSeconds]);
208
+
209
+ useEffect(() => {
210
+ if (!isRunning || seconds <= 0) {
211
+ if (seconds <= 0 && isRunning) {
212
+ setIsRunning(false);
213
+ onCompleteRef.current?.();
214
+ }
215
+ return;
216
+ }
217
+
218
+ intervalRef.current = setInterval(() => {
219
+ setSeconds((prev) => prev - 1);
220
+ }, 1000);
221
+
222
+ return () => {
223
+ if (intervalRef.current) clearInterval(intervalRef.current);
224
+ };
225
+ }, [isRunning, seconds]);
226
+
227
+ return { seconds, isRunning, start, pause, reset };
228
+ }
229
+
230
+ export function useFps(targetFps: number = 24): number {
231
+ const [frame, setFrame] = useState(0);
232
+ const frameRef = useRef(0);
233
+ const lastTimeRef = useRef(performance.now());
234
+ const rafIdRef = useRef<number | null>(null);
235
+
236
+ useEffect(() => {
237
+ const interval = 1000 / targetFps;
238
+
239
+ const tick = () => {
240
+ const now = performance.now();
241
+ const delta = now - lastTimeRef.current;
242
+
243
+ if (delta >= interval) {
244
+ lastTimeRef.current = now - (delta % interval);
245
+ frameRef.current++;
246
+ setFrame(frameRef.current);
247
+ }
248
+
249
+ rafIdRef.current = requestAnimationFrame(tick);
250
+ };
251
+
252
+ rafIdRef.current = requestAnimationFrame(tick);
253
+ return () => {
254
+ if (rafIdRef.current) cancelAnimationFrame(rafIdRef.current);
255
+ };
256
+ }, [targetFps]);
257
+
258
+ return frame;
259
+ }
260
+
261
+ export function useDebouncedCallback<T extends (...args: unknown[]) => void>(
262
+ callback: T,
263
+ delay: number
264
+ ): T {
265
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
266
+ const callbackRef = useRef(callback);
267
+ callbackRef.current = callback;
268
+
269
+ return useCallback(((...args: Parameters<T>) => {
270
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
271
+ timeoutRef.current = setTimeout(() => callbackRef.current(...args), delay);
272
+ }) as T, [delay]);
273
+ }
274
+
275
+ export function useThrottledCallback<T extends (...args: unknown[]) => void>(
276
+ callback: T,
277
+ delay: number
278
+ ): T {
279
+ const lastCallRef = useRef<number>(0);
280
+ const callbackRef = useRef(callback);
281
+ callbackRef.current = callback;
282
+
283
+ return useCallback(((...args: Parameters<T>) => {
284
+ const now = performance.now();
285
+ if (now - lastCallRef.current >= delay) {
286
+ lastCallRef.current = now;
287
+ callbackRef.current(...args);
288
+ }
289
+ }) as T, [delay]);
290
+ }
@@ -41,6 +41,15 @@ interface WorkflowState {
41
41
  totalProgress: number;
42
42
  }
43
43
 
44
+ interface MetricsState {
45
+ sessions: number;
46
+ avgDuration: number;
47
+ successRate: number;
48
+ totalCost: number;
49
+ totalRetries: number;
50
+ totalEscalations: number;
51
+ }
52
+
44
53
  interface AppState {
45
54
  projectName: string;
46
55
  agentCount: number;
@@ -55,12 +64,7 @@ interface AppState {
55
64
  isProcessing: boolean;
56
65
  workflowState: WorkflowState;
57
66
  logs: string[];
58
- metrics: {
59
- sessions: number;
60
- avgDuration: number;
61
- successRate: number;
62
- totalCost: number;
63
- };
67
+ metrics: MetricsState;
64
68
  }
65
69
 
66
70
  const COMMAND_SUGGESTIONS = [
@@ -150,8 +154,10 @@ export function useAppState() {
150
154
  metrics: {
151
155
  sessions: 0,
152
156
  avgDuration: 0,
153
- successRate: 96,
157
+ successRate: 100,
154
158
  totalCost: 0,
159
+ totalRetries: 0,
160
+ totalEscalations: 0,
155
161
  },
156
162
  });
157
163
 
@@ -205,17 +211,30 @@ export function useAppState() {
205
211
 
206
212
  case "run_end":
207
213
  addLog(`[${timeStr}] [DONE] Task completed (confidence: ${event.metrics.confidence.toFixed(2)})`);
208
- setState((prev) => ({
209
- ...prev,
210
- status: "ready",
211
- isProcessing: false,
212
- fitness: Math.min(prev.fitness + 0.02, 0.99),
213
- metrics: {
214
- ...prev.metrics,
215
- sessions: prev.metrics.sessions + 1,
216
- totalCost: prev.metrics.totalCost + (event.metrics.totalCostUsd || 0),
217
- },
218
- }));
214
+ setState((prev) => {
215
+ const newSessions = prev.metrics.sessions + 1;
216
+ const newAvgDuration = prev.metrics.avgDuration === 0
217
+ ? event.metrics.totalLatencyMs
218
+ : Math.round((prev.metrics.avgDuration * prev.metrics.sessions + event.metrics.totalLatencyMs) / newSessions);
219
+ const newSuccessRate = event.metrics.confidence >= 0.7
220
+ ? Math.round((prev.metrics.successRate * prev.metrics.sessions + 100) / newSessions)
221
+ : Math.round((prev.metrics.successRate * prev.metrics.sessions) / newSessions);
222
+
223
+ return {
224
+ ...prev,
225
+ status: "ready",
226
+ isProcessing: false,
227
+ fitness: Math.min(prev.fitness + 0.02, 0.99),
228
+ metrics: {
229
+ sessions: newSessions,
230
+ avgDuration: newAvgDuration,
231
+ successRate: newSuccessRate,
232
+ totalCost: prev.metrics.totalCost + (event.metrics.totalCostUsd || 0),
233
+ totalRetries: prev.metrics.totalRetries + (event.metrics.retries || 0),
234
+ totalEscalations: prev.metrics.totalEscalations + (event.metrics.escalations || 0),
235
+ },
236
+ };
237
+ });
219
238
  break;
220
239
  }
221
240
  }, []);
@@ -288,16 +307,16 @@ export function useAppState() {
288
307
  }, []);
289
308
 
290
309
  const processCommand = useCallback((input: string) => {
291
- if (input.startsWith("/")) {
292
- const cmd = input.slice(1).toLowerCase();
293
- const parts = cmd.split(" ");
294
- const command = parts[0];
295
- const args = parts.slice(1).join(" ");
296
-
297
- switch (command) {
298
- case "help":
299
- addMessage("system", `Commandes: /run /status /agents /providers /workflows /logs /metrics /memory /doctor /bridge-test /evolve /config /exit`);
300
- break;
310
+ const normalizedInput = input.startsWith("/") ? input : `/${input}`;
311
+ const cmd = normalizedInput.slice(1).toLowerCase();
312
+ const parts = cmd.split(" ");
313
+ const command = parts[0];
314
+ const args = parts.slice(1).join(" ");
315
+
316
+ switch (command) {
317
+ case "help":
318
+ addMessage("system", `Commandes: /run /status /agents /providers /workflows /logs /metrics /memory /doctor /bridge-test /evolve /config /exit\n(Note: commands work with or without '/' prefix)`);
319
+ break;
301
320
 
302
321
  case "status":
303
322
  const status = orchestratorRef.current.getStatus();
@@ -353,11 +372,12 @@ export function useAppState() {
353
372
  break;
354
373
 
355
374
  default:
356
- addMessage("error", `Commande inconnue: /${command}. Tapez /help pour l'aide.`);
375
+ if (args || command) {
376
+ addMessage("error", `Commande inconnue: /${command}. Tapez /help pour l'aide.`);
377
+ } else {
378
+ runWorkflow(input);
379
+ }
357
380
  }
358
- } else {
359
- runWorkflow(input);
360
- }
361
381
  }, [state, addMessage]);
362
382
 
363
383
  const runWorkflow = useCallback(async (prompt: string) => {
@@ -9,6 +9,13 @@ import {
9
9
  ModelResponse,
10
10
  ProviderCallOptions,
11
11
  } from "rax-flow-core";
12
+ import {
13
+ OpenAIAdapter,
14
+ ClaudeAdapter,
15
+ GeminiAdapter,
16
+ GroqAdapter,
17
+ HostBridgeAdapter,
18
+ } from "rax-flow-providers";
12
19
 
13
20
  export interface OrchestratorService {
14
21
  run(prompt: string): Promise<void>;
@@ -24,6 +31,36 @@ export interface OrchestratorStatus {
24
31
  fitness: number;
25
32
  }
26
33
 
34
+ function createProviderFromEnv(name: string): IModelProvider | null {
35
+ switch (name) {
36
+ case "openai":
37
+ if (process.env.OPENAI_API_KEY) {
38
+ return new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY });
39
+ }
40
+ return null;
41
+ case "claude":
42
+ case "anthropic":
43
+ if (process.env.ANTHROPIC_API_KEY) {
44
+ return new ClaudeAdapter({ apiKey: process.env.ANTHROPIC_API_KEY });
45
+ }
46
+ return null;
47
+ case "gemini":
48
+ if (process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY) {
49
+ return new GeminiAdapter({ apiKey: process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY! });
50
+ }
51
+ return null;
52
+ case "groq":
53
+ if (process.env.GROQ_API_KEY) {
54
+ return new GroqAdapter({ apiKey: process.env.GROQ_API_KEY });
55
+ }
56
+ return null;
57
+ case "host":
58
+ return new HostBridgeAdapter({ mode: "auto" });
59
+ default:
60
+ return null;
61
+ }
62
+ }
63
+
27
64
  function createMockProvider(): IModelProvider {
28
65
  return {
29
66
  async callModel(prompt: string, options?: ProviderCallOptions): Promise<ModelResponse<string>> {
@@ -49,6 +86,25 @@ function createMockProvider(): IModelProvider {
49
86
  };
50
87
  }
51
88
 
89
+ function buildProviders(): Record<string, IModelProvider> {
90
+ const providers: Record<string, IModelProvider> = {};
91
+
92
+ const hostProvider = createProviderFromEnv("host");
93
+ if (hostProvider) providers["host"] = hostProvider;
94
+
95
+ for (const name of ["openai", "claude", "gemini", "groq"]) {
96
+ const provider = createProviderFromEnv(name);
97
+ if (provider) providers[name] = provider;
98
+ }
99
+
100
+ if (Object.keys(providers).length === 0) {
101
+ console.log("[Orchestrator] No API keys found, using mock provider");
102
+ providers["mock"] = createMockProvider();
103
+ }
104
+
105
+ return providers;
106
+ }
107
+
52
108
  function createAgent(name: string, role: string): AgentDefinition {
53
109
  return {
54
110
  name,
@@ -83,10 +139,7 @@ const DEFAULT_AGENTS = [
83
139
  ];
84
140
 
85
141
  export function createOrchestrator(): OrchestratorService {
86
- const providers: Record<string, IModelProvider> = {
87
- host: createMockProvider(),
88
- };
89
-
142
+ const providers = buildProviders();
90
143
  const agents: Record<string, AgentDefinition> = {};
91
144
  for (const agent of DEFAULT_AGENTS) {
92
145
  agents[agent.name] = agent;
@@ -0,0 +1,51 @@
1
+ export const borders = {
2
+ horizontal: "─",
3
+ vertical: "│",
4
+ topLeft: "┌",
5
+ topRight: "┐",
6
+ bottomLeft: "└",
7
+ bottomRight: "┘",
8
+ leftT: "├",
9
+ rightT: "┤",
10
+ topT: "┬",
11
+ bottomT: "┴",
12
+ cross: "┼",
13
+ };
14
+
15
+ export const doubleBorders = {
16
+ horizontal: "═",
17
+ vertical: "║",
18
+ topLeft: "╔",
19
+ topRight: "╗",
20
+ bottomLeft: "╚",
21
+ bottomRight: "╝",
22
+ };
23
+
24
+ export const treeChars = {
25
+ branch: "├─",
26
+ lastBranch: "└─",
27
+ vertical: "│",
28
+ indent: " ",
29
+ };
30
+
31
+ export function drawTopBorder(width: number, title?: string): string {
32
+ const { topLeft, topRight, horizontal } = borders;
33
+ if (title) {
34
+ const titleStr = ` ${title} `;
35
+ const titleLen = titleStr.length;
36
+ const remaining = width - 2 - titleLen;
37
+ const rightDashes = Math.max(0, remaining);
38
+ return `${topLeft}${titleStr}${horizontal.repeat(rightDashes)}${topRight}`;
39
+ }
40
+ return `${topLeft}${horizontal.repeat(width - 2)}${topRight}`;
41
+ }
42
+
43
+ export function drawBottomBorder(width: number): string {
44
+ const { bottomLeft, bottomRight, horizontal } = borders;
45
+ return `${bottomLeft}${horizontal.repeat(width - 2)}${bottomRight}`;
46
+ }
47
+
48
+ export function drawSeparator(width: number): string {
49
+ const { leftT, rightT, horizontal } = borders;
50
+ return `${leftT}${horizontal.repeat(width - 2)}${rightT}`;
51
+ }
@@ -0,0 +1,19 @@
1
+ import { colors } from "../../hub/styles/colors.js";
2
+
3
+ export { colors };
4
+
5
+ export const tuiColors = {
6
+ background: "#050505",
7
+ surface: "#0a0a0a",
8
+ primary: "#f97316",
9
+ textPrimary: "#ffffff",
10
+ textSecondary: "#a1a1aa",
11
+ textTertiary: "#71717a",
12
+ textQuaternary: "#3f3f46",
13
+ success: "#22c55e",
14
+ warning: "#f59e0b",
15
+ error: "#ef4444",
16
+ border: "#27272a",
17
+ mutation: "#f59e0b",
18
+ checkpoint: "#f97316",
19
+ };
@@ -0,0 +1,20 @@
1
+ export { colors, tuiColors } from "./colors.js";
2
+ export { borders, doubleBorders, treeChars, drawTopBorder, drawBottomBorder, drawSeparator } from "./borders.js";
3
+ export {
4
+ statusIndicators,
5
+ getStatusIndicator,
6
+ spinnerFrames,
7
+ getSpinnerFrame,
8
+ dotsSpinnerFrames,
9
+ circleSpinnerFrames,
10
+ pulseFrames,
11
+ breathFrames,
12
+ progressMarkerFrames,
13
+ progressGlowFrames,
14
+ getPulseFrame,
15
+ getBreathFrame,
16
+ getProgressMarker,
17
+ getProgressGlow
18
+ } from "./indicators.js";
19
+ export { providerTags, getProviderTag, logStatusTags } from "./providers.js";
20
+ export { layout, formatTimestamp, formatTimestampMs, padRight, truncate } from "./layout.js";
@@ -0,0 +1,54 @@
1
+ export const statusIndicators = {
2
+ online: { icon: "●", color: "#22c55e" },
3
+ offline: { icon: "○", color: "#71717a" },
4
+ pending: { icon: "○", color: "#71717a" },
5
+ running: { icon: "▶", color: "#f97316" },
6
+ loading: { icon: "◐", color: "#f59e0b" },
7
+ queued: { icon: "◐", color: "#f59e0b" },
8
+ done: { icon: "●", color: "#22c55e" },
9
+ success: { icon: "●", color: "#22c55e" },
10
+ error: { icon: "✗", color: "#ef4444" },
11
+ mutation: { icon: "◆", color: "#f59e0b" },
12
+ checkpoint: { icon: "■", color: "#f97316" },
13
+ idle: { icon: "○", color: "#71717a" },
14
+ };
15
+
16
+ export function getStatusIndicator(status: string): { icon: string; color: string } {
17
+ const normalized = status.toLowerCase();
18
+ return statusIndicators[normalized as keyof typeof statusIndicators] || statusIndicators.pending;
19
+ }
20
+
21
+ export const spinnerFrames = ["◐", "◓", "◑", "◒"];
22
+
23
+ export const dotsSpinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⼸", "⠴", "⠦", "⠧", "⠇", "⠏"];
24
+
25
+ export const circleSpinnerFrames = ["◐", "◓", "◑", "◒"];
26
+
27
+ export const pulseFrames = ["●", "◐", "○", "◐"];
28
+
29
+ export const breathFrames = ["●", "●", "◐", "◐", "○", "○", "◐", "◐"];
30
+
31
+ export const progressMarkerFrames = ["▶", "▸", "▶", "▸"];
32
+
33
+ export const progressGlowFrames = ["█", "▓", "█", "▓"];
34
+
35
+ export function getSpinnerFrame(tick: number, type: "circle" | "dots" | "pulse" = "circle"): string {
36
+ const frames = type === "dots" ? dotsSpinnerFrames : type === "pulse" ? pulseFrames : circleSpinnerFrames;
37
+ return frames[tick % frames.length];
38
+ }
39
+
40
+ export function getPulseFrame(tick: number): string {
41
+ return pulseFrames[tick % pulseFrames.length];
42
+ }
43
+
44
+ export function getBreathFrame(tick: number): string {
45
+ return breathFrames[tick % breathFrames.length];
46
+ }
47
+
48
+ export function getProgressMarker(tick: number): string {
49
+ return progressMarkerFrames[tick % progressMarkerFrames.length];
50
+ }
51
+
52
+ export function getProgressGlow(tick: number): string {
53
+ return progressGlowFrames[tick % progressGlowFrames.length];
54
+ }
@@ -0,0 +1,44 @@
1
+ export const layout = {
2
+ defaultWidth: 80,
3
+ panelWidth: {
4
+ chat: 40,
5
+ dag: 38,
6
+ status: 28,
7
+ logs: 32,
8
+ metrics: 28,
9
+ memory: 30,
10
+ },
11
+ padding: {
12
+ x: 1,
13
+ y: 0,
14
+ },
15
+ progressWidth: 16,
16
+ };
17
+
18
+ export function formatTimestamp(date: Date): string {
19
+ return date.toLocaleTimeString("fr-FR", {
20
+ hour: "2-digit",
21
+ minute: "2-digit",
22
+ second: "2-digit",
23
+ });
24
+ }
25
+
26
+ export function formatTimestampMs(date: Date): string {
27
+ const time = date.toLocaleTimeString("fr-FR", {
28
+ hour: "2-digit",
29
+ minute: "2-digit",
30
+ second: "2-digit",
31
+ });
32
+ const ms = String(date.getMilliseconds()).padStart(3, "0");
33
+ return `${time}.${ms}`;
34
+ }
35
+
36
+ export function padRight(text: string, width: number): string {
37
+ const padding = Math.max(0, width - text.length);
38
+ return text + " ".repeat(padding);
39
+ }
40
+
41
+ export function truncate(text: string, maxLength: number): string {
42
+ if (text.length <= maxLength) return text;
43
+ return text.slice(0, maxLength - 3) + "...";
44
+ }