@mmmbuto/nexuscli 0.7.7 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -32
- package/bin/nexuscli.js +6 -6
- package/frontend/dist/assets/{index-CHOlrfA0.css → index-WfmfixF4.css} +1 -1
- package/frontend/dist/index.html +2 -2
- package/lib/server/.env.example +1 -1
- package/lib/server/lib/pty-adapter.js +1 -15
- package/lib/server/routes/codex.js +9 -2
- package/lib/server/routes/gemini.js +9 -3
- package/lib/server/routes/sessions.js +15 -0
- package/lib/server/server.js +9 -0
- package/lib/server/services/claude-wrapper.js +1 -11
- package/lib/server/services/codex-output-parser.js +0 -8
- package/lib/server/services/codex-wrapper.js +3 -3
- package/lib/server/services/context-bridge.js +143 -24
- package/lib/server/services/gemini-wrapper.js +4 -3
- package/lib/server/services/session-importer.js +155 -0
- package/lib/server/services/workspace-manager.js +2 -7
- package/lib/server/tests/performance.test.js +1 -1
- package/lib/server/tests/services.test.js +2 -2
- package/package.json +1 -1
- package/lib/server/db.js.old +0 -225
- package/lib/server/docs/API_WRAPPER_CONTRACT.md +0 -682
- package/lib/server/docs/ARCHITECTURE.md +0 -441
- package/lib/server/docs/DATABASE_SCHEMA.md +0 -783
- package/lib/server/docs/DESIGN_PRINCIPLES.md +0 -598
- package/lib/server/docs/NEXUSCHAT_ANALYSIS.md +0 -488
- package/lib/server/docs/PIPELINE_INTEGRATION.md +0 -636
- package/lib/server/docs/README.md +0 -272
- package/lib/server/docs/UI_DESIGN.md +0 -916
- package/lib/server/services/base-cli-wrapper.js +0 -137
- package/lib/server/services/cli-loader.js.backup +0 -446
- /package/frontend/dist/assets/{index-BAY_sRAu.js → index-BbBoc8w4.js} +0 -0
|
@@ -1,916 +0,0 @@
|
|
|
1
|
-
# NexusCLI Chat UI Design
|
|
2
|
-
|
|
3
|
-
**Version**: 0.1.0
|
|
4
|
-
**Created**: 2025-11-17
|
|
5
|
-
**Inspired by**: LibreChat UI + ChatGPT
|
|
6
|
-
**Focus**: CLI job execution with real-time status
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## 🎨 Design Philosophy
|
|
11
|
-
|
|
12
|
-
**LibreChat Style + CLI-Specific Features**
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
┌────────────────────────────────────────────────────────────┐
|
|
16
|
-
│ LibreChat Base CLI Enhancements │
|
|
17
|
-
│ ──────────────── ───────────────── │
|
|
18
|
-
│ • Chat bubbles • Job status line │
|
|
19
|
-
│ • Markdown rendering • Real-time progress │
|
|
20
|
-
│ • Dark theme • Exit code badges │
|
|
21
|
-
│ • Streaming text • Command preview │
|
|
22
|
-
│ • Code blocks • Collapsible output │
|
|
23
|
-
└────────────────────────────────────────────────────────────┘
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## 📐 Layout Structure
|
|
29
|
-
|
|
30
|
-
### Main Layout (LibreChat-inspired)
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
34
|
-
│ ┌──────────────┐ ┌──────────────────────────────────────┐│
|
|
35
|
-
│ │ │ │ NexusCLI [@] [⚙️] [user] ││
|
|
36
|
-
│ │ Conversations│ ├──────────────────────────────────────┤│
|
|
37
|
-
│ │ Sidebar │ │ ││
|
|
38
|
-
│ │ │ │ ┌────────────────────────────────┐ ││
|
|
39
|
-
│ │ + New Chat │ │ │ 🤖 System │ ││
|
|
40
|
-
│ │ │ │ │ 3 nodes online. Ready for │ ││
|
|
41
|
-
│ │ Today │ │ │ commands. │ ││
|
|
42
|
-
│ │ • Deploy... │ │ └────────────────────────────────┘ ││
|
|
43
|
-
│ │ • Check logs │ │ ││
|
|
44
|
-
│ │ │ │ ┌────────────────────────────────┐ ││
|
|
45
|
-
│ │ Yesterday │ │ │ 👤 You │ ││
|
|
46
|
-
│ │ • Fix nginx │ │ │ systemctl status nginx │ ││
|
|
47
|
-
│ │ │ │ └────────────────────────────────┘ ││
|
|
48
|
-
│ │ Last Week │ │ ││
|
|
49
|
-
│ │ • Update... │ │ ┌────────────────────────────────┐ ││
|
|
50
|
-
│ │ │ │ │ 🤖 NexusCLI │ ││
|
|
51
|
-
│ │ │ │ │ ▶ Executing on prod-001... │ ││
|
|
52
|
-
│ │ │ │ │ 🔧 bash: systemctl status... │ ││
|
|
53
|
-
│ │ │ │ │ │ ││
|
|
54
|
-
│ │ │ │ │ ``` │ ││
|
|
55
|
-
│ │ │ │ │ ● nginx.service - running │ ││
|
|
56
|
-
│ │ │ │ │ Active: active (running) │ ││
|
|
57
|
-
│ │ │ │ │ ``` │ ││
|
|
58
|
-
│ │ │ │ │ ✅ Exit code: 0 (1.2s) │ ││
|
|
59
|
-
│ │ │ │ └────────────────────────────────┘ ││
|
|
60
|
-
│ │ │ │ ││
|
|
61
|
-
│ │ │ │ ┌────────────────────────────────┐ ││
|
|
62
|
-
│ │ │ │ │ Type command or question... │ ││
|
|
63
|
-
│ │ │ │ │ [↑] │ ││
|
|
64
|
-
│ │ │ │ └────────────────────────────────┘ ││
|
|
65
|
-
│ └──────────────┘ └──────────────────────────────────────┘│
|
|
66
|
-
└─────────────────────────────────────────────────────────────┘
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
## 💬 Message Types
|
|
72
|
-
|
|
73
|
-
### 1. User Message (Command Input)
|
|
74
|
-
|
|
75
|
-
**Visual**:
|
|
76
|
-
```
|
|
77
|
-
┌────────────────────────────────────────────┐
|
|
78
|
-
│ 👤 You 10:30 AM │
|
|
79
|
-
│ systemctl status nginx │
|
|
80
|
-
└────────────────────────────────────────────┘
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
**Code**:
|
|
84
|
-
```jsx
|
|
85
|
-
<div className="message user-message">
|
|
86
|
-
<div className="message-header">
|
|
87
|
-
<span className="avatar">👤</span>
|
|
88
|
-
<span className="sender">You</span>
|
|
89
|
-
<span className="timestamp">10:30 AM</span>
|
|
90
|
-
</div>
|
|
91
|
-
<div className="message-content">
|
|
92
|
-
<code>systemctl status nginx</code>
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
### 2. Assistant Message (Job Execution)
|
|
100
|
-
|
|
101
|
-
#### a) **Queued State**
|
|
102
|
-
|
|
103
|
-
```
|
|
104
|
-
┌────────────────────────────────────────────┐
|
|
105
|
-
│ 🤖 NexusCLI 10:30 AM │
|
|
106
|
-
│ ⏱️ Queued on prod-001... │
|
|
107
|
-
└────────────────────────────────────────────┘
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
#### b) **Executing State** (Real-time Status)
|
|
111
|
-
|
|
112
|
-
```
|
|
113
|
-
┌────────────────────────────────────────────┐
|
|
114
|
-
│ 🤖 NexusCLI 10:30 AM │
|
|
115
|
-
│ ▶ Executing on prod-001... │
|
|
116
|
-
│ 🔧 bash: systemctl status nginx │
|
|
117
|
-
│ │
|
|
118
|
-
│ ● nginx.service - A high performance... │
|
|
119
|
-
│ Loaded: loaded (/lib/systemd/system... │
|
|
120
|
-
│ Active: active (running) since... │ ← Streaming output
|
|
121
|
-
└────────────────────────────────────────────┘
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
**Key Feature**: Status line updates in real-time
|
|
125
|
-
- `🔧 bash: systemctl status nginx` ← Shows what CLI is doing
|
|
126
|
-
- Updates as new events arrive via SSE
|
|
127
|
-
|
|
128
|
-
#### c) **Completed State**
|
|
129
|
-
|
|
130
|
-
```
|
|
131
|
-
┌────────────────────────────────────────────┐
|
|
132
|
-
│ 🤖 NexusCLI 10:30 AM │
|
|
133
|
-
│ ✅ Completed on prod-001 (exit: 0, 1.2s) │
|
|
134
|
-
│ │
|
|
135
|
-
│ ```bash │
|
|
136
|
-
│ ● nginx.service - A high performance... │
|
|
137
|
-
│ Loaded: loaded (/lib/systemd/system... │
|
|
138
|
-
│ Active: active (running) since Mon... │
|
|
139
|
-
│ Main PID: 1234 (nginx) │
|
|
140
|
-
│ ``` │
|
|
141
|
-
│ │
|
|
142
|
-
│ [📋 Copy] [↻ Re-run] [⋯ More] │
|
|
143
|
-
└────────────────────────────────────────────┘
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
**Features**:
|
|
147
|
-
- ✅ Success badge with exit code and duration
|
|
148
|
-
- Code block with syntax highlighting
|
|
149
|
-
- Action buttons (Copy, Re-run, More)
|
|
150
|
-
|
|
151
|
-
#### d) **Failed State**
|
|
152
|
-
|
|
153
|
-
```
|
|
154
|
-
┌────────────────────────────────────────────┐
|
|
155
|
-
│ 🤖 NexusCLI 10:30 AM │
|
|
156
|
-
│ ❌ Failed on prod-003 (exit: 3, 0.5s) │
|
|
157
|
-
│ │
|
|
158
|
-
│ ```bash │
|
|
159
|
-
│ ● nginx.service - inactive (dead) │
|
|
160
|
-
│ ``` │
|
|
161
|
-
│ │
|
|
162
|
-
│ stderr: │
|
|
163
|
-
│ ``` │
|
|
164
|
-
│ Failed to start nginx.service: Unit... │
|
|
165
|
-
│ ``` │
|
|
166
|
-
│ │
|
|
167
|
-
│ [🔍 Debug] [↻ Retry] [⋯ More] │
|
|
168
|
-
└────────────────────────────────────────────┘
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
---
|
|
172
|
-
|
|
173
|
-
### 3. Multi-Node Job (Batch Execution)
|
|
174
|
-
|
|
175
|
-
```
|
|
176
|
-
┌────────────────────────────────────────────┐
|
|
177
|
-
│ 🤖 NexusCLI 10:30 AM │
|
|
178
|
-
│ Executing on 3 nodes... │
|
|
179
|
-
│ │
|
|
180
|
-
│ [prod-001] ✅ nginx active (0.8s) │
|
|
181
|
-
│ [prod-002] ✅ nginx active (1.1s) │
|
|
182
|
-
│ [prod-003] ⚠️ nginx inactive (0.5s) │
|
|
183
|
-
│ │
|
|
184
|
-
│ 2/3 nodes healthy │
|
|
185
|
-
│ │
|
|
186
|
-
│ [📊 Details] [↻ Re-run Failed] │
|
|
187
|
-
└────────────────────────────────────────────┘
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
**Expandable Details**:
|
|
191
|
-
```
|
|
192
|
-
┌────────────────────────────────────────────┐
|
|
193
|
-
│ 🤖 NexusCLI 10:30 AM │
|
|
194
|
-
│ Executing on 3 nodes... [▼] │
|
|
195
|
-
│ │
|
|
196
|
-
│ ┌─ prod-001 ─────────────────────────┐ │
|
|
197
|
-
│ │ ✅ Exit: 0 (0.8s) │ │
|
|
198
|
-
│ │ ● nginx.service - running │ │
|
|
199
|
-
│ └────────────────────────────────────┘ │
|
|
200
|
-
│ │
|
|
201
|
-
│ ┌─ prod-002 ─────────────────────────┐ │
|
|
202
|
-
│ │ ✅ Exit: 0 (1.1s) │ │
|
|
203
|
-
│ │ ● nginx.service - running │ │
|
|
204
|
-
│ └────────────────────────────────────┘ │
|
|
205
|
-
│ │
|
|
206
|
-
│ ┌─ prod-003 ─────────────────────────┐ │
|
|
207
|
-
│ │ ❌ Exit: 3 (0.5s) │ │
|
|
208
|
-
│ │ ● nginx.service - inactive (dead) │ │
|
|
209
|
-
│ └────────────────────────────────────┘ │
|
|
210
|
-
└────────────────────────────────────────────┘
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
## 🎭 Status Indicators
|
|
216
|
-
|
|
217
|
-
### Icon Library (CLI-specific)
|
|
218
|
-
|
|
219
|
-
| State | Icon | Color | Meaning |
|
|
220
|
-
|-------|------|-------|---------|
|
|
221
|
-
| **Queued** | ⏱️ | Gray | Job waiting in queue |
|
|
222
|
-
| **Executing** | ▶ | Blue | Job currently running |
|
|
223
|
-
| **Tool execution** | 🔧 | Blue | Specific tool running |
|
|
224
|
-
| **Success** | ✅ | Green | Job completed successfully |
|
|
225
|
-
| **Failed** | ❌ | Red | Job failed (exit != 0) |
|
|
226
|
-
| **Warning** | ⚠️ | Yellow | Job completed with warnings |
|
|
227
|
-
| **Cancelled** | ⛔ | Gray | Job cancelled by user |
|
|
228
|
-
| **Streaming** | 💬 | Blue | Assistant streaming response |
|
|
229
|
-
| **Thinking** | 🤔 | Purple | Processing request |
|
|
230
|
-
|
|
231
|
-
### Status Line Format
|
|
232
|
-
|
|
233
|
-
**Pattern**: `[Icon] [Action] on [Node]... [Detail]`
|
|
234
|
-
|
|
235
|
-
**Examples**:
|
|
236
|
-
```
|
|
237
|
-
⏱️ Queued on prod-001...
|
|
238
|
-
▶ Executing on prod-001...
|
|
239
|
-
🔧 bash: systemctl status nginx
|
|
240
|
-
🔧 bash: journalctl -n 50
|
|
241
|
-
✅ Completed on prod-001 (exit: 0, 1.2s)
|
|
242
|
-
❌ Failed on prod-003 (exit: 3, 0.5s)
|
|
243
|
-
⚠️ Completed with warnings (exit: 0, 2.1s)
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
---
|
|
247
|
-
|
|
248
|
-
## 🎨 Styling (LibreChat-inspired)
|
|
249
|
-
|
|
250
|
-
### Color Palette
|
|
251
|
-
|
|
252
|
-
```css
|
|
253
|
-
:root {
|
|
254
|
-
/* Dark theme (default) */
|
|
255
|
-
--bg-primary: #0d0d0d; /* Main background */
|
|
256
|
-
--bg-secondary: #1a1a1a; /* Sidebar, cards */
|
|
257
|
-
--bg-tertiary: #2a2a2a; /* User message bg */
|
|
258
|
-
|
|
259
|
-
--text-primary: #ececec; /* Main text */
|
|
260
|
-
--text-secondary: #9b9b9b; /* Muted text */
|
|
261
|
-
--text-tertiary: #6e6e6e; /* Very muted */
|
|
262
|
-
|
|
263
|
-
--accent-primary: #10a37f; /* Success, primary actions */
|
|
264
|
-
--accent-secondary: #667eea; /* Links, highlights */
|
|
265
|
-
|
|
266
|
-
--border-color: #303030; /* Borders, dividers */
|
|
267
|
-
|
|
268
|
-
/* Status colors */
|
|
269
|
-
--status-success: #4ade80; /* Green */
|
|
270
|
-
--status-error: #ef4444; /* Red */
|
|
271
|
-
--status-warning: #fbbf24; /* Yellow */
|
|
272
|
-
--status-info: #3b82f6; /* Blue */
|
|
273
|
-
|
|
274
|
-
/* Code block */
|
|
275
|
-
--code-bg: #1e1e1e;
|
|
276
|
-
--code-text: #d4d4d4;
|
|
277
|
-
}
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
### Message Styling
|
|
281
|
-
|
|
282
|
-
```css
|
|
283
|
-
/* Message container */
|
|
284
|
-
.message {
|
|
285
|
-
display: flex;
|
|
286
|
-
flex-direction: column;
|
|
287
|
-
padding: 1.5rem;
|
|
288
|
-
margin-bottom: 1rem;
|
|
289
|
-
border-radius: 8px;
|
|
290
|
-
max-width: 100%;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/* User message */
|
|
294
|
-
.user-message {
|
|
295
|
-
background: var(--bg-tertiary);
|
|
296
|
-
align-self: flex-end;
|
|
297
|
-
max-width: 80%;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
.user-message .message-content {
|
|
301
|
-
font-family: 'Fira Code', monospace;
|
|
302
|
-
color: var(--text-primary);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/* Assistant message */
|
|
306
|
-
.assistant-message {
|
|
307
|
-
background: var(--bg-secondary);
|
|
308
|
-
border: 1px solid var(--border-color);
|
|
309
|
-
max-width: 100%;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/* Message header */
|
|
313
|
-
.message-header {
|
|
314
|
-
display: flex;
|
|
315
|
-
align-items: center;
|
|
316
|
-
gap: 0.5rem;
|
|
317
|
-
margin-bottom: 0.75rem;
|
|
318
|
-
font-size: 0.875rem;
|
|
319
|
-
color: var(--text-secondary);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
.avatar {
|
|
323
|
-
font-size: 1.25rem;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
.sender {
|
|
327
|
-
font-weight: 600;
|
|
328
|
-
color: var(--text-primary);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
.timestamp {
|
|
332
|
-
margin-left: auto;
|
|
333
|
-
font-size: 0.75rem;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/* Status line */
|
|
337
|
-
.status-line {
|
|
338
|
-
display: flex;
|
|
339
|
-
align-items: center;
|
|
340
|
-
gap: 0.5rem;
|
|
341
|
-
padding: 0.5rem 0.75rem;
|
|
342
|
-
background: rgba(59, 130, 246, 0.1);
|
|
343
|
-
border-left: 3px solid var(--status-info);
|
|
344
|
-
border-radius: 4px;
|
|
345
|
-
font-size: 0.875rem;
|
|
346
|
-
margin-bottom: 0.75rem;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
.status-line.success {
|
|
350
|
-
background: rgba(74, 222, 128, 0.1);
|
|
351
|
-
border-color: var(--status-success);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
.status-line.error {
|
|
355
|
-
background: rgba(239, 68, 68, 0.1);
|
|
356
|
-
border-color: var(--status-error);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
.status-line.warning {
|
|
360
|
-
background: rgba(251, 191, 36, 0.1);
|
|
361
|
-
border-color: var(--status-warning);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/* Code block */
|
|
365
|
-
.code-block {
|
|
366
|
-
background: var(--code-bg);
|
|
367
|
-
border-radius: 6px;
|
|
368
|
-
padding: 1rem;
|
|
369
|
-
overflow-x: auto;
|
|
370
|
-
margin: 0.5rem 0;
|
|
371
|
-
font-family: 'Fira Code', 'Consolas', monospace;
|
|
372
|
-
font-size: 0.875rem;
|
|
373
|
-
line-height: 1.5;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
.code-block pre {
|
|
377
|
-
margin: 0;
|
|
378
|
-
color: var(--code-text);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/* Action buttons */
|
|
382
|
-
.message-actions {
|
|
383
|
-
display: flex;
|
|
384
|
-
gap: 0.5rem;
|
|
385
|
-
margin-top: 0.75rem;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
.action-btn {
|
|
389
|
-
padding: 0.375rem 0.75rem;
|
|
390
|
-
background: transparent;
|
|
391
|
-
border: 1px solid var(--border-color);
|
|
392
|
-
border-radius: 4px;
|
|
393
|
-
color: var(--text-secondary);
|
|
394
|
-
font-size: 0.75rem;
|
|
395
|
-
cursor: pointer;
|
|
396
|
-
transition: all 0.2s;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
.action-btn:hover {
|
|
400
|
-
background: var(--bg-tertiary);
|
|
401
|
-
color: var(--text-primary);
|
|
402
|
-
}
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
---
|
|
406
|
-
|
|
407
|
-
## ⚛️ React Components
|
|
408
|
-
|
|
409
|
-
### 1. Message Component
|
|
410
|
-
|
|
411
|
-
```jsx
|
|
412
|
-
// components/Message.jsx
|
|
413
|
-
import React, { useState } from 'react';
|
|
414
|
-
import ReactMarkdown from 'react-markdown';
|
|
415
|
-
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
416
|
-
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
|
417
|
-
|
|
418
|
-
function Message({ message, streaming = false }) {
|
|
419
|
-
const [expanded, setExpanded] = useState(false);
|
|
420
|
-
|
|
421
|
-
const { role, content, status, metadata } = message;
|
|
422
|
-
|
|
423
|
-
return (
|
|
424
|
-
<div className={`message ${role}-message`}>
|
|
425
|
-
<div className="message-header">
|
|
426
|
-
<span className="avatar">{role === 'user' ? '👤' : '🤖'}</span>
|
|
427
|
-
<span className="sender">{role === 'user' ? 'You' : 'NexusCLI'}</span>
|
|
428
|
-
<span className="timestamp">
|
|
429
|
-
{new Date(message.created_at).toLocaleTimeString()}
|
|
430
|
-
</span>
|
|
431
|
-
</div>
|
|
432
|
-
|
|
433
|
-
{/* Status line (for assistant messages with jobs) */}
|
|
434
|
-
{role === 'assistant' && status && (
|
|
435
|
-
<StatusLine status={status} metadata={metadata} />
|
|
436
|
-
)}
|
|
437
|
-
|
|
438
|
-
{/* Message content */}
|
|
439
|
-
<div className="message-content">
|
|
440
|
-
<ReactMarkdown
|
|
441
|
-
components={{
|
|
442
|
-
code({ node, inline, className, children, ...props }) {
|
|
443
|
-
const match = /language-(\w+)/.exec(className || '');
|
|
444
|
-
return !inline && match ? (
|
|
445
|
-
<SyntaxHighlighter
|
|
446
|
-
style={vscDarkPlus}
|
|
447
|
-
language={match[1]}
|
|
448
|
-
PreTag="div"
|
|
449
|
-
{...props}
|
|
450
|
-
>
|
|
451
|
-
{String(children).replace(/\n$/, '')}
|
|
452
|
-
</SyntaxHighlighter>
|
|
453
|
-
) : (
|
|
454
|
-
<code className={className} {...props}>
|
|
455
|
-
{children}
|
|
456
|
-
</code>
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
}}
|
|
460
|
-
>
|
|
461
|
-
{content}
|
|
462
|
-
</ReactMarkdown>
|
|
463
|
-
</div>
|
|
464
|
-
|
|
465
|
-
{/* Streaming indicator */}
|
|
466
|
-
{streaming && (
|
|
467
|
-
<div className="streaming-indicator">
|
|
468
|
-
<span className="dot"></span>
|
|
469
|
-
<span className="dot"></span>
|
|
470
|
-
<span className="dot"></span>
|
|
471
|
-
</div>
|
|
472
|
-
)}
|
|
473
|
-
|
|
474
|
-
{/* Action buttons (for completed jobs) */}
|
|
475
|
-
{role === 'assistant' && status?.state === 'completed' && (
|
|
476
|
-
<MessageActions message={message} />
|
|
477
|
-
)}
|
|
478
|
-
</div>
|
|
479
|
-
);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
export default Message;
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
---
|
|
486
|
-
|
|
487
|
-
### 2. StatusLine Component
|
|
488
|
-
|
|
489
|
-
```jsx
|
|
490
|
-
// components/StatusLine.jsx
|
|
491
|
-
import React from 'react';
|
|
492
|
-
|
|
493
|
-
const STATUS_CONFIG = {
|
|
494
|
-
queued: {
|
|
495
|
-
icon: '⏱️',
|
|
496
|
-
label: 'Queued',
|
|
497
|
-
className: 'info'
|
|
498
|
-
},
|
|
499
|
-
executing: {
|
|
500
|
-
icon: '▶',
|
|
501
|
-
label: 'Executing',
|
|
502
|
-
className: 'info'
|
|
503
|
-
},
|
|
504
|
-
completed: {
|
|
505
|
-
icon: '✅',
|
|
506
|
-
label: 'Completed',
|
|
507
|
-
className: 'success'
|
|
508
|
-
},
|
|
509
|
-
failed: {
|
|
510
|
-
icon: '❌',
|
|
511
|
-
label: 'Failed',
|
|
512
|
-
className: 'error'
|
|
513
|
-
},
|
|
514
|
-
cancelled: {
|
|
515
|
-
icon: '⛔',
|
|
516
|
-
label: 'Cancelled',
|
|
517
|
-
className: 'warning'
|
|
518
|
-
}
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
function StatusLine({ status, metadata }) {
|
|
522
|
-
const config = STATUS_CONFIG[status.state] || STATUS_CONFIG.executing;
|
|
523
|
-
|
|
524
|
-
// Build status message
|
|
525
|
-
let message = '';
|
|
526
|
-
|
|
527
|
-
if (status.state === 'queued') {
|
|
528
|
-
message = `Queued on ${status.nodeId}...`;
|
|
529
|
-
} else if (status.state === 'executing') {
|
|
530
|
-
message = `Executing on ${status.nodeId}...`;
|
|
531
|
-
|
|
532
|
-
// Add tool execution detail
|
|
533
|
-
if (status.currentTool) {
|
|
534
|
-
message += `\n 🔧 ${status.currentTool}: ${status.command}`;
|
|
535
|
-
}
|
|
536
|
-
} else if (status.state === 'completed') {
|
|
537
|
-
const duration = ((metadata?.duration || 0) / 1000).toFixed(1);
|
|
538
|
-
message = `Completed on ${status.nodeId} (exit: ${metadata?.exitCode || 0}, ${duration}s)`;
|
|
539
|
-
} else if (status.state === 'failed') {
|
|
540
|
-
const duration = ((metadata?.duration || 0) / 1000).toFixed(1);
|
|
541
|
-
message = `Failed on ${status.nodeId} (exit: ${metadata?.exitCode || 1}, ${duration}s)`;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return (
|
|
545
|
-
<div className={`status-line ${config.className}`}>
|
|
546
|
-
<span className="status-icon">{config.icon}</span>
|
|
547
|
-
<span className="status-message">{message}</span>
|
|
548
|
-
</div>
|
|
549
|
-
);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
export default StatusLine;
|
|
553
|
-
```
|
|
554
|
-
|
|
555
|
-
---
|
|
556
|
-
|
|
557
|
-
### 3. MessageActions Component
|
|
558
|
-
|
|
559
|
-
```jsx
|
|
560
|
-
// components/MessageActions.jsx
|
|
561
|
-
import React from 'react';
|
|
562
|
-
|
|
563
|
-
function MessageActions({ message }) {
|
|
564
|
-
const handleCopy = () => {
|
|
565
|
-
navigator.clipboard.writeText(message.content);
|
|
566
|
-
// Show toast notification
|
|
567
|
-
};
|
|
568
|
-
|
|
569
|
-
const handleRerun = () => {
|
|
570
|
-
// Re-execute the job
|
|
571
|
-
console.log('Re-run job:', message.metadata?.jobId);
|
|
572
|
-
};
|
|
573
|
-
|
|
574
|
-
const handleDetails = () => {
|
|
575
|
-
// Show job details modal
|
|
576
|
-
console.log('Show details:', message.metadata?.jobId);
|
|
577
|
-
};
|
|
578
|
-
|
|
579
|
-
return (
|
|
580
|
-
<div className="message-actions">
|
|
581
|
-
<button className="action-btn" onClick={handleCopy}>
|
|
582
|
-
📋 Copy
|
|
583
|
-
</button>
|
|
584
|
-
<button className="action-btn" onClick={handleRerun}>
|
|
585
|
-
↻ Re-run
|
|
586
|
-
</button>
|
|
587
|
-
<button className="action-btn" onClick={handleDetails}>
|
|
588
|
-
⋯ More
|
|
589
|
-
</button>
|
|
590
|
-
</div>
|
|
591
|
-
);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
export default MessageActions;
|
|
595
|
-
```
|
|
596
|
-
|
|
597
|
-
---
|
|
598
|
-
|
|
599
|
-
## 🌊 SSE Integration (Real-time Updates)
|
|
600
|
-
|
|
601
|
-
### Frontend EventSource Handler
|
|
602
|
-
|
|
603
|
-
```jsx
|
|
604
|
-
// hooks/useJobStream.js
|
|
605
|
-
import { useState, useEffect } from 'react';
|
|
606
|
-
|
|
607
|
-
export function useJobStream(jobId) {
|
|
608
|
-
const [status, setStatus] = useState({ state: 'queued' });
|
|
609
|
-
const [output, setOutput] = useState('');
|
|
610
|
-
const [completed, setCompleted] = useState(false);
|
|
611
|
-
|
|
612
|
-
useEffect(() => {
|
|
613
|
-
if (!jobId) return;
|
|
614
|
-
|
|
615
|
-
const eventSource = new EventSource(`/api/v1/jobs/${jobId}/stream`);
|
|
616
|
-
|
|
617
|
-
eventSource.onmessage = (event) => {
|
|
618
|
-
const data = JSON.parse(event.data);
|
|
619
|
-
|
|
620
|
-
if (data.type === 'status') {
|
|
621
|
-
// Update status line
|
|
622
|
-
setStatus({
|
|
623
|
-
state: data.category,
|
|
624
|
-
nodeId: data.nodeId,
|
|
625
|
-
currentTool: data.tool,
|
|
626
|
-
command: data.message,
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
if (data.type === 'output_chunk') {
|
|
631
|
-
// Append to output (streaming)
|
|
632
|
-
setOutput(prev => prev + data.text);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
if (data.type === 'done') {
|
|
636
|
-
// Mark as completed
|
|
637
|
-
setCompleted(true);
|
|
638
|
-
setStatus(prev => ({
|
|
639
|
-
...prev,
|
|
640
|
-
state: data.exitCode === 0 ? 'completed' : 'failed'
|
|
641
|
-
}));
|
|
642
|
-
eventSource.close();
|
|
643
|
-
}
|
|
644
|
-
};
|
|
645
|
-
|
|
646
|
-
eventSource.onerror = () => {
|
|
647
|
-
eventSource.close();
|
|
648
|
-
};
|
|
649
|
-
|
|
650
|
-
return () => {
|
|
651
|
-
eventSource.close();
|
|
652
|
-
};
|
|
653
|
-
}, [jobId]);
|
|
654
|
-
|
|
655
|
-
return { status, output, completed };
|
|
656
|
-
}
|
|
657
|
-
```
|
|
658
|
-
|
|
659
|
-
### Usage in Message Component
|
|
660
|
-
|
|
661
|
-
```jsx
|
|
662
|
-
// In assistant message rendering
|
|
663
|
-
function AssistantMessage({ message }) {
|
|
664
|
-
const jobId = message.metadata?.jobId;
|
|
665
|
-
const { status, output, completed } = useJobStream(jobId);
|
|
666
|
-
|
|
667
|
-
return (
|
|
668
|
-
<div className="assistant-message">
|
|
669
|
-
<StatusLine status={status} metadata={message.metadata} />
|
|
670
|
-
|
|
671
|
-
{/* Show streaming output */}
|
|
672
|
-
{output && (
|
|
673
|
-
<pre className="code-block">{output}</pre>
|
|
674
|
-
)}
|
|
675
|
-
|
|
676
|
-
{/* Show final message when completed */}
|
|
677
|
-
{completed && message.content && (
|
|
678
|
-
<ReactMarkdown>{message.content}</ReactMarkdown>
|
|
679
|
-
)}
|
|
680
|
-
</div>
|
|
681
|
-
);
|
|
682
|
-
}
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
---
|
|
686
|
-
|
|
687
|
-
## 📱 Mobile Responsive
|
|
688
|
-
|
|
689
|
-
### Breakpoints
|
|
690
|
-
|
|
691
|
-
```css
|
|
692
|
-
/* Mobile (Termux on phone) */
|
|
693
|
-
@media (max-width: 768px) {
|
|
694
|
-
.chat-layout {
|
|
695
|
-
flex-direction: column;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
.sidebar {
|
|
699
|
-
position: fixed;
|
|
700
|
-
left: -300px;
|
|
701
|
-
transition: left 0.3s;
|
|
702
|
-
z-index: 100;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
.sidebar.open {
|
|
706
|
-
left: 0;
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
.message {
|
|
710
|
-
padding: 1rem;
|
|
711
|
-
font-size: 14px;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
.user-message {
|
|
715
|
-
max-width: 95%;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
.code-block {
|
|
719
|
-
font-size: 12px;
|
|
720
|
-
overflow-x: scroll;
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
/* Tablet */
|
|
725
|
-
@media (min-width: 769px) and (max-width: 1024px) {
|
|
726
|
-
.sidebar {
|
|
727
|
-
width: 200px;
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
/* Desktop */
|
|
732
|
-
@media (min-width: 1025px) {
|
|
733
|
-
.sidebar {
|
|
734
|
-
width: 260px;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
.chat-container {
|
|
738
|
-
max-width: 900px;
|
|
739
|
-
margin: 0 auto;
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
```
|
|
743
|
-
|
|
744
|
-
---
|
|
745
|
-
|
|
746
|
-
## 🎯 Example: Complete Job Flow
|
|
747
|
-
|
|
748
|
-
### 1. User sends command
|
|
749
|
-
|
|
750
|
-
```
|
|
751
|
-
┌────────────────────────────────────────────┐
|
|
752
|
-
│ 👤 You 10:30 AM │
|
|
753
|
-
│ systemctl restart nginx │
|
|
754
|
-
└────────────────────────────────────────────┘
|
|
755
|
-
```
|
|
756
|
-
|
|
757
|
-
### 2. Assistant queues job
|
|
758
|
-
|
|
759
|
-
```
|
|
760
|
-
┌────────────────────────────────────────────┐
|
|
761
|
-
│ 🤖 NexusCLI 10:30 AM │
|
|
762
|
-
│ ⏱️ Queued on prod-001... │
|
|
763
|
-
└────────────────────────────────────────────┘
|
|
764
|
-
```
|
|
765
|
-
|
|
766
|
-
### 3. Job starts executing (status updates)
|
|
767
|
-
|
|
768
|
-
```
|
|
769
|
-
┌────────────────────────────────────────────┐
|
|
770
|
-
│ 🤖 NexusCLI 10:30 AM │
|
|
771
|
-
│ ▶ Executing on prod-001... │
|
|
772
|
-
│ 🔧 bash: systemctl restart nginx │
|
|
773
|
-
└────────────────────────────────────────────┘
|
|
774
|
-
```
|
|
775
|
-
|
|
776
|
-
### 4. Output streams in
|
|
777
|
-
|
|
778
|
-
```
|
|
779
|
-
┌────────────────────────────────────────────┐
|
|
780
|
-
│ 🤖 NexusCLI 10:30 AM │
|
|
781
|
-
│ ▶ Executing on prod-001... │
|
|
782
|
-
│ 🔧 bash: systemctl restart nginx │
|
|
783
|
-
│ │
|
|
784
|
-
│ Stopping nginx.service... │ ← Streaming
|
|
785
|
-
│ Starting nginx.service... │
|
|
786
|
-
│ Started nginx.service │
|
|
787
|
-
└────────────────────────────────────────────┘
|
|
788
|
-
```
|
|
789
|
-
|
|
790
|
-
### 5. Job completes (final state)
|
|
791
|
-
|
|
792
|
-
```
|
|
793
|
-
┌────────────────────────────────────────────┐
|
|
794
|
-
│ 🤖 NexusCLI 10:30 AM │
|
|
795
|
-
│ ✅ Completed on prod-001 (exit: 0, 2.1s) │
|
|
796
|
-
│ │
|
|
797
|
-
│ ```bash │
|
|
798
|
-
│ Stopping nginx.service... │
|
|
799
|
-
│ Starting nginx.service... │
|
|
800
|
-
│ Started nginx.service │
|
|
801
|
-
│ ``` │
|
|
802
|
-
│ │
|
|
803
|
-
│ nginx restarted successfully. │
|
|
804
|
-
│ │
|
|
805
|
-
│ [📋 Copy] [↻ Re-run] [⋯ More] │
|
|
806
|
-
└────────────────────────────────────────────┘
|
|
807
|
-
```
|
|
808
|
-
|
|
809
|
-
---
|
|
810
|
-
|
|
811
|
-
## 🎨 Animation & UX Polish
|
|
812
|
-
|
|
813
|
-
### Streaming Dots Animation
|
|
814
|
-
|
|
815
|
-
```css
|
|
816
|
-
@keyframes blink {
|
|
817
|
-
0%, 100% { opacity: 0.2; }
|
|
818
|
-
50% { opacity: 1; }
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
.streaming-indicator {
|
|
822
|
-
display: flex;
|
|
823
|
-
gap: 0.25rem;
|
|
824
|
-
align-items: center;
|
|
825
|
-
margin-top: 0.5rem;
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
.streaming-indicator .dot {
|
|
829
|
-
width: 6px;
|
|
830
|
-
height: 6px;
|
|
831
|
-
border-radius: 50%;
|
|
832
|
-
background: var(--accent-secondary);
|
|
833
|
-
animation: blink 1.4s infinite;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
.streaming-indicator .dot:nth-child(2) {
|
|
837
|
-
animation-delay: 0.2s;
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
.streaming-indicator .dot:nth-child(3) {
|
|
841
|
-
animation-delay: 0.4s;
|
|
842
|
-
}
|
|
843
|
-
```
|
|
844
|
-
|
|
845
|
-
### Status Line Pulse
|
|
846
|
-
|
|
847
|
-
```css
|
|
848
|
-
@keyframes pulse {
|
|
849
|
-
0%, 100% { opacity: 1; }
|
|
850
|
-
50% { opacity: 0.7; }
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
.status-line.executing .status-icon {
|
|
854
|
-
animation: pulse 2s infinite;
|
|
855
|
-
}
|
|
856
|
-
```
|
|
857
|
-
|
|
858
|
-
### Smooth Transitions
|
|
859
|
-
|
|
860
|
-
```css
|
|
861
|
-
.message {
|
|
862
|
-
animation: slideIn 0.3s ease-out;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
@keyframes slideIn {
|
|
866
|
-
from {
|
|
867
|
-
opacity: 0;
|
|
868
|
-
transform: translateY(10px);
|
|
869
|
-
}
|
|
870
|
-
to {
|
|
871
|
-
opacity: 1;
|
|
872
|
-
transform: translateY(0);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
```
|
|
876
|
-
|
|
877
|
-
---
|
|
878
|
-
|
|
879
|
-
## 📚 Related Documents
|
|
880
|
-
|
|
881
|
-
- [Design Principles](./DESIGN_PRINCIPLES.md) - Three pillars
|
|
882
|
-
- [Database Schema](./DATABASE_SCHEMA.md) - Chat/jobs storage
|
|
883
|
-
- [API Contract](./API_WRAPPER_CONTRACT.md) - SSE events spec
|
|
884
|
-
|
|
885
|
-
---
|
|
886
|
-
|
|
887
|
-
## 🚀 Implementation Checklist
|
|
888
|
-
|
|
889
|
-
### Phase 1: Basic Chat UI
|
|
890
|
-
- [ ] Message component (user + assistant)
|
|
891
|
-
- [ ] Markdown rendering
|
|
892
|
-
- [ ] Code block syntax highlighting
|
|
893
|
-
- [ ] Conversation sidebar
|
|
894
|
-
|
|
895
|
-
### Phase 2: Job Status
|
|
896
|
-
- [ ] StatusLine component
|
|
897
|
-
- [ ] Real-time SSE integration
|
|
898
|
-
- [ ] Status updates (queued → executing → done)
|
|
899
|
-
- [ ] Tool execution indicator
|
|
900
|
-
|
|
901
|
-
### Phase 3: Output Streaming
|
|
902
|
-
- [ ] Incremental output rendering
|
|
903
|
-
- [ ] Code block formatting
|
|
904
|
-
- [ ] Collapsible long output
|
|
905
|
-
- [ ] Copy to clipboard
|
|
906
|
-
|
|
907
|
-
### Phase 4: Actions & UX
|
|
908
|
-
- [ ] Re-run button
|
|
909
|
-
- [ ] Job details modal
|
|
910
|
-
- [ ] Multi-node batch display
|
|
911
|
-
- [ ] Mobile responsive layout
|
|
912
|
-
|
|
913
|
-
---
|
|
914
|
-
|
|
915
|
-
_UI Design for NexusCLI - LibreChat Style + CLI Enhancements_
|
|
916
|
-
_Generated by Claude Code (Sonnet 4.5) - 2025-11-17_
|