wave-code 0.0.2

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 (131) hide show
  1. package/README.md +120 -0
  2. package/bin/wave-code.js +16 -0
  3. package/dist/cli.d.ts +6 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +62 -0
  6. package/dist/components/App.d.ts +8 -0
  7. package/dist/components/App.d.ts.map +1 -0
  8. package/dist/components/App.js +10 -0
  9. package/dist/components/BashHistorySelector.d.ts +10 -0
  10. package/dist/components/BashHistorySelector.d.ts.map +1 -0
  11. package/dist/components/BashHistorySelector.js +83 -0
  12. package/dist/components/BashShellManager.d.ts +6 -0
  13. package/dist/components/BashShellManager.d.ts.map +1 -0
  14. package/dist/components/BashShellManager.js +116 -0
  15. package/dist/components/ChatInterface.d.ts +3 -0
  16. package/dist/components/ChatInterface.d.ts.map +1 -0
  17. package/dist/components/ChatInterface.js +31 -0
  18. package/dist/components/CommandOutputDisplay.d.ts +9 -0
  19. package/dist/components/CommandOutputDisplay.d.ts.map +1 -0
  20. package/dist/components/CommandOutputDisplay.js +40 -0
  21. package/dist/components/CommandSelector.d.ts +11 -0
  22. package/dist/components/CommandSelector.d.ts.map +1 -0
  23. package/dist/components/CommandSelector.js +60 -0
  24. package/dist/components/CompressDisplay.d.ts +9 -0
  25. package/dist/components/CompressDisplay.d.ts.map +1 -0
  26. package/dist/components/CompressDisplay.js +17 -0
  27. package/dist/components/DiffViewer.d.ts +9 -0
  28. package/dist/components/DiffViewer.d.ts.map +1 -0
  29. package/dist/components/DiffViewer.js +221 -0
  30. package/dist/components/FileSelector.d.ts +13 -0
  31. package/dist/components/FileSelector.d.ts.map +1 -0
  32. package/dist/components/FileSelector.js +48 -0
  33. package/dist/components/InputBox.d.ts +23 -0
  34. package/dist/components/InputBox.d.ts.map +1 -0
  35. package/dist/components/InputBox.js +124 -0
  36. package/dist/components/McpManager.d.ts +10 -0
  37. package/dist/components/McpManager.d.ts.map +1 -0
  38. package/dist/components/McpManager.js +123 -0
  39. package/dist/components/MemoryDisplay.d.ts +8 -0
  40. package/dist/components/MemoryDisplay.d.ts.map +1 -0
  41. package/dist/components/MemoryDisplay.js +25 -0
  42. package/dist/components/MemoryTypeSelector.d.ts +8 -0
  43. package/dist/components/MemoryTypeSelector.d.ts.map +1 -0
  44. package/dist/components/MemoryTypeSelector.js +38 -0
  45. package/dist/components/MessageList.d.ts +12 -0
  46. package/dist/components/MessageList.d.ts.map +1 -0
  47. package/dist/components/MessageList.js +36 -0
  48. package/dist/components/ToolResultDisplay.d.ts +9 -0
  49. package/dist/components/ToolResultDisplay.d.ts.map +1 -0
  50. package/dist/components/ToolResultDisplay.js +52 -0
  51. package/dist/contexts/useAppConfig.d.ts +11 -0
  52. package/dist/contexts/useAppConfig.d.ts.map +1 -0
  53. package/dist/contexts/useAppConfig.js +13 -0
  54. package/dist/contexts/useChat.d.ts +36 -0
  55. package/dist/contexts/useChat.d.ts.map +1 -0
  56. package/dist/contexts/useChat.js +208 -0
  57. package/dist/hooks/useBashHistorySelector.d.ts +15 -0
  58. package/dist/hooks/useBashHistorySelector.d.ts.map +1 -0
  59. package/dist/hooks/useBashHistorySelector.js +61 -0
  60. package/dist/hooks/useCommandSelector.d.ts +24 -0
  61. package/dist/hooks/useCommandSelector.d.ts.map +1 -0
  62. package/dist/hooks/useCommandSelector.js +98 -0
  63. package/dist/hooks/useFileSelector.d.ts +16 -0
  64. package/dist/hooks/useFileSelector.d.ts.map +1 -0
  65. package/dist/hooks/useFileSelector.js +174 -0
  66. package/dist/hooks/useImageManager.d.ts +13 -0
  67. package/dist/hooks/useImageManager.d.ts.map +1 -0
  68. package/dist/hooks/useImageManager.js +46 -0
  69. package/dist/hooks/useInputHistory.d.ts +11 -0
  70. package/dist/hooks/useInputHistory.d.ts.map +1 -0
  71. package/dist/hooks/useInputHistory.js +64 -0
  72. package/dist/hooks/useInputKeyboardHandler.d.ts +83 -0
  73. package/dist/hooks/useInputKeyboardHandler.d.ts.map +1 -0
  74. package/dist/hooks/useInputKeyboardHandler.js +507 -0
  75. package/dist/hooks/useInputState.d.ts +14 -0
  76. package/dist/hooks/useInputState.d.ts.map +1 -0
  77. package/dist/hooks/useInputState.js +57 -0
  78. package/dist/hooks/useMemoryTypeSelector.d.ts +9 -0
  79. package/dist/hooks/useMemoryTypeSelector.d.ts.map +1 -0
  80. package/dist/hooks/useMemoryTypeSelector.js +27 -0
  81. package/dist/hooks/usePagination.d.ts +20 -0
  82. package/dist/hooks/usePagination.d.ts.map +1 -0
  83. package/dist/hooks/usePagination.js +168 -0
  84. package/dist/index.d.ts +5 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +91 -0
  87. package/dist/plain-cli.d.ts +7 -0
  88. package/dist/plain-cli.d.ts.map +1 -0
  89. package/dist/plain-cli.js +49 -0
  90. package/dist/utils/clipboard.d.ts +22 -0
  91. package/dist/utils/clipboard.d.ts.map +1 -0
  92. package/dist/utils/clipboard.js +347 -0
  93. package/dist/utils/constants.d.ts +17 -0
  94. package/dist/utils/constants.d.ts.map +1 -0
  95. package/dist/utils/constants.js +18 -0
  96. package/dist/utils/logger.d.ts +72 -0
  97. package/dist/utils/logger.d.ts.map +1 -0
  98. package/dist/utils/logger.js +245 -0
  99. package/package.json +60 -0
  100. package/src/cli.tsx +82 -0
  101. package/src/components/App.tsx +31 -0
  102. package/src/components/BashHistorySelector.tsx +163 -0
  103. package/src/components/BashShellManager.tsx +306 -0
  104. package/src/components/ChatInterface.tsx +88 -0
  105. package/src/components/CommandOutputDisplay.tsx +81 -0
  106. package/src/components/CommandSelector.tsx +144 -0
  107. package/src/components/CompressDisplay.tsx +58 -0
  108. package/src/components/DiffViewer.tsx +321 -0
  109. package/src/components/FileSelector.tsx +137 -0
  110. package/src/components/InputBox.tsx +310 -0
  111. package/src/components/McpManager.tsx +328 -0
  112. package/src/components/MemoryDisplay.tsx +62 -0
  113. package/src/components/MemoryTypeSelector.tsx +96 -0
  114. package/src/components/MessageList.tsx +215 -0
  115. package/src/components/ToolResultDisplay.tsx +138 -0
  116. package/src/contexts/useAppConfig.tsx +32 -0
  117. package/src/contexts/useChat.tsx +300 -0
  118. package/src/hooks/useBashHistorySelector.ts +77 -0
  119. package/src/hooks/useCommandSelector.ts +131 -0
  120. package/src/hooks/useFileSelector.ts +227 -0
  121. package/src/hooks/useImageManager.ts +64 -0
  122. package/src/hooks/useInputHistory.ts +74 -0
  123. package/src/hooks/useInputKeyboardHandler.ts +778 -0
  124. package/src/hooks/useInputState.ts +66 -0
  125. package/src/hooks/useMemoryTypeSelector.ts +40 -0
  126. package/src/hooks/usePagination.ts +203 -0
  127. package/src/index.ts +108 -0
  128. package/src/plain-cli.ts +66 -0
  129. package/src/utils/clipboard.ts +384 -0
  130. package/src/utils/constants.ts +22 -0
  131. package/src/utils/logger.ts +301 -0
@@ -0,0 +1,328 @@
1
+ import React, { useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import { McpServerStatus } from "wave-agent-sdk";
4
+
5
+ export interface McpManagerProps {
6
+ onCancel: () => void;
7
+ servers: McpServerStatus[];
8
+ onConnectServer: (serverName: string) => Promise<boolean>;
9
+ onDisconnectServer: (serverName: string) => Promise<boolean>;
10
+ }
11
+
12
+ export const McpManager: React.FC<McpManagerProps> = ({
13
+ onCancel,
14
+ servers,
15
+ onConnectServer,
16
+ onDisconnectServer,
17
+ }) => {
18
+ const [selectedIndex, setSelectedIndex] = useState(0);
19
+ const [viewMode, setViewMode] = useState<"list" | "detail">("list");
20
+
21
+ // Dynamically calculate selectedServer based on selectedIndex and servers
22
+ const selectedServer =
23
+ viewMode === "detail" &&
24
+ servers.length > 0 &&
25
+ selectedIndex < servers.length
26
+ ? servers[selectedIndex]
27
+ : null;
28
+
29
+ const formatTime = (timestamp: number): string => {
30
+ return new Date(timestamp).toLocaleTimeString();
31
+ };
32
+
33
+ const getStatusColor = (status: string): string => {
34
+ switch (status) {
35
+ case "connected":
36
+ return "green";
37
+ case "connecting":
38
+ return "yellow";
39
+ case "error":
40
+ return "red";
41
+ default:
42
+ return "gray";
43
+ }
44
+ };
45
+
46
+ const getStatusIcon = (status: string): string => {
47
+ switch (status) {
48
+ case "connected":
49
+ return "✓";
50
+ case "connecting":
51
+ return "⟳";
52
+ case "error":
53
+ return "✗";
54
+ default:
55
+ return "○";
56
+ }
57
+ };
58
+
59
+ const handleConnect = async (serverName: string) => {
60
+ await onConnectServer(serverName);
61
+ };
62
+
63
+ const handleDisconnect = async (serverName: string) => {
64
+ await onDisconnectServer(serverName);
65
+ };
66
+
67
+ useInput((input, key) => {
68
+ if (viewMode === "list") {
69
+ // List mode navigation
70
+ if (key.return) {
71
+ if (servers.length > 0 && selectedIndex < servers.length) {
72
+ setViewMode("detail");
73
+ }
74
+ return;
75
+ }
76
+
77
+ if (key.escape) {
78
+ onCancel();
79
+ return;
80
+ }
81
+
82
+ if (key.upArrow) {
83
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
84
+ return;
85
+ }
86
+
87
+ if (key.downArrow) {
88
+ setSelectedIndex(Math.min(servers.length - 1, selectedIndex + 1));
89
+ return;
90
+ }
91
+
92
+ // Hotkeys for server actions
93
+ if (
94
+ input === "c" &&
95
+ servers.length > 0 &&
96
+ selectedIndex < servers.length
97
+ ) {
98
+ const server = servers[selectedIndex];
99
+ if (server.status === "disconnected" || server.status === "error") {
100
+ handleConnect(server.name);
101
+ }
102
+ return;
103
+ }
104
+
105
+ if (
106
+ input === "d" &&
107
+ servers.length > 0 &&
108
+ selectedIndex < servers.length
109
+ ) {
110
+ const server = servers[selectedIndex];
111
+ if (server.status === "connected") {
112
+ handleDisconnect(server.name);
113
+ }
114
+ return;
115
+ }
116
+ } else if (viewMode === "detail") {
117
+ // Detail mode navigation
118
+ if (key.escape) {
119
+ setViewMode("list");
120
+ return;
121
+ }
122
+
123
+ if (selectedServer) {
124
+ if (
125
+ input === "c" &&
126
+ (selectedServer.status === "disconnected" ||
127
+ selectedServer.status === "error")
128
+ ) {
129
+ handleConnect(selectedServer.name);
130
+ return;
131
+ }
132
+
133
+ if (input === "d" && selectedServer.status === "connected") {
134
+ handleDisconnect(selectedServer.name);
135
+ return;
136
+ }
137
+ }
138
+ }
139
+ });
140
+
141
+ if (viewMode === "detail" && selectedServer) {
142
+ return (
143
+ <Box
144
+ flexDirection="column"
145
+ borderStyle="single"
146
+ borderColor="cyan"
147
+ padding={1}
148
+ gap={1}
149
+ marginBottom={1}
150
+ >
151
+ <Box>
152
+ <Text color="cyan" bold>
153
+ MCP Server Details: {selectedServer.name}
154
+ </Text>
155
+ </Box>
156
+
157
+ <Box flexDirection="column" gap={1}>
158
+ <Box>
159
+ <Text>
160
+ <Text color="blue">Status:</Text>{" "}
161
+ <Text color={getStatusColor(selectedServer.status)}>
162
+ {getStatusIcon(selectedServer.status)} {selectedServer.status}
163
+ </Text>
164
+ </Text>
165
+ </Box>
166
+ <Box>
167
+ <Text>
168
+ <Text color="blue">Command:</Text> {selectedServer.config.command}
169
+ </Text>
170
+ </Box>
171
+ {selectedServer.config.args && (
172
+ <Box>
173
+ <Text>
174
+ <Text color="blue">Args:</Text>{" "}
175
+ {selectedServer.config.args.join(" ")}
176
+ </Text>
177
+ </Box>
178
+ )}
179
+ {selectedServer.toolCount !== undefined && (
180
+ <Box>
181
+ <Text>
182
+ <Text color="blue">Tools:</Text> {selectedServer.toolCount}{" "}
183
+ tools
184
+ </Text>
185
+ </Box>
186
+ )}
187
+ {selectedServer.capabilities && (
188
+ <Box>
189
+ <Text>
190
+ <Text color="blue">Capabilities:</Text>{" "}
191
+ {selectedServer.capabilities.join(", ")}
192
+ </Text>
193
+ </Box>
194
+ )}
195
+ {selectedServer.lastConnected && (
196
+ <Box>
197
+ <Text>
198
+ <Text color="blue">Last Connected:</Text>{" "}
199
+ {formatTime(selectedServer.lastConnected)}
200
+ </Text>
201
+ </Box>
202
+ )}
203
+ {selectedServer.error && (
204
+ <Box>
205
+ <Text>
206
+ <Text color="red">Error:</Text> {selectedServer.error}
207
+ </Text>
208
+ </Box>
209
+ )}
210
+ </Box>
211
+
212
+ {selectedServer.config.env &&
213
+ Object.keys(selectedServer.config.env).length > 0 && (
214
+ <Box flexDirection="column" marginTop={1}>
215
+ <Text color="blue" bold>
216
+ Environment Variables:
217
+ </Text>
218
+ <Box borderStyle="single" borderColor="blue" padding={1}>
219
+ {Object.entries(selectedServer.config.env).map(
220
+ ([key, value]) => (
221
+ <Text key={key}>
222
+ {key}={value}
223
+ </Text>
224
+ ),
225
+ )}
226
+ </Box>
227
+ </Box>
228
+ )}
229
+
230
+ <Box marginTop={1}>
231
+ <Text dimColor>
232
+ {selectedServer.status === "disconnected" ||
233
+ selectedServer.status === "error"
234
+ ? "c to connect · "
235
+ : ""}
236
+ {selectedServer.status === "connected" ? "d to disconnect · " : ""}
237
+ Esc to go back
238
+ </Text>
239
+ </Box>
240
+ </Box>
241
+ );
242
+ }
243
+
244
+ if (servers.length === 0) {
245
+ return (
246
+ <Box
247
+ flexDirection="column"
248
+ borderStyle="single"
249
+ borderColor="cyan"
250
+ padding={1}
251
+ marginBottom={1}
252
+ >
253
+ <Text color="cyan" bold>
254
+ Manage MCP servers
255
+ </Text>
256
+ <Text>No MCP servers configured</Text>
257
+ <Text dimColor>
258
+ Create a .mcp.json file in your project root to add servers
259
+ </Text>
260
+ <Text dimColor>Press Escape to close</Text>
261
+ </Box>
262
+ );
263
+ }
264
+
265
+ return (
266
+ <Box
267
+ flexDirection="column"
268
+ borderStyle="single"
269
+ borderColor="cyan"
270
+ padding={1}
271
+ gap={1}
272
+ marginBottom={1}
273
+ >
274
+ <Box>
275
+ <Text color="cyan" bold>
276
+ Manage MCP servers
277
+ </Text>
278
+ </Box>
279
+ <Text dimColor>Select a server to view details</Text>
280
+
281
+ {servers.map((server, index) => (
282
+ <Box key={server.name} flexDirection="column">
283
+ <Text
284
+ color={index === selectedIndex ? "black" : "white"}
285
+ backgroundColor={index === selectedIndex ? "cyan" : undefined}
286
+ >
287
+ {index === selectedIndex ? "▶ " : " "}
288
+ {index + 1}.{" "}
289
+ <Text color={getStatusColor(server.status)}>
290
+ {getStatusIcon(server.status)}
291
+ </Text>{" "}
292
+ {server.name}
293
+ {server.status === "connected" && server.toolCount && (
294
+ <Text color="green"> · {server.toolCount} tools</Text>
295
+ )}
296
+ </Text>
297
+ {index === selectedIndex && (
298
+ <Box marginLeft={4} flexDirection="column">
299
+ <Text color="gray" dimColor>
300
+ {server.config.command}
301
+ {server.config.args ? ` ${server.config.args.join(" ")}` : ""}
302
+ </Text>
303
+ {server.lastConnected && (
304
+ <Text color="gray" dimColor>
305
+ Last connected: {formatTime(server.lastConnected)}
306
+ </Text>
307
+ )}
308
+ </Box>
309
+ )}
310
+ </Box>
311
+ ))}
312
+
313
+ <Box marginTop={1}>
314
+ <Text dimColor>
315
+ ↑/↓ to select · Enter to view ·{" "}
316
+ {servers[selectedIndex]?.status === "disconnected" ||
317
+ servers[selectedIndex]?.status === "error"
318
+ ? "c to connect · "
319
+ : ""}
320
+ {servers[selectedIndex]?.status === "connected"
321
+ ? "d to disconnect · "
322
+ : ""}
323
+ Esc to close
324
+ </Text>
325
+ </Box>
326
+ </Box>
327
+ );
328
+ };
@@ -0,0 +1,62 @@
1
+ import React from "react";
2
+ import { Box, Text } from "ink";
3
+ import type { MemoryBlock } from "wave-agent-sdk";
4
+
5
+ interface MemoryDisplayProps {
6
+ block: MemoryBlock;
7
+ }
8
+
9
+ export const MemoryDisplay: React.FC<MemoryDisplayProps> = ({ block }) => {
10
+ const { content, isSuccess, memoryType, storagePath } = block;
11
+
12
+ const getStatusIcon = () => {
13
+ return isSuccess ? "💾" : "⚠️";
14
+ };
15
+
16
+ const getStatusColor = () => {
17
+ return isSuccess ? "green" : "red";
18
+ };
19
+
20
+ const getStatusText = () => {
21
+ return isSuccess ? "Added to memory" : "Failed to add memory";
22
+ };
23
+
24
+ const getStorageText = () => {
25
+ if (!isSuccess) return null;
26
+
27
+ if (memoryType === "user") {
28
+ return `Memory saved to ${storagePath || "user-memory.md"}`;
29
+ } else {
30
+ return `Memory saved to ${storagePath || "WAVE.md"}`;
31
+ }
32
+ };
33
+
34
+ return (
35
+ <Box flexDirection="column">
36
+ <Box>
37
+ <Text color={getStatusColor()}>{getStatusIcon()} </Text>
38
+ <Text color={getStatusColor()}>{getStatusText()}</Text>
39
+ </Box>
40
+
41
+ {content && (
42
+ <Box marginTop={1} paddingLeft={2}>
43
+ <Box
44
+ borderLeft
45
+ borderColor={isSuccess ? "green" : "red"}
46
+ paddingLeft={1}
47
+ >
48
+ <Text color="gray">{content}</Text>
49
+ </Box>
50
+ </Box>
51
+ )}
52
+
53
+ {isSuccess && (
54
+ <Box paddingLeft={2} marginTop={1}>
55
+ <Text color="yellow" dimColor>
56
+ {getStorageText()}
57
+ </Text>
58
+ </Box>
59
+ )}
60
+ </Box>
61
+ );
62
+ };
@@ -0,0 +1,96 @@
1
+ import React, { useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+
4
+ export interface MemoryTypeSelectorProps {
5
+ message: string;
6
+ onSelect: (type: "project" | "user") => void;
7
+ onCancel: () => void;
8
+ }
9
+
10
+ export const MemoryTypeSelector: React.FC<MemoryTypeSelectorProps> = ({
11
+ message,
12
+ onSelect,
13
+ onCancel,
14
+ }) => {
15
+ const [selectedIndex, setSelectedIndex] = useState(0);
16
+
17
+ const options = [
18
+ {
19
+ type: "project" as const,
20
+ label: "Project Memory",
21
+ description: "Save to current project (WAVE.md)",
22
+ },
23
+ {
24
+ type: "user" as const,
25
+ label: "User Memory",
26
+ description: "Save to user global memory",
27
+ },
28
+ ];
29
+
30
+ useInput((input, key) => {
31
+ if (key.return) {
32
+ const selectedOption = options[selectedIndex];
33
+ onSelect(selectedOption.type);
34
+ return;
35
+ }
36
+
37
+ if (key.escape) {
38
+ onCancel();
39
+ return;
40
+ }
41
+
42
+ if (key.upArrow) {
43
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
44
+ return;
45
+ }
46
+
47
+ if (key.downArrow) {
48
+ setSelectedIndex(Math.min(options.length - 1, selectedIndex + 1));
49
+ return;
50
+ }
51
+ });
52
+
53
+ return (
54
+ <Box
55
+ flexDirection="column"
56
+ borderStyle="single"
57
+ borderColor="green"
58
+ padding={1}
59
+ gap={1}
60
+ marginBottom={1}
61
+ >
62
+ <Box>
63
+ <Text color="green" bold>
64
+ Save Memory: "{message.substring(1).trim()}"
65
+ </Text>
66
+ </Box>
67
+
68
+ <Text color="gray">Choose where to save this memory:</Text>
69
+
70
+ {options.map((option, index) => (
71
+ <Box key={option.type} flexDirection="column">
72
+ <Text
73
+ color={index === selectedIndex ? "black" : "white"}
74
+ backgroundColor={index === selectedIndex ? "green" : undefined}
75
+ bold={index === selectedIndex}
76
+ >
77
+ {option.label}
78
+ </Text>
79
+ {index === selectedIndex && (
80
+ <Box marginLeft={2}>
81
+ <Text color="gray" dimColor>
82
+ {option.description}
83
+ </Text>
84
+ </Box>
85
+ )}
86
+ </Box>
87
+ ))}
88
+
89
+ <Box>
90
+ <Text dimColor>
91
+ Use ↑↓ to navigate, Enter to select, Escape to cancel
92
+ </Text>
93
+ </Box>
94
+ </Box>
95
+ );
96
+ };
@@ -0,0 +1,215 @@
1
+ import React, { useMemo } from "react";
2
+ import { Box, Text } from "ink";
3
+ import type { Message } from "wave-agent-sdk";
4
+ import { DiffViewer } from "./DiffViewer.js";
5
+ import { CommandOutputDisplay } from "./CommandOutputDisplay.js";
6
+ import { ToolResultDisplay } from "./ToolResultDisplay.js";
7
+ import { MemoryDisplay } from "./MemoryDisplay.js";
8
+ import { CompressDisplay } from "./CompressDisplay.js";
9
+ import { usePagination } from "../hooks/usePagination.js";
10
+
11
+ // Function to render a single message
12
+ const renderMessageItem = (
13
+ message: Message,
14
+ originalIndex: number,
15
+ isExpanded: boolean,
16
+ previousMessage?: Message,
17
+ ) => {
18
+ const shouldShowHeader = previousMessage?.role !== message.role;
19
+
20
+ return (
21
+ <Box key={`message-${originalIndex}`} flexDirection="column" marginTop={1}>
22
+ {shouldShowHeader && (
23
+ <Box>
24
+ <Text color={message.role === "user" ? "cyan" : "green"} bold>
25
+ {message.role === "user" ? "👤 You" : "🤖 Assistant"}
26
+ <Text color="gray" dimColor>
27
+ {" "}
28
+ #{originalIndex + 1}
29
+ </Text>
30
+ </Text>
31
+ </Box>
32
+ )}
33
+
34
+ <Box
35
+ marginLeft={2}
36
+ flexDirection="column"
37
+ gap={1}
38
+ marginTop={shouldShowHeader ? 1 : 0}
39
+ >
40
+ {message.blocks.map((block, blockIndex) => (
41
+ <Box key={blockIndex}>
42
+ {block.type === "text" && block.content.trim() && (
43
+ <Box>
44
+ <Text>{block.content}</Text>
45
+ </Box>
46
+ )}
47
+
48
+ {block.type === "error" && (
49
+ <Box>
50
+ <Text color="red">❌ Error: {block.content}</Text>
51
+ </Box>
52
+ )}
53
+
54
+ {block.type === "diff" && (
55
+ <DiffViewer block={block} isExpanded={isExpanded} />
56
+ )}
57
+
58
+ {block.type === "command_output" && (
59
+ <CommandOutputDisplay block={block} isExpanded={isExpanded} />
60
+ )}
61
+
62
+ {block.type === "tool" && (
63
+ <ToolResultDisplay block={block} isExpanded={isExpanded} />
64
+ )}
65
+
66
+ {block.type === "image" && (
67
+ <Box>
68
+ <Text color="magenta" bold>
69
+ 📷 Image
70
+ </Text>
71
+ {block.imageUrls && block.imageUrls.length > 0 && (
72
+ <Text color="gray" dimColor>
73
+ {" "}
74
+ ({block.imageUrls.length})
75
+ </Text>
76
+ )}
77
+ </Box>
78
+ )}
79
+
80
+ {block.type === "memory" && <MemoryDisplay block={block} />}
81
+
82
+ {block.type === "compress" && (
83
+ <CompressDisplay block={block} isExpanded={isExpanded} />
84
+ )}
85
+
86
+ {block.type === "custom_command" && (
87
+ <Box>
88
+ <Text color="cyan" bold>
89
+
90
+ </Text>
91
+ <Text>{block.originalInput || `/${block.commandName}`}</Text>
92
+ </Box>
93
+ )}
94
+ </Box>
95
+ ))}
96
+ </Box>
97
+ </Box>
98
+ );
99
+ };
100
+
101
+ export interface MessageListProps {
102
+ messages: Message[];
103
+ isLoading?: boolean;
104
+ isCommandRunning?: boolean;
105
+ isCompressing?: boolean;
106
+ latestTotalTokens?: number;
107
+ isExpanded?: boolean;
108
+ }
109
+
110
+ export const MessageList: React.FC<MessageListProps> = ({
111
+ messages,
112
+ isLoading = false,
113
+ isCommandRunning = false,
114
+ isCompressing = false,
115
+ latestTotalTokens = 0,
116
+ isExpanded = false,
117
+ }) => {
118
+ // Use original messages for pagination calculation
119
+ const { displayInfo } = usePagination(messages);
120
+
121
+ // Get current page messages while preserving original index information
122
+ const currentMessagesWithIndex = useMemo(() => {
123
+ return messages
124
+ .slice(displayInfo.startIndex, displayInfo.endIndex)
125
+ .map((message, index) => ({
126
+ message,
127
+ originalIndex: displayInfo.startIndex + index,
128
+ }));
129
+ }, [messages, displayInfo.startIndex, displayInfo.endIndex]);
130
+
131
+ // Empty message state
132
+ if (messages.length === 0) {
133
+ return (
134
+ <Box flexDirection="column" paddingY={1}>
135
+ <Text color="gray">Welcome to WAVE Code Assistant!</Text>
136
+ </Box>
137
+ );
138
+ }
139
+
140
+ return (
141
+ <Box flexDirection="column">
142
+ {/* Message list */}
143
+ <Box flexDirection="column">
144
+ {currentMessagesWithIndex.map(({ message, originalIndex }) => {
145
+ // Get previous message
146
+ const previousMessage =
147
+ originalIndex > 0 ? messages[originalIndex - 1] : undefined;
148
+ return renderMessageItem(
149
+ message,
150
+ originalIndex,
151
+ isExpanded,
152
+ previousMessage,
153
+ );
154
+ })}
155
+ </Box>
156
+
157
+ {/* Loading state display - only show in non-expanded state */}
158
+ {!isExpanded && (isLoading || isCommandRunning || isCompressing) && (
159
+ <Box marginTop={1} flexDirection="column" gap={1}>
160
+ {isLoading && (
161
+ <Box>
162
+ <Text color="yellow">💭 AI is thinking... </Text>
163
+ <Text color="gray" dimColor>
164
+ {" "}
165
+ |{" "}
166
+ </Text>
167
+ <Text color="blue" bold>
168
+ {latestTotalTokens.toLocaleString()}
169
+ </Text>
170
+ <Text color="gray" dimColor>
171
+ {" "}
172
+ tokens |{" "}
173
+ </Text>
174
+ <Text color="red" bold>
175
+ Esc
176
+ </Text>
177
+ <Text color="gray" dimColor>
178
+ {" "}
179
+ to abort
180
+ </Text>
181
+ </Box>
182
+ )}
183
+ {isCommandRunning && (
184
+ <Text color="blue">🚀 Command is running...</Text>
185
+ )}
186
+ {isCompressing && (
187
+ <Text color="magenta">🗜️ Compressing message history...</Text>
188
+ )}
189
+ </Box>
190
+ )}
191
+
192
+ {/* Bottom info and shortcut key hints */}
193
+ {messages.length > 0 && (
194
+ <Box marginTop={1}>
195
+ <Box justifyContent="space-between" width="100%">
196
+ <Box>
197
+ <Text color="gray">
198
+ Messages {messages.length} Page {displayInfo.currentPage}/
199
+ {displayInfo.totalPages}
200
+ </Text>
201
+ <Text color="gray" dimColor>
202
+ {" "}
203
+ ← <Text color="cyan">Ctrl+U/D</Text> Navigate
204
+ </Text>
205
+ </Box>
206
+ <Text color="gray" dimColor>
207
+ <Text color="cyan">Ctrl+O</Text> Toggle{" "}
208
+ {isExpanded ? "Collapse" : "Expand"}
209
+ </Text>
210
+ </Box>
211
+ </Box>
212
+ )}
213
+ </Box>
214
+ );
215
+ };