4runr-os 2.4.0 → 2.4.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.
- package/mk3-tui/src/app.rs +111 -47
- package/mk3-tui/src/main.rs +42 -7
- package/mk3-tui/src/screens/mod.rs +3 -1
- package/mk3-tui/src/storage/cache.rs +213 -213
- package/mk3-tui/src/storage/mod.rs +6 -6
- package/mk3-tui/src/ui/agent_builder.rs +921 -921
- package/mk3-tui/src/ui/agent_list.rs +247 -0
- package/mk3-tui/src/ui/help.rs +353 -0
- package/mk3-tui/src/ui/layout.rs +3 -3
- package/mk3-tui/src/ui/mod.rs +2 -0
- package/mk3-tui/src/ui/run_manager.rs +676 -676
- package/mk3-tui/src/ui/settings.rs +362 -362
- package/mk3-tui/src/websocket.rs +303 -303
- package/package.json +2 -1
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/// Agent List Screen
|
|
2
|
+
/// View and manage AI agents
|
|
3
|
+
|
|
4
|
+
use ratatui::prelude::*;
|
|
5
|
+
use ratatui::widgets::{Block, Borders, Paragraph, Wrap, Table, Row, Cell};
|
|
6
|
+
use crate::app::{AppState, AgentInfo};
|
|
7
|
+
|
|
8
|
+
// === 4RUNR BRAND COLORS ===
|
|
9
|
+
const BRAND_PURPLE: Color = Color::Rgb(138, 43, 226);
|
|
10
|
+
const CYBER_CYAN: Color = Color::Rgb(0, 255, 255);
|
|
11
|
+
const NEON_GREEN: Color = Color::Rgb(57, 255, 20);
|
|
12
|
+
const TEXT_PRIMARY: Color = Color::Rgb(230, 230, 240);
|
|
13
|
+
const TEXT_DIM: Color = Color::Rgb(100, 100, 120);
|
|
14
|
+
const TEXT_MUTED: Color = Color::Rgb(60, 60, 80);
|
|
15
|
+
const BG_PANEL: Color = Color::Rgb(18, 18, 25);
|
|
16
|
+
|
|
17
|
+
/// Render agent list screen
|
|
18
|
+
pub fn render(f: &mut Frame, state: &AppState) {
|
|
19
|
+
let area = f.size();
|
|
20
|
+
|
|
21
|
+
if state.agent_list.detail_view.is_some() {
|
|
22
|
+
// Render list view first (as base)
|
|
23
|
+
render_list_view(f, area, state);
|
|
24
|
+
// Then render detail popup on top
|
|
25
|
+
render_detail_popup(f, area, state);
|
|
26
|
+
} else {
|
|
27
|
+
// Render list view only
|
|
28
|
+
render_list_view(f, area, state);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn render_list_view(f: &mut Frame, area: Rect, state: &AppState) {
|
|
33
|
+
use ratatui::layout::{Constraint, Direction, Layout};
|
|
34
|
+
|
|
35
|
+
// Split into header, content, footer
|
|
36
|
+
let chunks = Layout::default()
|
|
37
|
+
.direction(Direction::Vertical)
|
|
38
|
+
.constraints([
|
|
39
|
+
Constraint::Length(3), // Header
|
|
40
|
+
Constraint::Min(0), // Content
|
|
41
|
+
Constraint::Length(3), // Footer
|
|
42
|
+
])
|
|
43
|
+
.split(area);
|
|
44
|
+
|
|
45
|
+
// === HEADER ===
|
|
46
|
+
let agent_count = state.agent_list.agents.len();
|
|
47
|
+
let header_block = Block::default()
|
|
48
|
+
.title(format!(" 🤖 Agent List ({} agents) ", agent_count))
|
|
49
|
+
.title_style(Style::default().fg(BRAND_PURPLE).add_modifier(Modifier::BOLD))
|
|
50
|
+
.borders(Borders::ALL)
|
|
51
|
+
.border_style(Style::default().fg(CYBER_CYAN))
|
|
52
|
+
.style(Style::default().bg(BG_PANEL));
|
|
53
|
+
|
|
54
|
+
f.render_widget(header_block, chunks[0]);
|
|
55
|
+
|
|
56
|
+
// === CONTENT: Agent Table ===
|
|
57
|
+
let content_block = Block::default()
|
|
58
|
+
.borders(Borders::ALL)
|
|
59
|
+
.border_style(Style::default().fg(TEXT_MUTED))
|
|
60
|
+
.style(Style::default().bg(BG_PANEL));
|
|
61
|
+
|
|
62
|
+
f.render_widget(content_block.clone(), chunks[1]);
|
|
63
|
+
|
|
64
|
+
let table_area = content_block.inner(chunks[1]);
|
|
65
|
+
|
|
66
|
+
// Create table rows
|
|
67
|
+
let rows: Vec<Row> = state.agent_list.agents.iter().enumerate().map(|(i, agent)| {
|
|
68
|
+
let is_selected = i == state.agent_list.selected_index;
|
|
69
|
+
let style = if is_selected {
|
|
70
|
+
Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)
|
|
71
|
+
} else {
|
|
72
|
+
Style::default().fg(TEXT_PRIMARY)
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
let name = if is_selected {
|
|
76
|
+
format!("▶ {}", agent.name)
|
|
77
|
+
} else {
|
|
78
|
+
format!(" {}", agent.name)
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
let desc = agent.description.as_deref().unwrap_or("No description")
|
|
82
|
+
.chars().take(30).collect::<String>();
|
|
83
|
+
let model = agent.model.chars().take(15).collect::<String>();
|
|
84
|
+
let provider = agent.provider.chars().take(10).collect::<String>();
|
|
85
|
+
|
|
86
|
+
Row::new(vec![
|
|
87
|
+
Cell::from(name).style(style),
|
|
88
|
+
Cell::from(desc).style(style),
|
|
89
|
+
Cell::from(model).style(style),
|
|
90
|
+
Cell::from(provider).style(style),
|
|
91
|
+
])
|
|
92
|
+
}).collect();
|
|
93
|
+
|
|
94
|
+
let table = Table::new(
|
|
95
|
+
rows,
|
|
96
|
+
[
|
|
97
|
+
Constraint::Percentage(25),
|
|
98
|
+
Constraint::Percentage(35),
|
|
99
|
+
Constraint::Percentage(20),
|
|
100
|
+
Constraint::Percentage(20),
|
|
101
|
+
]
|
|
102
|
+
)
|
|
103
|
+
.header(Row::new(vec![
|
|
104
|
+
Cell::from("Name").style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
|
|
105
|
+
Cell::from("Description").style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
|
|
106
|
+
Cell::from("Model").style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
|
|
107
|
+
Cell::from("Provider").style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
|
|
108
|
+
]))
|
|
109
|
+
.column_spacing(1)
|
|
110
|
+
.style(Style::default().fg(TEXT_PRIMARY));
|
|
111
|
+
|
|
112
|
+
f.render_widget(table, table_area);
|
|
113
|
+
|
|
114
|
+
// === FOOTER ===
|
|
115
|
+
let footer_text = Line::from(vec![
|
|
116
|
+
Span::styled("↑/↓", Style::default().fg(CYBER_CYAN)),
|
|
117
|
+
Span::styled(" Navigate | ", Style::default().fg(TEXT_DIM)),
|
|
118
|
+
Span::styled("Enter", Style::default().fg(NEON_GREEN)),
|
|
119
|
+
Span::styled(" View Details | ", Style::default().fg(TEXT_DIM)),
|
|
120
|
+
Span::styled("ESC", Style::default().fg(BRAND_PURPLE)),
|
|
121
|
+
Span::styled(" Close", Style::default().fg(TEXT_DIM)),
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
f.render_widget(
|
|
125
|
+
Paragraph::new(footer_text)
|
|
126
|
+
.alignment(Alignment::Center)
|
|
127
|
+
.style(Style::default().bg(BG_PANEL)),
|
|
128
|
+
chunks[2]
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fn render_detail_popup(f: &mut Frame, area: Rect, state: &AppState) {
|
|
133
|
+
let detail_index = state.agent_list.detail_view.unwrap();
|
|
134
|
+
if let Some(agent) = state.agent_list.agents.get(detail_index) {
|
|
135
|
+
render_agent_detail(f, area, agent);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fn render_agent_detail(f: &mut Frame, area: Rect, agent: &AgentInfo) {
|
|
140
|
+
use ratatui::layout::{Constraint, Direction, Layout};
|
|
141
|
+
|
|
142
|
+
// Calculate popup size (70% width, 80% height, centered)
|
|
143
|
+
let popup_width = (area.width * 70 / 100).max(50);
|
|
144
|
+
let popup_height = (area.height * 80 / 100).max(15);
|
|
145
|
+
let popup_x = (area.width.saturating_sub(popup_width)) / 2;
|
|
146
|
+
let popup_y = (area.height.saturating_sub(popup_height)) / 2;
|
|
147
|
+
|
|
148
|
+
let popup_area = Rect {
|
|
149
|
+
x: popup_x,
|
|
150
|
+
y: popup_y,
|
|
151
|
+
width: popup_width,
|
|
152
|
+
height: popup_height,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Render dimmed overlay
|
|
156
|
+
let overlay = Block::default()
|
|
157
|
+
.style(Style::default().bg(Color::Black).fg(Color::Black));
|
|
158
|
+
f.render_widget(overlay, area);
|
|
159
|
+
|
|
160
|
+
// Render detail popup
|
|
161
|
+
let detail_block = Block::default()
|
|
162
|
+
.title(format!(" 📋 Agent Details: {} ", agent.name))
|
|
163
|
+
.title_style(Style::default().fg(BRAND_PURPLE).add_modifier(Modifier::BOLD))
|
|
164
|
+
.borders(Borders::ALL)
|
|
165
|
+
.border_style(Style::default().fg(CYBER_CYAN))
|
|
166
|
+
.style(Style::default().bg(BG_PANEL));
|
|
167
|
+
|
|
168
|
+
f.render_widget(detail_block.clone(), popup_area);
|
|
169
|
+
|
|
170
|
+
let inner = detail_block.inner(popup_area);
|
|
171
|
+
|
|
172
|
+
// Create detail text
|
|
173
|
+
let mut detail_lines = vec![
|
|
174
|
+
Line::from(vec![
|
|
175
|
+
Span::styled("Name: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
|
|
176
|
+
Span::styled(&agent.name, Style::default().fg(NEON_GREEN)),
|
|
177
|
+
]),
|
|
178
|
+
Line::from(""),
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
if let Some(desc) = &agent.description {
|
|
182
|
+
detail_lines.push(Line::from(vec![
|
|
183
|
+
Span::styled("Description: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
|
|
184
|
+
Span::styled(desc, Style::default().fg(TEXT_PRIMARY)),
|
|
185
|
+
]));
|
|
186
|
+
detail_lines.push(Line::from(""));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
detail_lines.push(Line::from(vec![
|
|
190
|
+
Span::styled("Model: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
|
|
191
|
+
Span::styled(&agent.model, Style::default().fg(TEXT_PRIMARY)),
|
|
192
|
+
]));
|
|
193
|
+
detail_lines.push(Line::from(vec![
|
|
194
|
+
Span::styled("Provider: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
|
|
195
|
+
Span::styled(&agent.provider, Style::default().fg(TEXT_PRIMARY)),
|
|
196
|
+
]));
|
|
197
|
+
|
|
198
|
+
if let Some(temp) = agent.temperature {
|
|
199
|
+
detail_lines.push(Line::from(vec![
|
|
200
|
+
Span::styled("Temperature: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
|
|
201
|
+
Span::styled(format!("{:.2}", temp), Style::default().fg(TEXT_PRIMARY)),
|
|
202
|
+
]));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if let Some(max_tokens) = agent.max_tokens {
|
|
206
|
+
detail_lines.push(Line::from(vec![
|
|
207
|
+
Span::styled("Max Tokens: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
|
|
208
|
+
Span::styled(format!("{}", max_tokens), Style::default().fg(TEXT_PRIMARY)),
|
|
209
|
+
]));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if !agent.tools.is_empty() {
|
|
213
|
+
detail_lines.push(Line::from(""));
|
|
214
|
+
detail_lines.push(Line::from(vec![
|
|
215
|
+
Span::styled("Tools: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
|
|
216
|
+
]));
|
|
217
|
+
for tool in &agent.tools {
|
|
218
|
+
detail_lines.push(Line::from(vec![
|
|
219
|
+
Span::raw(" • "),
|
|
220
|
+
Span::styled(tool, Style::default().fg(NEON_GREEN)),
|
|
221
|
+
]));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if let Some(prompt) = &agent.system_prompt {
|
|
226
|
+
detail_lines.push(Line::from(""));
|
|
227
|
+
detail_lines.push(Line::from(vec![
|
|
228
|
+
Span::styled("System Prompt: ", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
|
|
229
|
+
]));
|
|
230
|
+
// Show first 200 chars of prompt
|
|
231
|
+
let prompt_preview = prompt.chars().take(200).collect::<String>();
|
|
232
|
+
detail_lines.push(Line::from(format!(" {}", prompt_preview)).style(Style::default().fg(TEXT_DIM)));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
detail_lines.push(Line::from(""));
|
|
236
|
+
detail_lines.push(Line::from(vec![
|
|
237
|
+
Span::styled("Press ", Style::default().fg(TEXT_DIM)),
|
|
238
|
+
Span::styled("ESC", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
|
|
239
|
+
Span::styled(" to close", Style::default().fg(TEXT_DIM)),
|
|
240
|
+
]));
|
|
241
|
+
|
|
242
|
+
f.render_widget(
|
|
243
|
+
Paragraph::new(detail_lines)
|
|
244
|
+
.wrap(Wrap { trim: false }),
|
|
245
|
+
inner
|
|
246
|
+
);
|
|
247
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/// Help popup screen
|
|
2
|
+
/// Professional, organized command reference
|
|
3
|
+
|
|
4
|
+
use ratatui::prelude::*;
|
|
5
|
+
use ratatui::widgets::{Block, Borders, Paragraph, Wrap, List, ListItem};
|
|
6
|
+
use crate::app::AppState;
|
|
7
|
+
|
|
8
|
+
// === 4RUNR BRAND COLORS ===
|
|
9
|
+
const BRAND_PURPLE: Color = Color::Rgb(138, 43, 226);
|
|
10
|
+
const CYBER_CYAN: Color = Color::Rgb(0, 255, 255);
|
|
11
|
+
const NEON_GREEN: Color = Color::Rgb(57, 255, 20);
|
|
12
|
+
const AMBER_WARN: Color = Color::Rgb(255, 191, 0);
|
|
13
|
+
const TEXT_PRIMARY: Color = Color::Rgb(230, 230, 240);
|
|
14
|
+
const TEXT_DIM: Color = Color::Rgb(100, 100, 120);
|
|
15
|
+
const TEXT_MUTED: Color = Color::Rgb(60, 60, 80);
|
|
16
|
+
const BG_PANEL: Color = Color::Rgb(18, 18, 25);
|
|
17
|
+
|
|
18
|
+
/// Render help popup overlay
|
|
19
|
+
pub fn render_help(f: &mut Frame, area: Rect, _state: &AppState) {
|
|
20
|
+
// Calculate popup size (80% width, 85% height, centered)
|
|
21
|
+
let popup_width = (area.width * 80 / 100).max(60);
|
|
22
|
+
let popup_height = (area.height * 85 / 100).max(20);
|
|
23
|
+
let popup_x = (area.width.saturating_sub(popup_width)) / 2;
|
|
24
|
+
let popup_y = (area.height.saturating_sub(popup_height)) / 2;
|
|
25
|
+
|
|
26
|
+
let popup_area = Rect {
|
|
27
|
+
x: popup_x,
|
|
28
|
+
y: popup_y,
|
|
29
|
+
width: popup_width,
|
|
30
|
+
height: popup_height,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Render background overlay (dimmed)
|
|
34
|
+
let overlay = Block::default()
|
|
35
|
+
.style(Style::default().bg(Color::Black).fg(Color::Black));
|
|
36
|
+
f.render_widget(overlay, area);
|
|
37
|
+
|
|
38
|
+
// Render help popup
|
|
39
|
+
render_help_content(f, popup_area);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fn render_help_content(f: &mut Frame, area: Rect) {
|
|
43
|
+
use ratatui::layout::{Constraint, Direction, Layout};
|
|
44
|
+
|
|
45
|
+
// Split into sections
|
|
46
|
+
let chunks = Layout::default()
|
|
47
|
+
.direction(Direction::Vertical)
|
|
48
|
+
.constraints([
|
|
49
|
+
Constraint::Length(3), // Header
|
|
50
|
+
Constraint::Min(0), // Content
|
|
51
|
+
Constraint::Length(1), // Footer
|
|
52
|
+
])
|
|
53
|
+
.split(area);
|
|
54
|
+
|
|
55
|
+
// === HEADER ===
|
|
56
|
+
let header_block = Block::default()
|
|
57
|
+
.title(" 📖 4Runr AI Agent OS - Command Reference ")
|
|
58
|
+
.title_style(Style::default().fg(BRAND_PURPLE).add_modifier(Modifier::BOLD))
|
|
59
|
+
.borders(Borders::ALL)
|
|
60
|
+
.border_style(Style::default().fg(CYBER_CYAN))
|
|
61
|
+
.style(Style::default().bg(BG_PANEL));
|
|
62
|
+
|
|
63
|
+
let header_text = Line::from(vec![
|
|
64
|
+
Span::styled("Press ", Style::default().fg(TEXT_DIM)),
|
|
65
|
+
Span::styled("ESC", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
|
|
66
|
+
Span::styled(" to close", Style::default().fg(TEXT_DIM)),
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
f.render_widget(header_block, chunks[0]);
|
|
70
|
+
f.render_widget(
|
|
71
|
+
Paragraph::new(header_text).alignment(Alignment::Right),
|
|
72
|
+
Rect {
|
|
73
|
+
x: chunks[0].x + 1,
|
|
74
|
+
y: chunks[0].y + 1,
|
|
75
|
+
width: chunks[0].width.saturating_sub(2),
|
|
76
|
+
height: 1,
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// === CONTENT ===
|
|
81
|
+
let content_chunks = Layout::default()
|
|
82
|
+
.direction(Direction::Horizontal)
|
|
83
|
+
.constraints([
|
|
84
|
+
Constraint::Percentage(40), // Left: Command list
|
|
85
|
+
Constraint::Percentage(60), // Right: Details
|
|
86
|
+
])
|
|
87
|
+
.split(chunks[1]);
|
|
88
|
+
|
|
89
|
+
// Left panel: Command categories
|
|
90
|
+
render_command_list(f, content_chunks[0]);
|
|
91
|
+
|
|
92
|
+
// Right panel: Command details
|
|
93
|
+
render_command_details(f, content_chunks[1]);
|
|
94
|
+
|
|
95
|
+
// === FOOTER ===
|
|
96
|
+
let footer_text = Line::from(vec![
|
|
97
|
+
Span::styled("Navigate: ", Style::default().fg(TEXT_DIM)),
|
|
98
|
+
Span::styled("↑/↓", Style::default().fg(CYBER_CYAN)),
|
|
99
|
+
Span::styled(" Select | ", Style::default().fg(TEXT_MUTED)),
|
|
100
|
+
Span::styled("ESC", Style::default().fg(NEON_GREEN)),
|
|
101
|
+
Span::styled(" Close", Style::default().fg(TEXT_DIM)),
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
f.render_widget(
|
|
105
|
+
Paragraph::new(footer_text)
|
|
106
|
+
.alignment(Alignment::Center)
|
|
107
|
+
.style(Style::default().bg(BG_PANEL)),
|
|
108
|
+
chunks[2]
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn render_command_list(f: &mut Frame, area: Rect) {
|
|
113
|
+
use ratatui::layout::{Constraint, Direction, Layout};
|
|
114
|
+
|
|
115
|
+
let chunks = Layout::default()
|
|
116
|
+
.direction(Direction::Vertical)
|
|
117
|
+
.constraints([
|
|
118
|
+
Constraint::Length(8), // Navigation Commands
|
|
119
|
+
Constraint::Length(7), // Local Commands
|
|
120
|
+
Constraint::Min(0), // WebSocket Commands
|
|
121
|
+
])
|
|
122
|
+
.split(area);
|
|
123
|
+
|
|
124
|
+
// Navigation Commands section
|
|
125
|
+
let nav_block = Block::default()
|
|
126
|
+
.title(" 🧭 Navigation ")
|
|
127
|
+
.title_style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD))
|
|
128
|
+
.borders(Borders::ALL)
|
|
129
|
+
.border_style(Style::default().fg(CYBER_CYAN))
|
|
130
|
+
.style(Style::default().bg(BG_PANEL));
|
|
131
|
+
|
|
132
|
+
let nav_items = vec![
|
|
133
|
+
ListItem::new(Line::from(vec![
|
|
134
|
+
Span::styled("build", Style::default().fg(NEON_GREEN)),
|
|
135
|
+
])),
|
|
136
|
+
ListItem::new(Line::from(vec![
|
|
137
|
+
Span::styled("runs", Style::default().fg(NEON_GREEN)),
|
|
138
|
+
])),
|
|
139
|
+
ListItem::new(Line::from(vec![
|
|
140
|
+
Span::styled("config", Style::default().fg(NEON_GREEN)),
|
|
141
|
+
Span::styled(", ", Style::default().fg(TEXT_DIM)),
|
|
142
|
+
Span::styled("settings", Style::default().fg(NEON_GREEN)),
|
|
143
|
+
])),
|
|
144
|
+
ListItem::new(Line::from(vec![
|
|
145
|
+
Span::styled("ESC", Style::default().fg(NEON_GREEN)),
|
|
146
|
+
])),
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
f.render_widget(nav_block.clone(), chunks[0]);
|
|
150
|
+
f.render_widget(
|
|
151
|
+
List::new(nav_items)
|
|
152
|
+
.style(Style::default().fg(TEXT_PRIMARY)),
|
|
153
|
+
nav_block.inner(chunks[0])
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Local Commands section
|
|
157
|
+
let local_block = Block::default()
|
|
158
|
+
.title(" 💻 Local ")
|
|
159
|
+
.title_style(Style::default().fg(BRAND_PURPLE).add_modifier(Modifier::BOLD))
|
|
160
|
+
.borders(Borders::ALL)
|
|
161
|
+
.border_style(Style::default().fg(BRAND_PURPLE))
|
|
162
|
+
.style(Style::default().bg(BG_PANEL));
|
|
163
|
+
|
|
164
|
+
let local_items = vec![
|
|
165
|
+
ListItem::new(Line::from(vec![
|
|
166
|
+
Span::styled("quit", Style::default().fg(NEON_GREEN)),
|
|
167
|
+
Span::styled(", ", Style::default().fg(TEXT_DIM)),
|
|
168
|
+
Span::styled("exit", Style::default().fg(NEON_GREEN)),
|
|
169
|
+
])),
|
|
170
|
+
ListItem::new(Line::from(vec![
|
|
171
|
+
Span::styled("clear", Style::default().fg(NEON_GREEN)),
|
|
172
|
+
])),
|
|
173
|
+
ListItem::new(Line::from(vec![
|
|
174
|
+
Span::styled("help", Style::default().fg(NEON_GREEN)),
|
|
175
|
+
])),
|
|
176
|
+
ListItem::new(Line::from(vec![
|
|
177
|
+
Span::styled(":perf", Style::default().fg(NEON_GREEN)),
|
|
178
|
+
])),
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
f.render_widget(local_block.clone(), chunks[1]);
|
|
182
|
+
f.render_widget(
|
|
183
|
+
List::new(local_items)
|
|
184
|
+
.style(Style::default().fg(TEXT_PRIMARY)),
|
|
185
|
+
local_block.inner(chunks[1])
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// WebSocket Commands section
|
|
189
|
+
let ws_block = Block::default()
|
|
190
|
+
.title(" 🌐 WebSocket (requires connection) ")
|
|
191
|
+
.title_style(Style::default().fg(AMBER_WARN).add_modifier(Modifier::BOLD))
|
|
192
|
+
.borders(Borders::ALL)
|
|
193
|
+
.border_style(Style::default().fg(AMBER_WARN))
|
|
194
|
+
.style(Style::default().bg(BG_PANEL));
|
|
195
|
+
|
|
196
|
+
let ws_items = vec![
|
|
197
|
+
ListItem::new(Line::from(vec![
|
|
198
|
+
Span::styled("agent.list", Style::default().fg(NEON_GREEN)),
|
|
199
|
+
])),
|
|
200
|
+
ListItem::new(Line::from(vec![
|
|
201
|
+
Span::styled("agent.get", Style::default().fg(NEON_GREEN)),
|
|
202
|
+
])),
|
|
203
|
+
ListItem::new(Line::from(vec![
|
|
204
|
+
Span::styled("agent.create", Style::default().fg(NEON_GREEN)),
|
|
205
|
+
])),
|
|
206
|
+
ListItem::new(Line::from(vec![
|
|
207
|
+
Span::styled("agent.delete", Style::default().fg(NEON_GREEN)),
|
|
208
|
+
])),
|
|
209
|
+
ListItem::new(Line::from(vec![
|
|
210
|
+
Span::styled("system.status", Style::default().fg(NEON_GREEN)),
|
|
211
|
+
])),
|
|
212
|
+
ListItem::new(Line::from(vec![
|
|
213
|
+
Span::styled("run.list", Style::default().fg(NEON_GREEN)),
|
|
214
|
+
])),
|
|
215
|
+
ListItem::new(Line::from(vec![
|
|
216
|
+
Span::styled("tool.list", Style::default().fg(NEON_GREEN)),
|
|
217
|
+
])),
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
f.render_widget(ws_block.clone(), chunks[2]);
|
|
221
|
+
f.render_widget(
|
|
222
|
+
List::new(ws_items)
|
|
223
|
+
.style(Style::default().fg(TEXT_PRIMARY)),
|
|
224
|
+
ws_block.inner(chunks[2])
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
fn render_command_details(f: &mut Frame, area: Rect) {
|
|
229
|
+
use ratatui::layout::{Constraint, Direction, Layout};
|
|
230
|
+
|
|
231
|
+
let chunks = Layout::default()
|
|
232
|
+
.direction(Direction::Vertical)
|
|
233
|
+
.constraints([
|
|
234
|
+
Constraint::Percentage(50), // Command descriptions
|
|
235
|
+
Constraint::Percentage(50), // Screen controls
|
|
236
|
+
])
|
|
237
|
+
.split(area);
|
|
238
|
+
|
|
239
|
+
// Command descriptions
|
|
240
|
+
let desc_block = Block::default()
|
|
241
|
+
.title(" 📋 Command Details ")
|
|
242
|
+
.title_style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD))
|
|
243
|
+
.borders(Borders::ALL)
|
|
244
|
+
.border_style(Style::default().fg(CYBER_CYAN))
|
|
245
|
+
.style(Style::default().bg(BG_PANEL));
|
|
246
|
+
|
|
247
|
+
let desc_text = vec![
|
|
248
|
+
Line::from(vec![
|
|
249
|
+
Span::styled("build", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
|
|
250
|
+
Span::styled(" - Open Agent Builder (6-step wizard)", Style::default().fg(TEXT_PRIMARY)),
|
|
251
|
+
]),
|
|
252
|
+
Line::from(""),
|
|
253
|
+
Line::from(vec![
|
|
254
|
+
Span::styled("runs", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
|
|
255
|
+
Span::styled(" - Open Run Manager (list, filter, sort)", Style::default().fg(TEXT_PRIMARY)),
|
|
256
|
+
]),
|
|
257
|
+
Line::from(""),
|
|
258
|
+
Line::from(vec![
|
|
259
|
+
Span::styled("config, settings", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
|
|
260
|
+
Span::styled(" - Open Settings (mode, AI provider)", Style::default().fg(TEXT_PRIMARY)),
|
|
261
|
+
]),
|
|
262
|
+
Line::from(""),
|
|
263
|
+
Line::from(vec![
|
|
264
|
+
Span::styled("ESC", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
|
|
265
|
+
Span::styled(" - Close overlay/popup or clear input", Style::default().fg(TEXT_PRIMARY)),
|
|
266
|
+
]),
|
|
267
|
+
Line::from(""),
|
|
268
|
+
Line::from(vec![
|
|
269
|
+
Span::styled("quit, exit", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
|
|
270
|
+
Span::styled(" - Exit application", Style::default().fg(TEXT_PRIMARY)),
|
|
271
|
+
]),
|
|
272
|
+
Line::from(""),
|
|
273
|
+
Line::from(vec![
|
|
274
|
+
Span::styled("clear", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
|
|
275
|
+
Span::styled(" - Clear operations log", Style::default().fg(TEXT_PRIMARY)),
|
|
276
|
+
]),
|
|
277
|
+
Line::from(""),
|
|
278
|
+
Line::from(vec![
|
|
279
|
+
Span::styled("help", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
|
|
280
|
+
Span::styled(" - Show this help", Style::default().fg(TEXT_PRIMARY)),
|
|
281
|
+
]),
|
|
282
|
+
Line::from(""),
|
|
283
|
+
Line::from(vec![
|
|
284
|
+
Span::styled(":perf", Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
|
|
285
|
+
Span::styled(" - Show performance stats", Style::default().fg(TEXT_PRIMARY)),
|
|
286
|
+
]),
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
f.render_widget(desc_block.clone(), chunks[0]);
|
|
290
|
+
f.render_widget(
|
|
291
|
+
Paragraph::new(desc_text)
|
|
292
|
+
.wrap(Wrap { trim: false }),
|
|
293
|
+
desc_block.inner(chunks[0])
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
// Screen controls
|
|
297
|
+
let controls_block = Block::default()
|
|
298
|
+
.title(" ⌨️ Screen Controls ")
|
|
299
|
+
.title_style(Style::default().fg(BRAND_PURPLE).add_modifier(Modifier::BOLD))
|
|
300
|
+
.borders(Borders::ALL)
|
|
301
|
+
.border_style(Style::default().fg(BRAND_PURPLE))
|
|
302
|
+
.style(Style::default().bg(BG_PANEL));
|
|
303
|
+
|
|
304
|
+
let controls_text = vec![
|
|
305
|
+
Line::from(vec![
|
|
306
|
+
Span::styled("Agent Builder:", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
|
|
307
|
+
]),
|
|
308
|
+
Line::from(vec![
|
|
309
|
+
Span::raw(" "),
|
|
310
|
+
Span::styled("Enter", Style::default().fg(NEON_GREEN)),
|
|
311
|
+
Span::styled(" = Next | ", Style::default().fg(TEXT_DIM)),
|
|
312
|
+
Span::styled("Backspace", Style::default().fg(NEON_GREEN)),
|
|
313
|
+
Span::styled(" = Prev | ", Style::default().fg(TEXT_DIM)),
|
|
314
|
+
Span::styled("ESC", Style::default().fg(NEON_GREEN)),
|
|
315
|
+
Span::styled(" = Cancel", Style::default().fg(TEXT_DIM)),
|
|
316
|
+
]),
|
|
317
|
+
Line::from(""),
|
|
318
|
+
Line::from(vec![
|
|
319
|
+
Span::styled("Run Manager:", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
|
|
320
|
+
]),
|
|
321
|
+
Line::from(vec![
|
|
322
|
+
Span::raw(" "),
|
|
323
|
+
Span::styled("↑/↓", Style::default().fg(NEON_GREEN)),
|
|
324
|
+
Span::styled(" = Navigate | ", Style::default().fg(TEXT_DIM)),
|
|
325
|
+
Span::styled("F", Style::default().fg(NEON_GREEN)),
|
|
326
|
+
Span::styled(" = Filter | ", Style::default().fg(TEXT_DIM)),
|
|
327
|
+
Span::styled("S", Style::default().fg(NEON_GREEN)),
|
|
328
|
+
Span::styled(" = Sort | ", Style::default().fg(TEXT_DIM)),
|
|
329
|
+
Span::styled("R", Style::default().fg(NEON_GREEN)),
|
|
330
|
+
Span::styled(" = Refresh", Style::default().fg(TEXT_DIM)),
|
|
331
|
+
]),
|
|
332
|
+
Line::from(""),
|
|
333
|
+
Line::from(vec![
|
|
334
|
+
Span::styled("Settings:", Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD)),
|
|
335
|
+
]),
|
|
336
|
+
Line::from(vec![
|
|
337
|
+
Span::raw(" "),
|
|
338
|
+
Span::styled("↑/↓", Style::default().fg(NEON_GREEN)),
|
|
339
|
+
Span::styled(" = Navigate | ", Style::default().fg(TEXT_DIM)),
|
|
340
|
+
Span::styled("Space", Style::default().fg(NEON_GREEN)),
|
|
341
|
+
Span::styled(" = Toggle | ", Style::default().fg(TEXT_DIM)),
|
|
342
|
+
Span::styled("Enter", Style::default().fg(NEON_GREEN)),
|
|
343
|
+
Span::styled(" = Save", Style::default().fg(TEXT_DIM)),
|
|
344
|
+
]),
|
|
345
|
+
];
|
|
346
|
+
|
|
347
|
+
f.render_widget(controls_block.clone(), chunks[1]);
|
|
348
|
+
f.render_widget(
|
|
349
|
+
Paragraph::new(controls_text)
|
|
350
|
+
.wrap(Wrap { trim: false }),
|
|
351
|
+
controls_block.inner(chunks[1])
|
|
352
|
+
);
|
|
353
|
+
}
|
package/mk3-tui/src/ui/layout.rs
CHANGED
|
@@ -120,7 +120,7 @@ fn render_header(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
120
120
|
|
|
121
121
|
// Line 1: Brand + version + uptime - Bug 3 fix: Use "4Runr." with dot (matches brand logo)
|
|
122
122
|
// Use npm package version (2.3.5) - matches package.json
|
|
123
|
-
const PACKAGE_VERSION: &str = "2.4.
|
|
123
|
+
const PACKAGE_VERSION: &str = "2.4.2";
|
|
124
124
|
let brand_line = Line::from(vec![
|
|
125
125
|
Span::styled("4Runr.", Style::default().fg(BRAND_PURPLE).add_modifier(Modifier::BOLD)),
|
|
126
126
|
Span::styled(" AI AGENT OS", Style::default().fg(BRAND_VIOLET)),
|
|
@@ -422,7 +422,6 @@ fn mem_color(value: f64) -> Color {
|
|
|
422
422
|
else if value > 0.6 { Color::Rgb(255, 191, 0) }
|
|
423
423
|
else { CYBER_CYAN }
|
|
424
424
|
}
|
|
425
|
-
|
|
426
425
|
fn render_center_column(f: &mut Frame, area: Rect, state: &AppState) {
|
|
427
426
|
let panel_area = Rect {
|
|
428
427
|
x: area.x + 1,
|
|
@@ -468,8 +467,8 @@ fn render_center_column(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
468
467
|
|
|
469
468
|
// Show real logs with proper formatting (reversed order - newest first)
|
|
470
469
|
for log in state.logs.iter().rev().skip(start_idx).take(visible_height) {
|
|
471
|
-
// Parse log format: [COMPONENT] message
|
|
472
470
|
if log.starts_with("[") {
|
|
471
|
+
// Parse log format: [COMPONENT] message
|
|
473
472
|
if let Some(bracket_end) = log.find(']') {
|
|
474
473
|
let component = &log[1..bracket_end];
|
|
475
474
|
let message = log[bracket_end + 1..].trim();
|
|
@@ -480,6 +479,7 @@ fn render_center_column(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
480
479
|
"SENTINEL" => NEON_GREEN,
|
|
481
480
|
"WORKER" => AMBER_WARN,
|
|
482
481
|
"SYSTEM" => TEXT_DIM,
|
|
482
|
+
"HELP" => CYBER_CYAN,
|
|
483
483
|
_ => TEXT_DIM,
|
|
484
484
|
};
|
|
485
485
|
|