@mmmbuto/nexuscli 0.8.7 β†’ 0.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,916 @@
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_