4runr-os 2.3.8 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mk3-tui/src/app.rs +86 -8
- package/mk3-tui/src/main.rs +54 -4
- package/mk3-tui/src/storage/cache.rs +213 -0
- package/mk3-tui/src/storage/mod.rs +6 -0
- package/mk3-tui/src/ui/agent_builder.rs +421 -30
- package/mk3-tui/src/ui/boot.rs +1 -1
- package/mk3-tui/src/ui/layout.rs +1 -1
- package/mk3-tui/src/ui/run_manager.rs +346 -71
- package/mk3-tui/src/ui/settings.rs +57 -42
- package/mk3-tui/src/websocket.rs +47 -2
- package/package.json +1 -1
|
@@ -10,11 +10,130 @@ const BRAND_PURPLE: Color = Color::Rgb(138, 43, 226);
|
|
|
10
10
|
const BRAND_VIOLET: Color = Color::Rgb(148, 103, 189);
|
|
11
11
|
const CYBER_CYAN: Color = Color::Rgb(0, 255, 255);
|
|
12
12
|
const NEON_GREEN: Color = Color::Rgb(57, 255, 20);
|
|
13
|
+
const AMBER_WARN: Color = Color::Rgb(255, 191, 0);
|
|
13
14
|
const TEXT_PRIMARY: Color = Color::Rgb(230, 230, 240);
|
|
14
15
|
const TEXT_DIM: Color = Color::Rgb(100, 100, 120);
|
|
15
16
|
const TEXT_MUTED: Color = Color::Rgb(60, 60, 80);
|
|
16
17
|
const BG_PANEL: Color = Color::Rgb(18, 18, 25);
|
|
17
18
|
|
|
19
|
+
// === MODEL INFORMATION ===
|
|
20
|
+
struct ModelInfo {
|
|
21
|
+
context_window: u32,
|
|
22
|
+
max_output: u32,
|
|
23
|
+
training_cutoff: &'static str,
|
|
24
|
+
capabilities: Vec<&'static str>,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fn get_model_info(provider: &str, model: &str) -> ModelInfo {
|
|
28
|
+
match (provider, model) {
|
|
29
|
+
// OpenAI
|
|
30
|
+
("openai", "gpt-4") => ModelInfo {
|
|
31
|
+
context_window: 8192,
|
|
32
|
+
max_output: 4096,
|
|
33
|
+
training_cutoff: "Sep 2021",
|
|
34
|
+
capabilities: vec!["Text", "Code", "Analysis"],
|
|
35
|
+
},
|
|
36
|
+
("openai", "gpt-4-turbo") => ModelInfo {
|
|
37
|
+
context_window: 128000,
|
|
38
|
+
max_output: 4096,
|
|
39
|
+
training_cutoff: "Apr 2023",
|
|
40
|
+
capabilities: vec!["Text", "Code", "Vision", "JSON"],
|
|
41
|
+
},
|
|
42
|
+
("openai", "gpt-3.5-turbo") => ModelInfo {
|
|
43
|
+
context_window: 16385,
|
|
44
|
+
max_output: 4096,
|
|
45
|
+
training_cutoff: "Sep 2021",
|
|
46
|
+
capabilities: vec!["Text", "Code"],
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
// Anthropic
|
|
50
|
+
("anthropic", "claude-3-opus") => ModelInfo {
|
|
51
|
+
context_window: 200000,
|
|
52
|
+
max_output: 4096,
|
|
53
|
+
training_cutoff: "Aug 2023",
|
|
54
|
+
capabilities: vec!["Text", "Code", "Vision", "Analysis", "Long Context"],
|
|
55
|
+
},
|
|
56
|
+
("anthropic", "claude-3-sonnet") => ModelInfo {
|
|
57
|
+
context_window: 200000,
|
|
58
|
+
max_output: 4096,
|
|
59
|
+
training_cutoff: "Aug 2023",
|
|
60
|
+
capabilities: vec!["Text", "Code", "Vision", "Balanced"],
|
|
61
|
+
},
|
|
62
|
+
("anthropic", "claude-3-haiku") => ModelInfo {
|
|
63
|
+
context_window: 200000,
|
|
64
|
+
max_output: 4096,
|
|
65
|
+
training_cutoff: "Aug 2023",
|
|
66
|
+
capabilities: vec!["Text", "Code", "Fast", "Efficient"],
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// Local models
|
|
70
|
+
_ => ModelInfo {
|
|
71
|
+
context_window: 4096,
|
|
72
|
+
max_output: 2048,
|
|
73
|
+
training_cutoff: "Varies",
|
|
74
|
+
capabilities: vec!["Text", "Local", "Private"],
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// === COST CALCULATOR ===
|
|
80
|
+
/// Pricing data for different AI providers (per 1M tokens)
|
|
81
|
+
struct ModelPricing {
|
|
82
|
+
input_cost: f64, // USD per 1M input tokens
|
|
83
|
+
output_cost: f64, // USD per 1M output tokens
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fn get_model_pricing(provider: &str, model: &str) -> ModelPricing {
|
|
87
|
+
match (provider, model) {
|
|
88
|
+
// OpenAI
|
|
89
|
+
("openai", "gpt-4") => ModelPricing { input_cost: 30.0, output_cost: 60.0 },
|
|
90
|
+
("openai", "gpt-4-turbo") => ModelPricing { input_cost: 10.0, output_cost: 30.0 },
|
|
91
|
+
("openai", "gpt-3.5-turbo") => ModelPricing { input_cost: 0.5, output_cost: 1.5 },
|
|
92
|
+
|
|
93
|
+
// Anthropic
|
|
94
|
+
("anthropic", "claude-3-opus") => ModelPricing { input_cost: 15.0, output_cost: 75.0 },
|
|
95
|
+
("anthropic", "claude-3-sonnet") => ModelPricing { input_cost: 3.0, output_cost: 15.0 },
|
|
96
|
+
("anthropic", "claude-3-haiku") => ModelPricing { input_cost: 0.25, output_cost: 1.25 },
|
|
97
|
+
|
|
98
|
+
// Local models (free)
|
|
99
|
+
_ => ModelPricing { input_cost: 0.0, output_cost: 0.0 },
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
struct CostEstimate {
|
|
104
|
+
cost_1k: f64,
|
|
105
|
+
cost_10k: f64,
|
|
106
|
+
cost_100k: f64,
|
|
107
|
+
cost_1m: f64,
|
|
108
|
+
is_free: bool,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fn calculate_cost(provider: &str, model: &str, max_tokens: u32) -> CostEstimate {
|
|
112
|
+
let pricing = get_model_pricing(provider, model);
|
|
113
|
+
let is_free = pricing.input_cost == 0.0 && pricing.output_cost == 0.0;
|
|
114
|
+
|
|
115
|
+
if is_free {
|
|
116
|
+
return CostEstimate {
|
|
117
|
+
cost_1k: 0.0,
|
|
118
|
+
cost_10k: 0.0,
|
|
119
|
+
cost_100k: 0.0,
|
|
120
|
+
cost_1m: 0.0,
|
|
121
|
+
is_free: true,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Assume 50/50 split between input and output tokens for estimation
|
|
126
|
+
let avg_cost_per_1m = (pricing.input_cost + pricing.output_cost) / 2.0;
|
|
127
|
+
|
|
128
|
+
CostEstimate {
|
|
129
|
+
cost_1k: avg_cost_per_1m / 1000.0,
|
|
130
|
+
cost_10k: avg_cost_per_1m / 100.0,
|
|
131
|
+
cost_100k: avg_cost_per_1m / 10.0,
|
|
132
|
+
cost_1m: avg_cost_per_1m,
|
|
133
|
+
is_free: false,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
18
137
|
/// Agent Builder wizard state
|
|
19
138
|
#[derive(Debug, Clone)]
|
|
20
139
|
pub struct AgentBuilderState {
|
|
@@ -188,16 +307,50 @@ pub fn render(f: &mut Frame, state: &AppState) {
|
|
|
188
307
|
}
|
|
189
308
|
|
|
190
309
|
fn render_wizard(f: &mut Frame, area: Rect, wizard: &AgentBuilderState) {
|
|
310
|
+
use ratatui::layout::{Constraint, Direction, Layout};
|
|
311
|
+
|
|
312
|
+
// Split area into content and footer
|
|
313
|
+
let v_chunks = Layout::default()
|
|
314
|
+
.direction(Direction::Vertical)
|
|
315
|
+
.constraints([
|
|
316
|
+
Constraint::Min(10), // Content
|
|
317
|
+
Constraint::Length(3), // Navigation footer
|
|
318
|
+
])
|
|
319
|
+
.split(area);
|
|
320
|
+
|
|
321
|
+
// Split content into main wizard and cost calculator (if on step 2 or 4)
|
|
322
|
+
let show_cost_calc = wizard.current_step == 2 || wizard.current_step == 4;
|
|
323
|
+
let h_chunks = Layout::default()
|
|
324
|
+
.direction(Direction::Horizontal)
|
|
325
|
+
.constraints(if show_cost_calc {
|
|
326
|
+
[
|
|
327
|
+
Constraint::Percentage(65), // Wizard content
|
|
328
|
+
Constraint::Percentage(35), // Cost calculator
|
|
329
|
+
].as_ref()
|
|
330
|
+
} else {
|
|
331
|
+
[
|
|
332
|
+
Constraint::Percentage(100), // Full width
|
|
333
|
+
Constraint::Length(0), // No cost calc
|
|
334
|
+
].as_ref()
|
|
335
|
+
})
|
|
336
|
+
.split(v_chunks[0]);
|
|
337
|
+
|
|
338
|
+
// Progress bar for title
|
|
339
|
+
let progress = wizard.current_step as f32 / 6.0;
|
|
340
|
+
let filled = (progress * 20.0) as usize;
|
|
341
|
+
let empty = 20 - filled;
|
|
342
|
+
let progress_bar = format!("[{}{}]", "█".repeat(filled), "░".repeat(empty));
|
|
343
|
+
|
|
191
344
|
// Main container with brand styling
|
|
192
345
|
let block = Block::default()
|
|
193
|
-
.title(format!(" Agent Builder - Step {}/6 ", wizard.current_step))
|
|
346
|
+
.title(format!(" 🤖 Agent Builder - Step {}/6 {} ", wizard.current_step, progress_bar))
|
|
194
347
|
.title_style(Style::default().fg(CYBER_CYAN).add_modifier(Modifier::BOLD))
|
|
195
348
|
.borders(Borders::ALL)
|
|
196
349
|
.border_style(Style::default().fg(BRAND_PURPLE))
|
|
197
350
|
.style(Style::default().bg(BG_PANEL));
|
|
198
351
|
|
|
199
|
-
let inner = block.inner(
|
|
200
|
-
f.render_widget(block,
|
|
352
|
+
let inner = block.inner(h_chunks[0]);
|
|
353
|
+
f.render_widget(block, h_chunks[0]);
|
|
201
354
|
|
|
202
355
|
// Render current step
|
|
203
356
|
match wizard.current_step {
|
|
@@ -209,6 +362,208 @@ fn render_wizard(f: &mut Frame, area: Rect, wizard: &AgentBuilderState) {
|
|
|
209
362
|
6 => render_step_6_review(f, inner, wizard),
|
|
210
363
|
_ => {}
|
|
211
364
|
}
|
|
365
|
+
|
|
366
|
+
// Render cost calculator if visible
|
|
367
|
+
if show_cost_calc {
|
|
368
|
+
render_cost_calculator(f, h_chunks[1], wizard);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Render navigation footer
|
|
372
|
+
render_navigation_footer(f, v_chunks[1], wizard);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
fn render_cost_calculator(f: &mut Frame, area: Rect, wizard: &AgentBuilderState) {
|
|
376
|
+
use ratatui::layout::{Constraint, Direction, Layout};
|
|
377
|
+
|
|
378
|
+
// Split into cost and model info sections
|
|
379
|
+
let chunks = Layout::default()
|
|
380
|
+
.direction(Direction::Vertical)
|
|
381
|
+
.constraints([
|
|
382
|
+
Constraint::Percentage(60), // Cost info
|
|
383
|
+
Constraint::Percentage(40), // Model info
|
|
384
|
+
])
|
|
385
|
+
.split(area);
|
|
386
|
+
|
|
387
|
+
let provider = if wizard.use_local_model {
|
|
388
|
+
"local"
|
|
389
|
+
} else {
|
|
390
|
+
&wizard.provider
|
|
391
|
+
};
|
|
392
|
+
let model = if wizard.use_local_model {
|
|
393
|
+
&wizard.local_model
|
|
394
|
+
} else {
|
|
395
|
+
&wizard.model
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
let cost = calculate_cost(provider, model, wizard.max_tokens);
|
|
399
|
+
let model_info = get_model_info(provider, model);
|
|
400
|
+
|
|
401
|
+
// === COST SECTION ===
|
|
402
|
+
let cost_block = Block::default()
|
|
403
|
+
.title(" 💰 Cost Estimator ")
|
|
404
|
+
.borders(Borders::ALL)
|
|
405
|
+
.border_style(Style::default().fg(AMBER_WARN))
|
|
406
|
+
.style(Style::default().bg(BG_PANEL));
|
|
407
|
+
|
|
408
|
+
let cost_inner = cost_block.inner(chunks[0]);
|
|
409
|
+
f.render_widget(cost_block, chunks[0]);
|
|
410
|
+
|
|
411
|
+
let text = if cost.is_free {
|
|
412
|
+
vec![
|
|
413
|
+
Line::from(""),
|
|
414
|
+
Line::from(vec![
|
|
415
|
+
Span::styled("✓ ", Style::default().fg(NEON_GREEN)),
|
|
416
|
+
Span::styled("Local Model", Style::default().fg(NEON_GREEN).bold()),
|
|
417
|
+
]),
|
|
418
|
+
Line::from(""),
|
|
419
|
+
Line::from(vec![
|
|
420
|
+
Span::styled("FREE", Style::default().fg(NEON_GREEN).bold().add_modifier(Modifier::UNDERLINED)),
|
|
421
|
+
]),
|
|
422
|
+
Line::from(""),
|
|
423
|
+
Line::from("No API costs!").style(Style::default().fg(TEXT_DIM)),
|
|
424
|
+
Line::from("Self-hosted model").style(Style::default().fg(TEXT_DIM)),
|
|
425
|
+
Line::from(""),
|
|
426
|
+
Line::from("Benefits:").style(Style::default().fg(TEXT_PRIMARY).bold()),
|
|
427
|
+
Line::from(" • No usage limits").style(Style::default().fg(TEXT_DIM)),
|
|
428
|
+
Line::from(" • Full privacy").style(Style::default().fg(TEXT_DIM)),
|
|
429
|
+
Line::from(" • No API keys").style(Style::default().fg(TEXT_DIM)),
|
|
430
|
+
]
|
|
431
|
+
} else {
|
|
432
|
+
let cost_color = if cost.cost_1k < 0.01 {
|
|
433
|
+
NEON_GREEN
|
|
434
|
+
} else if cost.cost_1k < 0.05 {
|
|
435
|
+
AMBER_WARN
|
|
436
|
+
} else {
|
|
437
|
+
Color::Rgb(255, 69, 69)
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
vec![
|
|
441
|
+
Line::from(""),
|
|
442
|
+
Line::from(vec![
|
|
443
|
+
Span::styled("Provider: ", Style::default().fg(TEXT_DIM)),
|
|
444
|
+
Span::styled(provider, Style::default().fg(TEXT_PRIMARY).bold()),
|
|
445
|
+
]),
|
|
446
|
+
Line::from(vec![
|
|
447
|
+
Span::styled("Model: ", Style::default().fg(TEXT_DIM)),
|
|
448
|
+
Span::styled(model, Style::default().fg(TEXT_PRIMARY)),
|
|
449
|
+
]),
|
|
450
|
+
Line::from(""),
|
|
451
|
+
Line::from("Estimated Cost:").style(Style::default().fg(TEXT_PRIMARY).bold()),
|
|
452
|
+
Line::from(""),
|
|
453
|
+
Line::from(vec![
|
|
454
|
+
Span::styled(" 1K tokens: ", Style::default().fg(TEXT_DIM)),
|
|
455
|
+
Span::styled(format!("${:.4}", cost.cost_1k), Style::default().fg(cost_color)),
|
|
456
|
+
]),
|
|
457
|
+
Line::from(vec![
|
|
458
|
+
Span::styled(" 10K tokens: ", Style::default().fg(TEXT_DIM)),
|
|
459
|
+
Span::styled(format!("${:.3}", cost.cost_10k), Style::default().fg(cost_color)),
|
|
460
|
+
]),
|
|
461
|
+
Line::from(vec![
|
|
462
|
+
Span::styled(" 100K tokens: ", Style::default().fg(TEXT_DIM)),
|
|
463
|
+
Span::styled(format!("${:.2}", cost.cost_100k), Style::default().fg(cost_color)),
|
|
464
|
+
]),
|
|
465
|
+
Line::from(vec![
|
|
466
|
+
Span::styled(" 1M tokens: ", Style::default().fg(TEXT_DIM)),
|
|
467
|
+
Span::styled(format!("${:.2}", cost.cost_1m), Style::default().fg(cost_color)),
|
|
468
|
+
]),
|
|
469
|
+
Line::from(""),
|
|
470
|
+
Line::from(vec![
|
|
471
|
+
Span::styled("Max tokens: ", Style::default().fg(TEXT_DIM)),
|
|
472
|
+
Span::styled(format!("{}", wizard.max_tokens), Style::default().fg(CYBER_CYAN)),
|
|
473
|
+
]),
|
|
474
|
+
]
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
let cost_paragraph = Paragraph::new(text)
|
|
478
|
+
.wrap(Wrap { trim: false })
|
|
479
|
+
.alignment(Alignment::Left);
|
|
480
|
+
|
|
481
|
+
f.render_widget(cost_paragraph, cost_inner);
|
|
482
|
+
|
|
483
|
+
// === MODEL INFO SECTION ===
|
|
484
|
+
let info_block = Block::default()
|
|
485
|
+
.title(" 📊 Model Info ")
|
|
486
|
+
.borders(Borders::ALL)
|
|
487
|
+
.border_style(Style::default().fg(CYBER_CYAN))
|
|
488
|
+
.style(Style::default().bg(BG_PANEL));
|
|
489
|
+
|
|
490
|
+
let info_inner = info_block.inner(chunks[1]);
|
|
491
|
+
f.render_widget(info_block, chunks[1]);
|
|
492
|
+
|
|
493
|
+
let info_text = vec![
|
|
494
|
+
Line::from(""),
|
|
495
|
+
Line::from(vec![
|
|
496
|
+
Span::styled("Context: ", Style::default().fg(TEXT_DIM)),
|
|
497
|
+
Span::styled(format!("{} tokens", model_info.context_window), Style::default().fg(NEON_GREEN)),
|
|
498
|
+
]),
|
|
499
|
+
Line::from(vec![
|
|
500
|
+
Span::styled("Max Output: ", Style::default().fg(TEXT_DIM)),
|
|
501
|
+
Span::styled(format!("{} tokens", model_info.max_output), Style::default().fg(TEXT_PRIMARY)),
|
|
502
|
+
]),
|
|
503
|
+
Line::from(vec![
|
|
504
|
+
Span::styled("Training: ", Style::default().fg(TEXT_DIM)),
|
|
505
|
+
Span::styled(model_info.training_cutoff, Style::default().fg(TEXT_MUTED)),
|
|
506
|
+
]),
|
|
507
|
+
Line::from(""),
|
|
508
|
+
Line::from("Capabilities:").style(Style::default().fg(TEXT_PRIMARY).bold()),
|
|
509
|
+
Line::from(
|
|
510
|
+
model_info.capabilities
|
|
511
|
+
.iter()
|
|
512
|
+
.map(|cap| format!(" • {}", cap))
|
|
513
|
+
.collect::<Vec<_>>()
|
|
514
|
+
.join("\n")
|
|
515
|
+
).style(Style::default().fg(TEXT_DIM)),
|
|
516
|
+
];
|
|
517
|
+
|
|
518
|
+
let info_paragraph = Paragraph::new(info_text)
|
|
519
|
+
.wrap(Wrap { trim: false })
|
|
520
|
+
.alignment(Alignment::Left);
|
|
521
|
+
|
|
522
|
+
f.render_widget(info_paragraph, info_inner);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
fn render_navigation_footer(f: &mut Frame, area: Rect, wizard: &AgentBuilderState) {
|
|
526
|
+
let block = Block::default()
|
|
527
|
+
.title(" ⌨️ Navigation ")
|
|
528
|
+
.borders(Borders::ALL)
|
|
529
|
+
.border_style(Style::default().fg(TEXT_DIM))
|
|
530
|
+
.style(Style::default().bg(BG_PANEL));
|
|
531
|
+
|
|
532
|
+
let inner = block.inner(area);
|
|
533
|
+
f.render_widget(block, area);
|
|
534
|
+
|
|
535
|
+
let can_go_back = wizard.current_step > 1;
|
|
536
|
+
let is_final_step = wizard.current_step == 6;
|
|
537
|
+
|
|
538
|
+
let nav_text = if is_final_step {
|
|
539
|
+
Line::from(vec![
|
|
540
|
+
Span::styled("←", Style::default().fg(if can_go_back { CYBER_CYAN } else { TEXT_MUTED }).bold()),
|
|
541
|
+
Span::styled(" Back │ ", Style::default().fg(TEXT_DIM)),
|
|
542
|
+
Span::styled("Tab/↑↓", Style::default().fg(CYBER_CYAN).bold()),
|
|
543
|
+
Span::styled(" Navigate │ ", Style::default().fg(TEXT_DIM)),
|
|
544
|
+
Span::styled("Enter", Style::default().fg(NEON_GREEN).bold()),
|
|
545
|
+
Span::styled(" Create Agent │ ", Style::default().fg(TEXT_DIM)),
|
|
546
|
+
Span::styled("ESC", Style::default().fg(Color::Rgb(255, 69, 69)).bold()),
|
|
547
|
+
Span::styled(" Cancel", Style::default().fg(TEXT_DIM)),
|
|
548
|
+
])
|
|
549
|
+
} else {
|
|
550
|
+
Line::from(vec![
|
|
551
|
+
Span::styled("←", Style::default().fg(if can_go_back { CYBER_CYAN } else { TEXT_MUTED }).bold()),
|
|
552
|
+
Span::styled(" Back │ ", Style::default().fg(TEXT_DIM)),
|
|
553
|
+
Span::styled("Tab/↑↓", Style::default().fg(CYBER_CYAN).bold()),
|
|
554
|
+
Span::styled(" Navigate │ ", Style::default().fg(TEXT_DIM)),
|
|
555
|
+
Span::styled("→/Enter", Style::default().fg(NEON_GREEN).bold()),
|
|
556
|
+
Span::styled(" Next │ ", Style::default().fg(TEXT_DIM)),
|
|
557
|
+
Span::styled("ESC", Style::default().fg(Color::Rgb(255, 69, 69)).bold()),
|
|
558
|
+
Span::styled(" Cancel", Style::default().fg(TEXT_DIM)),
|
|
559
|
+
])
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
let paragraph = Paragraph::new(nav_text)
|
|
563
|
+
.style(Style::default().fg(TEXT_PRIMARY))
|
|
564
|
+
.alignment(Alignment::Center);
|
|
565
|
+
|
|
566
|
+
f.render_widget(paragraph, inner);
|
|
212
567
|
}
|
|
213
568
|
|
|
214
569
|
fn render_step_1_basic_info(f: &mut Frame, area: Rect, wizard: &AgentBuilderState) {
|
|
@@ -460,68 +815,104 @@ fn render_step_5_tools(f: &mut Frame, area: Rect, wizard: &AgentBuilderState) {
|
|
|
460
815
|
}
|
|
461
816
|
|
|
462
817
|
fn render_step_6_review(f: &mut Frame, area: Rect, wizard: &AgentBuilderState) {
|
|
463
|
-
let
|
|
818
|
+
let provider = if wizard.use_local_model {
|
|
819
|
+
"local"
|
|
820
|
+
} else {
|
|
821
|
+
&wizard.provider
|
|
822
|
+
};
|
|
823
|
+
let model = if wizard.use_local_model {
|
|
824
|
+
&wizard.local_model
|
|
825
|
+
} else {
|
|
826
|
+
&wizard.model
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
let model_info_str = if wizard.use_local_model {
|
|
464
830
|
format!("{} ({})", wizard.local_model, wizard.local_provider)
|
|
465
831
|
} else {
|
|
466
832
|
format!("{} ({})", wizard.model, wizard.provider)
|
|
467
833
|
};
|
|
468
834
|
|
|
469
|
-
let
|
|
835
|
+
let cost = calculate_cost(provider, model, wizard.max_tokens);
|
|
836
|
+
let model_info = get_model_info(provider, model);
|
|
837
|
+
|
|
838
|
+
let mut text = vec![
|
|
470
839
|
Line::from(""),
|
|
471
840
|
Line::from("Step 6: Review & Confirm").style(Style::default().fg(BRAND_PURPLE).add_modifier(Modifier::BOLD)),
|
|
472
841
|
Line::from(""),
|
|
473
|
-
Line::from("
|
|
842
|
+
Line::from("✓ Configuration Complete - Ready to Create").style(Style::default().fg(NEON_GREEN).bold()),
|
|
474
843
|
Line::from(""),
|
|
475
844
|
Line::from("═══════════════════════════════════════════════════").style(Style::default().fg(TEXT_MUTED)),
|
|
845
|
+
Line::from(""),
|
|
846
|
+
Line::from("Basic Info:").style(Style::default().fg(CYBER_CYAN).bold()),
|
|
476
847
|
Line::from(vec![
|
|
477
|
-
Span::
|
|
848
|
+
Span::styled(" Name: ", Style::default().fg(TEXT_DIM)),
|
|
478
849
|
Span::styled(&wizard.name, Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
|
|
479
850
|
]),
|
|
480
851
|
Line::from(vec![
|
|
481
|
-
Span::
|
|
852
|
+
Span::styled(" Description: ", Style::default().fg(TEXT_DIM)),
|
|
482
853
|
Span::styled(
|
|
483
|
-
wizard.description.as_str(),
|
|
484
|
-
Style::default().fg(
|
|
854
|
+
if wizard.description.is_empty() { "(none)" } else { wizard.description.as_str() },
|
|
855
|
+
Style::default().fg(TEXT_PRIMARY)
|
|
485
856
|
),
|
|
486
857
|
]),
|
|
487
858
|
Line::from(""),
|
|
859
|
+
Line::from("Model Configuration:").style(Style::default().fg(CYBER_CYAN).bold()),
|
|
488
860
|
Line::from(vec![
|
|
489
|
-
Span::
|
|
490
|
-
Span::styled(&
|
|
861
|
+
Span::styled(" Model: ", Style::default().fg(TEXT_DIM)),
|
|
862
|
+
Span::styled(&model_info_str, Style::default().fg(TEXT_PRIMARY).bold()),
|
|
491
863
|
]),
|
|
492
864
|
Line::from(vec![
|
|
493
|
-
Span::
|
|
494
|
-
Span::styled(format!("{
|
|
865
|
+
Span::styled(" Context Window: ", Style::default().fg(TEXT_DIM)),
|
|
866
|
+
Span::styled(format!("{} tokens", model_info.context_window), Style::default().fg(TEXT_PRIMARY)),
|
|
495
867
|
]),
|
|
496
868
|
Line::from(vec![
|
|
497
|
-
Span::
|
|
498
|
-
Span::styled(format!("{}", wizard.
|
|
869
|
+
Span::styled(" Temperature: ", Style::default().fg(TEXT_DIM)),
|
|
870
|
+
Span::styled(format!("{:.2}", wizard.temperature), Style::default().fg(TEXT_PRIMARY)),
|
|
871
|
+
]),
|
|
872
|
+
Line::from(vec![
|
|
873
|
+
Span::styled(" Max Tokens: ", Style::default().fg(TEXT_DIM)),
|
|
874
|
+
Span::styled(format!("{}", wizard.max_tokens), Style::default().fg(TEXT_PRIMARY)),
|
|
499
875
|
]),
|
|
500
876
|
Line::from(""),
|
|
877
|
+
Line::from("Tools:").style(Style::default().fg(CYBER_CYAN).bold()),
|
|
501
878
|
Line::from(vec![
|
|
502
|
-
Span::
|
|
879
|
+
Span::styled(" ", Style::default()),
|
|
503
880
|
Span::styled(
|
|
504
881
|
if wizard.selected_tools.is_empty() {
|
|
505
|
-
"None".to_string()
|
|
882
|
+
"None selected".to_string()
|
|
506
883
|
} else {
|
|
507
884
|
wizard.selected_tools.join(", ")
|
|
508
885
|
},
|
|
509
|
-
Style::default().fg(
|
|
886
|
+
Style::default().fg(TEXT_PRIMARY)
|
|
510
887
|
),
|
|
511
888
|
]),
|
|
512
|
-
Line::from(""),
|
|
513
|
-
Line::from("System Prompt:").style(Style::default().fg(TEXT_PRIMARY).add_modifier(Modifier::BOLD)),
|
|
514
|
-
Line::from(if wizard.system_prompt.is_empty() {
|
|
515
|
-
" (none)".to_string()
|
|
516
|
-
} else {
|
|
517
|
-
format!(" {}", wizard.system_prompt.chars().take(100).collect::<String>())
|
|
518
|
-
}).style(Style::default().fg(TEXT_DIM)),
|
|
519
|
-
Line::from("═══════════════════════════════════════════════════").style(Style::default().fg(TEXT_MUTED)),
|
|
520
|
-
Line::from(""),
|
|
521
|
-
Line::from("Actions:").style(Style::default().fg(NEON_GREEN).add_modifier(Modifier::BOLD)),
|
|
522
|
-
Line::from(" Enter - Create Agent | ESC - Cancel").style(Style::default().fg(TEXT_DIM)),
|
|
523
889
|
];
|
|
524
890
|
|
|
891
|
+
// Add cost estimate
|
|
892
|
+
if !cost.is_free {
|
|
893
|
+
text.push(Line::from(""));
|
|
894
|
+
text.push(Line::from("Estimated Cost:").style(Style::default().fg(AMBER_WARN).bold()));
|
|
895
|
+
text.push(Line::from(vec![
|
|
896
|
+
Span::styled(" Per 1K tokens: ", Style::default().fg(TEXT_DIM)),
|
|
897
|
+
Span::styled(format!("${:.4}", cost.cost_1k), Style::default().fg(TEXT_PRIMARY)),
|
|
898
|
+
]));
|
|
899
|
+
} else {
|
|
900
|
+
text.push(Line::from(""));
|
|
901
|
+
text.push(Line::from(vec![
|
|
902
|
+
Span::styled(" ✓ ", Style::default().fg(NEON_GREEN)),
|
|
903
|
+
Span::styled("FREE (Local Model)", Style::default().fg(NEON_GREEN).bold()),
|
|
904
|
+
]));
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
text.push(Line::from(""));
|
|
908
|
+
text.push(Line::from("═══════════════════════════════════════════════════").style(Style::default().fg(TEXT_MUTED)));
|
|
909
|
+
text.push(Line::from(""));
|
|
910
|
+
text.push(Line::from(vec![
|
|
911
|
+
Span::styled("Press ", Style::default().fg(TEXT_DIM)),
|
|
912
|
+
Span::styled("Enter", Style::default().fg(NEON_GREEN).bold()),
|
|
913
|
+
Span::styled(" to create this agent", Style::default().fg(TEXT_DIM)),
|
|
914
|
+
]));
|
|
915
|
+
|
|
525
916
|
let paragraph = Paragraph::new(text)
|
|
526
917
|
.wrap(Wrap { trim: false })
|
|
527
918
|
.alignment(Alignment::Left);
|
package/mk3-tui/src/ui/boot.rs
CHANGED
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.
|
|
123
|
+
const PACKAGE_VERSION: &str = "2.4.0";
|
|
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)),
|