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
package/src/tui/App.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useRef } from "react";
1
+ import React, { useState, useEffect, useRef, useMemo, useCallback } from "react";
2
2
  import { Box, Text, useApp, useInput } from "ink";
3
3
  import { Header } from "./components/Header.js";
4
4
  import { ChatPanel } from "./components/ChatPanel.js";
@@ -14,33 +14,43 @@ import { useAppState } from "./hooks/useAppState.js";
14
14
  type ActivePanel = "chat" | "dag" | "status" | "logs" | "metrics" | "memory";
15
15
 
16
16
  const PANEL_ORDER: ActivePanel[] = ["chat", "dag", "status", "logs", "metrics", "memory"];
17
+ const TICK_INTERVAL = 200;
17
18
 
18
- export function App() {
19
+ export const App = React.memo(function App() {
19
20
  const { exit } = useApp();
20
21
  const { state, processCommand } = useAppState();
21
22
  const [activePanel, setActivePanel] = useState<ActivePanel>("chat");
22
23
  const [showHelp, setShowHelp] = useState(false);
23
24
  const [tick, setTick] = useState(0);
24
25
  const [viewMode, setViewMode] = useState<"full" | "compact">("full");
26
+ const intervalRef = useRef<NodeJS.Timeout | null>(null);
25
27
 
26
28
  useEffect(() => {
27
- const interval = setInterval(() => {
29
+ intervalRef.current = setInterval(() => {
28
30
  setTick((t) => t + 1);
29
- }, 100);
30
- return () => clearInterval(interval);
31
+ }, TICK_INTERVAL);
32
+ return () => {
33
+ if (intervalRef.current) clearInterval(intervalRef.current);
34
+ };
35
+ }, []);
36
+
37
+ const handleTab = useCallback(() => {
38
+ setActivePanel((current) => {
39
+ const currentIndex = PANEL_ORDER.indexOf(current);
40
+ return PANEL_ORDER[(currentIndex + 1) % PANEL_ORDER.length];
41
+ });
31
42
  }, []);
32
43
 
33
44
  useInput((input, key) => {
34
- if (key.ctrl && input === "c") {
35
- exit();
36
- }
37
- if (key.ctrl && input === "d") {
45
+ if (key.ctrl && (input === "c" || input === "d")) {
38
46
  exit();
47
+ return;
39
48
  }
40
49
  if (key.escape) {
41
50
  if (showHelp) {
42
51
  setShowHelp(false);
43
52
  }
53
+ return;
44
54
  }
45
55
  if (input === "?" && !showHelp) {
46
56
  setShowHelp(true);
@@ -49,17 +59,16 @@ export function App() {
49
59
  if (showHelp) return;
50
60
 
51
61
  if (key.tab) {
52
- const currentIndex = PANEL_ORDER.indexOf(activePanel);
53
- const nextIndex = (currentIndex + 1) % PANEL_ORDER.length;
54
- setActivePanel(PANEL_ORDER[nextIndex]);
62
+ handleTab();
63
+ return;
55
64
  }
56
65
 
57
66
  if (key.leftArrow) {
58
- setViewMode(viewMode === "full" ? "compact" : "full");
67
+ setViewMode((v) => (v === "full" ? "compact" : "full"));
59
68
  }
60
69
  });
61
70
 
62
- const handleSubmit = (input: string) => {
71
+ const handleSubmit = useCallback((input: string) => {
63
72
  const trimmed = input.trim();
64
73
  if (!trimmed) return;
65
74
 
@@ -69,78 +78,100 @@ export function App() {
69
78
  }
70
79
 
71
80
  if (trimmed === "?" || trimmed === "/help") {
72
- setShowHelp(!showHelp);
81
+ setShowHelp((s) => !s);
73
82
  return;
74
83
  }
75
84
 
76
85
  processCommand(trimmed);
77
- };
86
+ }, [exit, processCommand]);
87
+
88
+ const handleDismissHelp = useCallback(() => setShowHelp(false), []);
89
+
90
+ const headerProps = useMemo(() => ({
91
+ projectName: state.projectName,
92
+ agentCount: state.agentCount,
93
+ provider: state.provider,
94
+ status: state.status,
95
+ tick,
96
+ activePanel,
97
+ }), [state.projectName, state.agentCount, state.provider, state.status, tick, activePanel]);
98
+
99
+ const chatPanelProps = useMemo(() => ({
100
+ messages: state.messages,
101
+ isProcessing: state.isProcessing,
102
+ isActive: activePanel === "chat",
103
+ }), [state.messages, state.isProcessing, activePanel]);
104
+
105
+ const dagPanelProps = useMemo(() => ({
106
+ workflowState: state.workflowState,
107
+ tick,
108
+ isActive: activePanel === "dag",
109
+ }), [state.workflowState, tick, activePanel]);
110
+
111
+ const logsPanelProps = useMemo(() => ({
112
+ logs: state.logs,
113
+ isActive: activePanel === "logs",
114
+ }), [state.logs, activePanel]);
115
+
116
+ const metricsPanelProps = useMemo(() => ({
117
+ metrics: state.metrics,
118
+ fitness: state.fitness,
119
+ isActive: activePanel === "metrics",
120
+ tick,
121
+ }), [state.metrics, state.fitness, activePanel, tick]);
122
+
123
+ const memoryPanelProps = useMemo(() => ({
124
+ nodes: [] as never[],
125
+ edges: [] as never[],
126
+ nodeCount: 247,
127
+ cacheHitRate: 94,
128
+ isActive: activePanel === "memory",
129
+ tick,
130
+ }), [activePanel, tick]);
131
+
132
+ const statusPanelProps = useMemo(() => ({
133
+ agents: state.agents,
134
+ providers: state.providers,
135
+ fitness: state.fitness,
136
+ currentWorkflow: state.currentWorkflow,
137
+ isActive: activePanel === "status",
138
+ tick,
139
+ }), [state.agents, state.providers, state.fitness, state.currentWorkflow, activePanel, tick]);
140
+
141
+ const inputBarProps = useMemo(() => ({
142
+ onSubmit: handleSubmit,
143
+ suggestions: state.suggestions,
144
+ isProcessing: state.isProcessing,
145
+ projectName: state.projectName,
146
+ }), [handleSubmit, state.suggestions, state.isProcessing, state.projectName]);
78
147
 
79
148
  return (
80
149
  <Box flexDirection="column" height="100%" width="100%">
81
- <Header
82
- projectName={state.projectName}
83
- agentCount={state.agentCount}
84
- provider={state.provider}
85
- status={state.status}
86
- tick={tick}
87
- activePanel={activePanel}
88
- />
150
+ <Header {...headerProps} />
89
151
 
90
152
  {showHelp ? (
91
- <HelpOverlay onDismiss={() => setShowHelp(false)} />
153
+ <HelpOverlay onDismiss={handleDismissHelp} />
92
154
  ) : (
93
155
  <>
94
156
  <Box flexGrow={1} flexDirection="row" width="100%">
95
- <ChatPanel
96
- messages={state.messages}
97
- isProcessing={state.isProcessing}
98
- isActive={activePanel === "chat"}
99
- />
100
- <DAGPanel
101
- workflowState={state.workflowState}
102
- tick={tick}
103
- isActive={activePanel === "dag"}
104
- />
157
+ <ChatPanel {...chatPanelProps} />
158
+ <DAGPanel {...dagPanelProps} />
105
159
  {viewMode === "full" && (
106
160
  <>
107
- <LogsPanel
108
- logs={state.logs}
109
- isActive={activePanel === "logs"}
110
- />
161
+ <LogsPanel {...logsPanelProps} />
111
162
  <Box flexDirection="column">
112
- <MetricsPanel
113
- metrics={state.metrics}
114
- fitness={state.fitness}
115
- isActive={activePanel === "metrics"}
116
- />
117
- <MemoryPanel
118
- nodes={[]}
119
- edges={[]}
120
- nodeCount={247}
121
- cacheHitRate={94}
122
- isActive={activePanel === "memory"}
123
- />
163
+ <MetricsPanel {...metricsPanelProps} />
164
+ <MemoryPanel {...memoryPanelProps} />
124
165
  </Box>
125
166
  </>
126
167
  )}
127
168
  {viewMode === "compact" && (
128
- <StatusPanel
129
- agents={state.agents}
130
- providers={state.providers}
131
- fitness={state.fitness}
132
- currentWorkflow={state.currentWorkflow}
133
- isActive={activePanel === "status"}
134
- />
169
+ <StatusPanel {...statusPanelProps} />
135
170
  )}
136
171
  </Box>
137
- <InputBar
138
- onSubmit={handleSubmit}
139
- suggestions={state.suggestions}
140
- isProcessing={state.isProcessing}
141
- />
172
+ <InputBar {...inputBarProps} />
142
173
  </>
143
174
  )}
144
175
  </Box>
145
176
  );
146
- }
177
+ });
@@ -0,0 +1,84 @@
1
+ import React, { memo, useMemo } from "react";
2
+ import { Text } from "ink";
3
+ import { tuiColors } from "../styles/index.js";
4
+
5
+ const THINKING_FRAMES = ["◠", "◡", "◠", "◡"];
6
+ const PROCESSING_FRAMES = ["▶", "▷", "▶", "▷"];
7
+ const RUNNING_FRAMES = ["●", "◉", "●", "○"];
8
+ const WAITING_FRAMES = ["◐", "◓", "◐", "◓"];
9
+
10
+ export type AgentState = 'idle' | 'thinking' | 'processing' | 'running' | 'success' | 'error' | 'waiting';
11
+
12
+ interface AgentStateIconProps {
13
+ state: AgentState;
14
+ animated?: boolean;
15
+ tick?: number;
16
+ }
17
+
18
+ const STATE_CONFIG: Record<AgentState, { icon: string; color: string; animated: boolean; frames?: string[] }> = {
19
+ idle: { icon: "○", color: tuiColors.textTertiary, animated: false },
20
+ thinking: { icon: "◠", color: tuiColors.warning, animated: true, frames: THINKING_FRAMES },
21
+ processing: { icon: "▶", color: tuiColors.primary, animated: true, frames: PROCESSING_FRAMES },
22
+ running: { icon: "●", color: tuiColors.success, animated: true, frames: RUNNING_FRAMES },
23
+ success: { icon: "✓", color: tuiColors.success, animated: false },
24
+ error: { icon: "✗", color: tuiColors.error, animated: false },
25
+ waiting: { icon: "◐", color: tuiColors.warning, animated: true, frames: WAITING_FRAMES },
26
+ };
27
+
28
+ export const AgentStateIcon = memo(function AgentStateIcon({ state, animated = true, tick = 0 }: AgentStateIconProps) {
29
+ const config = STATE_CONFIG[state];
30
+ const showAnimation = animated && config.animated && config.frames;
31
+
32
+ const icon = useMemo(() => {
33
+ if (showAnimation && config.frames) {
34
+ return config.frames[tick % config.frames.length];
35
+ }
36
+ return config.icon;
37
+ }, [showAnimation, config.frames, config.icon, tick]);
38
+
39
+ return <Text color={config.color}>{icon}</Text>;
40
+ });
41
+
42
+ export function useAgentStateFrame(state: AgentState, tick: number): string {
43
+ const config = STATE_CONFIG[state];
44
+ if (config.frames) {
45
+ return config.frames[tick % config.frames.length];
46
+ }
47
+ return config.icon;
48
+ }
49
+
50
+ const STATE_LABELS: Record<AgentState, string> = {
51
+ idle: "pending",
52
+ thinking: "thinking...",
53
+ processing: "processing",
54
+ running: "running",
55
+ success: "done",
56
+ error: "error",
57
+ waiting: "waiting",
58
+ };
59
+
60
+ interface AgentStateWithLabelProps {
61
+ state: AgentState;
62
+ label: string;
63
+ tick: number;
64
+ }
65
+
66
+ export const AgentStateWithLabel = memo(function AgentStateWithLabel({ state, label, tick }: AgentStateWithLabelProps) {
67
+ return (
68
+ <>
69
+ <AgentStateIcon state={state} tick={tick} />
70
+ <Text color={tuiColors.textSecondary}> {label}</Text>
71
+ <Text color={tuiColors.textTertiary}> {STATE_LABELS[state]}</Text>
72
+ </>
73
+ );
74
+ });
75
+
76
+ export const agentStateColors: Record<AgentState, string> = {
77
+ idle: tuiColors.textTertiary,
78
+ thinking: tuiColors.warning,
79
+ processing: tuiColors.primary,
80
+ running: tuiColors.success,
81
+ success: tuiColors.success,
82
+ error: tuiColors.error,
83
+ waiting: tuiColors.warning,
84
+ };
@@ -0,0 +1,134 @@
1
+ import React, { memo, useMemo } from "react";
2
+ import { Box, Text } from "ink";
3
+ import { tuiColors } from "../styles/index.js";
4
+
5
+ const VERTICAL_PULSE_FRAMES = ["│", "┃", "│", "┃"];
6
+ const HORIZONTAL_PULSE_FRAMES = ["─", "━", "─", "━"];
7
+ const FLOW_FRAMES = ["◀═══▶", "═◀═══▶", "══◀══▶", "═══◀═▶", "════◀▶", "═▶═══◀", "══▶══◀", "═══▶═◀"];
8
+
9
+ export type BranchType = 'vertical' | 'horizontal' | 'corner-left' | 'corner-right' | 'branch' | 'last-branch' | 'flow';
10
+
11
+ interface AnimatedBranchProps {
12
+ type: BranchType;
13
+ tick?: number;
14
+ animated?: boolean;
15
+ length?: number;
16
+ color?: string;
17
+ }
18
+
19
+ export const AnimatedBranch = memo(function AnimatedBranch({
20
+ type,
21
+ tick = 0,
22
+ animated = true,
23
+ length = 1,
24
+ color
25
+ }: AnimatedBranchProps) {
26
+ const branchColor = color || tuiColors.textTertiary;
27
+
28
+ const getFrame = (frames: string[]): string => {
29
+ return frames[tick % frames.length];
30
+ };
31
+
32
+ const renderBranch = (): string => {
33
+ switch (type) {
34
+ case 'vertical':
35
+ return animated ? getFrame(VERTICAL_PULSE_FRAMES) : "│";
36
+ case 'horizontal':
37
+ return animated ? getFrame(HORIZONTAL_PULSE_FRAMES).repeat(length) : "─".repeat(length);
38
+ case 'corner-left':
39
+ return "┌";
40
+ case 'corner-right':
41
+ return "┐";
42
+ case 'branch':
43
+ return "├─";
44
+ case 'last-branch':
45
+ return "└─";
46
+ case 'flow':
47
+ return animated ? getFrame(FLOW_FRAMES) : "◀═══▶";
48
+ default:
49
+ return "│";
50
+ }
51
+ };
52
+
53
+ return <Text color={branchColor}>{renderBranch()}</Text>;
54
+ });
55
+
56
+ interface BranchLineProps {
57
+ tick: number;
58
+ animated?: boolean;
59
+ length?: number;
60
+ color?: string;
61
+ }
62
+
63
+ export const VerticalBranch = memo(function VerticalBranch({ tick, animated = true, length = 1, color }: BranchLineProps) {
64
+ return <AnimatedBranch type="vertical" tick={tick} animated={animated} color={color} />;
65
+ });
66
+
67
+ export const HorizontalBranch = memo(function HorizontalBranch({ tick, animated = true, length = 1, color }: BranchLineProps) {
68
+ return <AnimatedBranch type="horizontal" tick={tick} animated={animated} length={length} color={color} />;
69
+ });
70
+
71
+ export const BranchConnector = memo(function BranchConnector({ tick, animated = true, color }: BranchLineProps) {
72
+ return <AnimatedBranch type="branch" tick={tick} animated={animated} color={color} />;
73
+ });
74
+
75
+ export const LastBranchConnector = memo(function LastBranchConnector({ tick, animated = true, color }: BranchLineProps) {
76
+ return <AnimatedBranch type="last-branch" tick={tick} animated={animated} color={color} />;
77
+ });
78
+
79
+ export const FlowIndicator = memo(function FlowIndicator({ tick, animated = true, color }: BranchLineProps) {
80
+ return <AnimatedBranch type="flow" tick={tick} animated={animated} color={color} />;
81
+ });
82
+
83
+ interface TaskBranchProps {
84
+ tick: number;
85
+ depth: number;
86
+ isLast: boolean;
87
+ hasChildren?: boolean;
88
+ isExpanded?: boolean;
89
+ color?: string;
90
+ }
91
+
92
+ export const TaskBranch = memo(function TaskBranch({ tick, depth, isLast, hasChildren = false, isExpanded = true, color }: TaskBranchProps) {
93
+ const branchColor = color || tuiColors.textTertiary;
94
+
95
+ if (depth === 0) {
96
+ return null;
97
+ }
98
+
99
+ const indent = " ".repeat(depth - 1);
100
+
101
+ return (
102
+ <Box flexDirection="row">
103
+ <Text color={branchColor}>{indent}</Text>
104
+ <AnimatedBranch type={isLast ? "last-branch" : "branch"} tick={tick} color={branchColor} />
105
+ </Box>
106
+ );
107
+ });
108
+
109
+ interface AnimatedConnectorProps {
110
+ tick: number;
111
+ fromLevel: number;
112
+ toLevel: number;
113
+ isActive: boolean;
114
+ }
115
+
116
+ export const AnimatedConnector = memo(function AnimatedConnector({ tick, fromLevel, toLevel, isActive }: AnimatedConnectorProps) {
117
+ const color = isActive ? tuiColors.primary : tuiColors.textTertiary;
118
+ const rows = toLevel - fromLevel;
119
+
120
+ const connectorElements = useMemo(() =>
121
+ Array.from({ length: rows }).map((_, idx) => (
122
+ <Box key={idx} paddingLeft={2}>
123
+ <AnimatedBranch type="vertical" tick={tick} animated={isActive} color={color} />
124
+ </Box>
125
+ )),
126
+ [rows, tick, isActive, color]
127
+ );
128
+
129
+ return (
130
+ <Box flexDirection="column">
131
+ {connectorElements}
132
+ </Box>
133
+ );
134
+ });
@@ -1,5 +1,7 @@
1
- import React from "react";
1
+ import React, { useState, useEffect, useMemo, memo, useCallback } from "react";
2
2
  import { Box, Text } from "ink";
3
+ import { tuiColors, formatTimestamp, getProviderTag } from "../styles/index.js";
4
+ import { TypingEffect, ProcessingIndicator } from "./animations/index.js";
3
5
 
4
6
  interface Message {
5
7
  id: string;
@@ -15,45 +17,90 @@ interface ChatPanelProps {
15
17
  isActive: boolean;
16
18
  }
17
19
 
18
- function formatTime(date: Date): string {
19
- return date.toLocaleTimeString("fr-FR", {
20
- hour: "2-digit",
21
- minute: "2-digit",
22
- second: "2-digit",
23
- });
24
- }
20
+ const MESSAGE_CONFIG: Record<string, { tag: string; color: string }> = {
21
+ user: { tag: "[YOU]", color: tuiColors.textPrimary },
22
+ system: { tag: "[SYS]", color: tuiColors.textTertiary },
23
+ agent: { tag: "[AGENT]", color: tuiColors.primary },
24
+ error: { tag: "[ERROR]", color: tuiColors.error },
25
+ success: { tag: "[SUCCESS]", color: tuiColors.success },
26
+ };
27
+
28
+ const MAX_DISPLAYED_MESSAGES = 12;
29
+ const TYPING_SPEED_PER_CHAR = 15;
30
+ const TYPING_BUFFER_MS = 500;
25
31
 
26
- function MessageItem({ message }: { message: Message }) {
27
- const typeColors: Record<string, string> = {
28
- user: "cyan",
29
- system: "gray",
30
- agent: "#f97316",
31
- error: "red",
32
- success: "green",
33
- };
32
+ interface MessageItemProps {
33
+ message: Message;
34
+ animate?: boolean;
35
+ }
34
36
 
35
- const typeLabels: Record<string, string> = {
36
- user: "YOU",
37
- system: "SYS",
38
- agent: message.agent || "AGENT",
39
- error: "ERR",
40
- success: "OK",
41
- };
37
+ const MessageItem = memo(function MessageItem({ message, animate = false }: MessageItemProps) {
38
+ const config = MESSAGE_CONFIG[message.type];
39
+ const providerInfo = useMemo(() =>
40
+ message.agent ? getProviderTag(message.agent) : null,
41
+ [message.agent]
42
+ );
43
+ const tag = message.type === "agent" && message.agent
44
+ ? `[${message.agent.toUpperCase().slice(0, 8)}]`
45
+ : config.tag;
46
+ const formattedTime = useMemo(() => formatTimestamp(message.timestamp), [message.timestamp]);
42
47
 
43
48
  return (
44
49
  <Box flexDirection="column" marginBottom={1}>
45
50
  <Box flexDirection="row">
46
- <Text color="gray" dimColor>[{formatTime(message.timestamp)}]</Text>
47
- <Text color={typeColors[message.type]} bold> [{typeLabels[message.type]}]</Text>
48
- {message.agent && <Text color="gray"> [{message.agent}]</Text>}
51
+ <Text color={tuiColors.textTertiary}>[{formattedTime}]</Text>
52
+ <Text color={config.color} bold>{tag.padEnd(12)}</Text>
53
+ {providerInfo && (
54
+ <Text color={tuiColors.textTertiary}>{providerInfo.code}</Text>
55
+ )}
49
56
  </Box>
50
- <Text color="white">{message.content}</Text>
57
+ {animate ? (
58
+ <TypingEffect text={message.content} speed={TYPING_SPEED_PER_CHAR} showCursor={false} />
59
+ ) : (
60
+ <Text color={tuiColors.textPrimary}> {message.content}</Text>
61
+ )}
51
62
  </Box>
52
63
  );
53
- }
64
+ });
54
65
 
55
- export function ChatPanel({ messages, isProcessing, isActive }: ChatPanelProps) {
56
- const borderColor = isActive ? "#f97316" : "gray";
66
+ export const ChatPanel = memo(function ChatPanel({ messages, isProcessing, isActive }: ChatPanelProps) {
67
+ const borderColor = isActive ? tuiColors.primary : tuiColors.border;
68
+ const [animatingMessageId, setAnimatingMessageId] = useState<string | null>(null);
69
+ const timeoutRef = React.useRef<NodeJS.Timeout | null>(null);
70
+
71
+ const displayMessages = useMemo(() =>
72
+ messages.slice(-MAX_DISPLAYED_MESSAGES),
73
+ [messages]
74
+ );
75
+
76
+ useEffect(() => {
77
+ if (messages.length === 0) return;
78
+
79
+ const lastMessage = messages[messages.length - 1];
80
+ if (lastMessage.type !== "agent" && lastMessage.type !== "system") return;
81
+
82
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
83
+
84
+ setAnimatingMessageId(lastMessage.id);
85
+ timeoutRef.current = setTimeout(() => {
86
+ setAnimatingMessageId(null);
87
+ }, lastMessage.content.length * TYPING_SPEED_PER_CHAR + TYPING_BUFFER_MS);
88
+
89
+ return () => {
90
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
91
+ };
92
+ }, [messages]);
93
+
94
+ const messageElements = useMemo(() =>
95
+ displayMessages.map((msg) => (
96
+ <MessageItem
97
+ key={msg.id}
98
+ message={msg}
99
+ animate={msg.id === animatingMessageId}
100
+ />
101
+ )),
102
+ [displayMessages, animatingMessageId]
103
+ );
57
104
 
58
105
  return (
59
106
  <Box
@@ -63,21 +110,16 @@ export function ChatPanel({ messages, isProcessing, isActive }: ChatPanelProps)
63
110
  borderColor={borderColor}
64
111
  paddingX={1}
65
112
  >
66
- <Box borderStyle="single" borderColor="#f97316" marginBottom={1}>
67
- <Text color="#f97316" bold>CHAT</Text>
68
- <Text color="gray"> Tapez /help pour les commandes</Text>
69
- {isActive && <Text color="yellow"> [ACTIF]</Text>}
113
+ <Box marginBottom={1}>
114
+ <Text color={tuiColors.primary} bold>┌─</Text>
115
+ <Text color={tuiColors.primary} bold> CHAT </Text>
116
+ <Text color={tuiColors.textTertiary}>─────────────────────────────────────</Text>
70
117
  </Box>
118
+
71
119
  <Box flexDirection="column" flexGrow={1} overflow="hidden">
72
- {messages.slice(-15).map((msg) => (
73
- <MessageItem key={msg.id} message={msg} />
74
- ))}
75
- {isProcessing && (
76
- <Box>
77
- <Text color="yellow">● Processing...</Text>
78
- </Box>
79
- )}
120
+ {messageElements}
121
+ {isProcessing && <ProcessingIndicator text="Processing" />}
80
122
  </Box>
81
123
  </Box>
82
124
  );
83
- }
125
+ });