@pheem49/mint 1.7.1 → 1.8.1
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/.github/workflows/release.yml +1 -1
- package/Cargo.lock +34 -3
- package/Cargo.toml +2 -2
- package/README.md +40 -0
- package/Release_Note.md +95 -0
- package/assets/Mint_CLI.png +0 -0
- package/crates/mint-cli/Cargo.toml +1 -0
- package/crates/mint-cli/src/agent.rs +142 -48
- package/crates/mint-cli/src/main.rs +231 -22
- package/crates/mint-core/src/api_server.rs +71 -4
- package/crates/mint-core/src/browser.rs +401 -0
- package/crates/mint-core/src/chat.rs +79 -0
- package/crates/mint-core/src/config.rs +69 -1
- package/crates/mint-core/src/lib.rs +6 -1
- package/crates/mint-core/src/orchestration.rs +137 -1
- package/crates/mint-core/src/skills.rs +113 -4
- package/crates/mint-core/src/workflows.rs +9 -22
- package/package.json +2 -1
- package/src/renderer/src/App.tsx +1 -1
- package/src/renderer/src/components/ChatPanel.tsx +197 -47
- package/src/renderer/src/components/DashboardSidebar.tsx +71 -12
- package/src/renderer/src/components/ImageStudioPanel.tsx +18 -4
- package/src/renderer/src/components/MintDashboard.tsx +239 -7
- package/src/renderer/src/components/PicturesLibrary.tsx +1 -3
- package/src/renderer/src/components/Settings/AudioTab.tsx +97 -0
- package/src/renderer/src/components/Settings/AutomationTab.tsx +195 -0
- package/src/renderer/src/components/Settings/GeneralTab.tsx +950 -0
- package/src/renderer/src/components/Settings/MemoryTab.tsx +60 -0
- package/src/renderer/src/components/Settings/PluginsTab.tsx +632 -0
- package/src/renderer/src/components/Settings/ShortcutsTab.tsx +30 -0
- package/src/renderer/src/components/Settings/ThemeTab.tsx +129 -0
- package/src/renderer/src/components/SettingsWindow.tsx +175 -970
- package/src/renderer/src/components/WorkflowBuilderPanel.tsx +536 -0
- package/src/renderer/src/components/WorkflowsWindow.tsx +46 -0
- package/src/renderer/src/components/WorkspacePanel.tsx +66 -9
- package/src/renderer/src/css/image-studio.css +15 -4
- package/src/renderer/src/css/settings.css +75 -6
- package/src/renderer/src/css/styles.css +419 -49
- package/src/renderer/src/css/workflows.css +542 -0
- package/src/renderer/src/env.d.ts +2 -0
- package/src/renderer/src/index.css +13 -1
- package/src/renderer/src/tauri.ts +140 -2
- package/src/renderer/src-web/components/ChatPanel.tsx +190 -46
- package/src/renderer/src-web/components/DashboardSidebar.tsx +17 -1
- package/src/renderer/src-web/components/ImageStudioPanel.tsx +18 -4
- package/src/renderer/src-web/components/MintDashboard.tsx +197 -3
- package/src/renderer/src-web/components/PicturesLibrary.tsx +1 -3
- package/src/renderer/src-web/components/Settings/AudioTab.tsx +97 -0
- package/src/renderer/src-web/components/Settings/AutomationTab.tsx +196 -0
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +948 -0
- package/src/renderer/src-web/components/Settings/MemoryTab.tsx +60 -0
- package/src/renderer/src-web/components/Settings/PluginsTab.tsx +632 -0
- package/src/renderer/src-web/components/Settings/ShortcutsTab.tsx +30 -0
- package/src/renderer/src-web/components/Settings/ThemeTab.tsx +129 -0
- package/src/renderer/src-web/components/SettingsWindow.tsx +187 -976
- package/src/renderer/src-web/css/image-studio.css +15 -4
- package/src/renderer/src-web/css/settings.css +75 -6
- package/src/renderer/src-web/css/styles.css +394 -50
- package/src/renderer/src-web/env.d.ts +2 -0
- package/src/renderer/src-web/index.css +12 -1
- package/src/renderer/src-web/tauri.ts +60 -2
- package/src-tauri/src/browser.rs +1 -141
- package/src-tauri/src/headless.rs +3 -2
- package/src-tauri/src/lib.rs +195 -111
- package/src-tauri/src/workflows.rs +56 -4
- package/src-tauri/tauri.conf.json +1 -1
|
@@ -110,6 +110,8 @@ enum Command {
|
|
|
110
110
|
},
|
|
111
111
|
/// Run one queued or supplied task through the native CLI agent.
|
|
112
112
|
Agent { task: Option<String> },
|
|
113
|
+
/// Start the browser automation environment and enable browser actions.
|
|
114
|
+
Auto,
|
|
113
115
|
/// Launch the web UI and local API server.
|
|
114
116
|
Web,
|
|
115
117
|
/// Start only the local API server.
|
|
@@ -453,6 +455,21 @@ enum MemoryCommand {
|
|
|
453
455
|
},
|
|
454
456
|
}
|
|
455
457
|
|
|
458
|
+
fn print_mcp_servers(servers: &std::collections::BTreeMap<String, mint_core::mcp::McpServer>) {
|
|
459
|
+
if servers.is_empty() {
|
|
460
|
+
println!("{DIM}(No MCP servers configured.){RESET}\n");
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
for (name, srv) in servers {
|
|
464
|
+
let args_str = srv.args.join(" ");
|
|
465
|
+
println!(
|
|
466
|
+
" {BLUE}●{RESET} {name} {DIM}({} {}){RESET}",
|
|
467
|
+
srv.command, args_str
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
println!();
|
|
471
|
+
}
|
|
472
|
+
|
|
456
473
|
#[tokio::main]
|
|
457
474
|
async fn main() -> Result<()> {
|
|
458
475
|
match Cli::parse().command {
|
|
@@ -523,6 +540,99 @@ async fn main() -> Result<()> {
|
|
|
523
540
|
Command::Agent { task } => {
|
|
524
541
|
run_cli_agent_task(task).await?;
|
|
525
542
|
}
|
|
543
|
+
Command::Auto => {
|
|
544
|
+
println!("🚀 Starting Mint Browser Automation Environment...");
|
|
545
|
+
let config = mint_core::load_config()?;
|
|
546
|
+
mint_core::spawn_automation_browser(&config)
|
|
547
|
+
.await
|
|
548
|
+
.map_err(anyhow::Error::msg)?;
|
|
549
|
+
|
|
550
|
+
// Enable the browser tools if they are disabled
|
|
551
|
+
let mut config_mut = config.clone();
|
|
552
|
+
let mut changed = false;
|
|
553
|
+
for tool in &[
|
|
554
|
+
"browser_open",
|
|
555
|
+
"browser_click",
|
|
556
|
+
"browser_type",
|
|
557
|
+
"browser_read",
|
|
558
|
+
] {
|
|
559
|
+
if config_mut.disabled_tools.contains(&tool.to_string()) {
|
|
560
|
+
config_mut.disabled_tools.retain(|x| x != *tool);
|
|
561
|
+
changed = true;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if changed {
|
|
565
|
+
mint_core::save_config(&config_mut)?;
|
|
566
|
+
println!(
|
|
567
|
+
"✅ Enabled browser automation tools in config: browser_open, browser_click, browser_type, browser_read"
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
println!(
|
|
572
|
+
"🌐 Isolated browser running with remote debugging on http://127.0.0.1:9222"
|
|
573
|
+
);
|
|
574
|
+
println!(
|
|
575
|
+
"💬 Keep this terminal open while you want Mint to automate browser tasks."
|
|
576
|
+
);
|
|
577
|
+
println!("Press Ctrl+C to terminate the automation browser session.");
|
|
578
|
+
println!("----------------------------------------------------------------------");
|
|
579
|
+
|
|
580
|
+
// Start tailing the log file in a background task
|
|
581
|
+
let log_dir = dirs::config_dir()
|
|
582
|
+
.unwrap_or_else(|| std::env::temp_dir())
|
|
583
|
+
.join("mint");
|
|
584
|
+
let log_file = log_dir.join("browser-automation.log");
|
|
585
|
+
|
|
586
|
+
// Clear existing log file on startup
|
|
587
|
+
let _ = std::fs::remove_file(&log_file);
|
|
588
|
+
|
|
589
|
+
let log_file_clone = log_file.clone();
|
|
590
|
+
tokio::spawn(async move {
|
|
591
|
+
use std::io::{BufRead, BufReader, Seek, SeekFrom};
|
|
592
|
+
let mut file_pos = 0;
|
|
593
|
+
loop {
|
|
594
|
+
if log_file_clone.exists() {
|
|
595
|
+
if let Ok(mut file) = std::fs::File::open(&log_file_clone) {
|
|
596
|
+
if let Ok(_) = file.seek(SeekFrom::Start(file_pos)) {
|
|
597
|
+
let reader = BufReader::new(file);
|
|
598
|
+
for line in reader.lines() {
|
|
599
|
+
if let Ok(line_str) = line {
|
|
600
|
+
if line_str.contains("[NAVIGATE]")
|
|
601
|
+
|| line_str.contains("[NAVIGATE_SUCCESS]")
|
|
602
|
+
{
|
|
603
|
+
println!("🌐 {line_str}");
|
|
604
|
+
} else if line_str.contains("[CLICK]")
|
|
605
|
+
|| line_str.contains("[CLICK_SUCCESS]")
|
|
606
|
+
{
|
|
607
|
+
println!("🖱️ {line_str}");
|
|
608
|
+
} else if line_str.contains("[TYPE]")
|
|
609
|
+
|| line_str.contains("[TYPE_SUCCESS]")
|
|
610
|
+
{
|
|
611
|
+
println!("⌨️ {line_str}");
|
|
612
|
+
} else if line_str.contains("[READ]")
|
|
613
|
+
|| line_str.contains("[READ_SUCCESS]")
|
|
614
|
+
{
|
|
615
|
+
println!("📖 {line_str}");
|
|
616
|
+
} else if line_str.contains("_ERROR]") {
|
|
617
|
+
println!("❌ {line_str}");
|
|
618
|
+
} else {
|
|
619
|
+
println!("📝 {line_str}");
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if let Ok(pos) = log_file_clone.metadata().map(|m| m.len()) {
|
|
624
|
+
file_pos = pos;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
tokio::signal::ctrl_c().await.ok();
|
|
634
|
+
println!("\n👋 Terminating Mint Browser Automation Environment...");
|
|
635
|
+
}
|
|
526
636
|
Command::Web => {
|
|
527
637
|
launch_mint_target("web".into()).await?;
|
|
528
638
|
}
|
|
@@ -539,7 +649,10 @@ async fn main() -> Result<()> {
|
|
|
539
649
|
mcp::add(&name, &command, args, env)?;
|
|
540
650
|
println!("Added MCP server: {name}");
|
|
541
651
|
}
|
|
542
|
-
McpCommand::List =>
|
|
652
|
+
McpCommand::List => {
|
|
653
|
+
println!("\n{BLUE}MCP servers:{RESET}");
|
|
654
|
+
print_mcp_servers(&mcp::list()?);
|
|
655
|
+
}
|
|
543
656
|
McpCommand::Remove { name } => {
|
|
544
657
|
println!(
|
|
545
658
|
"{}",
|
|
@@ -585,10 +698,55 @@ async fn main() -> Result<()> {
|
|
|
585
698
|
Command::Learn { path, list, delete } => {
|
|
586
699
|
let memory = MemoryStore::open_default()?;
|
|
587
700
|
if list {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
701
|
+
let mut skills = memory.learned_skills(100)?;
|
|
702
|
+
|
|
703
|
+
if let Some(home) = dirs::home_dir() {
|
|
704
|
+
let global_skills_path =
|
|
705
|
+
home.join(".config").join("mint").join("mint-skills");
|
|
706
|
+
mint_core::skills::load_skills_from_dir(&global_skills_path, &mut skills);
|
|
707
|
+
}
|
|
708
|
+
if let Ok(root) = std::env::current_dir() {
|
|
709
|
+
if let Ok(root) = root.canonicalize() {
|
|
710
|
+
let workspace_skills_path1 = root.join(".agents").join("skills");
|
|
711
|
+
mint_core::skills::load_skills_from_dir(
|
|
712
|
+
&workspace_skills_path1,
|
|
713
|
+
&mut skills,
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
let workspace_skills_path2 = root.join("skills");
|
|
717
|
+
mint_core::skills::load_skills_from_dir(
|
|
718
|
+
&workspace_skills_path2,
|
|
719
|
+
&mut skills,
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
let mut unique_skills = std::collections::BTreeMap::new();
|
|
725
|
+
for skill in skills {
|
|
726
|
+
let loc = if skill.source_path.contains("/.config/mint/mint-skills") {
|
|
727
|
+
"Global"
|
|
728
|
+
} else if skill.source_path.contains("/skills")
|
|
729
|
+
|| skill.source_path.contains("/.agents/skills")
|
|
730
|
+
{
|
|
731
|
+
"Workspace"
|
|
732
|
+
} else {
|
|
733
|
+
"Taught"
|
|
734
|
+
};
|
|
735
|
+
unique_skills.insert(skill.name.clone(), (skill, loc));
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if unique_skills.is_empty() {
|
|
739
|
+
println!("No learned skills found.");
|
|
740
|
+
} else {
|
|
741
|
+
println!("Learned AI Skills:");
|
|
742
|
+
for (name, (skill, loc)) in &unique_skills {
|
|
743
|
+
if *loc == "Taught" {
|
|
744
|
+
println!(" ● [{}] {}", loc, name);
|
|
745
|
+
} else {
|
|
746
|
+
println!(" ● [{}] {} (Source: {})", loc, name, skill.source_path);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
592
750
|
} else if let Some(identifier) = delete {
|
|
593
751
|
println!("{}", memory.delete_learned_skill(&identifier)?);
|
|
594
752
|
} else if let Some(path) = path {
|
|
@@ -1315,6 +1473,9 @@ impl CliStreamFormatter {
|
|
|
1315
1473
|
print_fn(ch);
|
|
1316
1474
|
}
|
|
1317
1475
|
print_fn('\n');
|
|
1476
|
+
for ch in " ".chars() {
|
|
1477
|
+
print_fn(ch);
|
|
1478
|
+
}
|
|
1318
1479
|
} else {
|
|
1319
1480
|
if is_table_line(&line) {
|
|
1320
1481
|
self.table_buffer.push(line);
|
|
@@ -1327,6 +1488,9 @@ impl CliStreamFormatter {
|
|
|
1327
1488
|
print_fn(ch);
|
|
1328
1489
|
}
|
|
1329
1490
|
print_fn('\n');
|
|
1491
|
+
for ch in " ".chars() {
|
|
1492
|
+
print_fn(ch);
|
|
1493
|
+
}
|
|
1330
1494
|
}
|
|
1331
1495
|
}
|
|
1332
1496
|
}
|
|
@@ -1792,7 +1956,56 @@ async fn handle_slash_command(
|
|
|
1792
1956
|
|
|
1793
1957
|
"/learn" => {
|
|
1794
1958
|
if rest.is_empty() {
|
|
1795
|
-
|
|
1959
|
+
let mut skills = match MemoryStore::open_default() {
|
|
1960
|
+
Ok(m) => match m.learned_skills(100) {
|
|
1961
|
+
Ok(s) => s,
|
|
1962
|
+
Err(e) => {
|
|
1963
|
+
println!("{ERROR}Error loading custom skills: {e}{RESET}\n");
|
|
1964
|
+
return Some(SlashResult::Handled);
|
|
1965
|
+
}
|
|
1966
|
+
},
|
|
1967
|
+
Err(e) => {
|
|
1968
|
+
println!("{ERROR}Memory error: {e}{RESET}\n");
|
|
1969
|
+
return Some(SlashResult::Handled);
|
|
1970
|
+
}
|
|
1971
|
+
};
|
|
1972
|
+
|
|
1973
|
+
if let Some(home) = dirs::home_dir() {
|
|
1974
|
+
let global_skills_path = home.join(".config").join("mint").join("mint-skills");
|
|
1975
|
+
mint_core::skills::load_skills_from_dir(&global_skills_path, &mut skills);
|
|
1976
|
+
}
|
|
1977
|
+
let workspace_skills_path1 = session.current_dir.join(".agents").join("skills");
|
|
1978
|
+
mint_core::skills::load_skills_from_dir(&workspace_skills_path1, &mut skills);
|
|
1979
|
+
let workspace_skills_path2 = session.current_dir.join("skills");
|
|
1980
|
+
mint_core::skills::load_skills_from_dir(&workspace_skills_path2, &mut skills);
|
|
1981
|
+
|
|
1982
|
+
let mut unique_skills = std::collections::BTreeMap::new();
|
|
1983
|
+
for skill in skills {
|
|
1984
|
+
let loc = if skill.source_path.contains("/.config/mint/mint-skills") {
|
|
1985
|
+
"Global"
|
|
1986
|
+
} else if skill.source_path.contains("/skills")
|
|
1987
|
+
|| skill.source_path.contains("/.agents/skills")
|
|
1988
|
+
{
|
|
1989
|
+
"Workspace"
|
|
1990
|
+
} else {
|
|
1991
|
+
"Taught"
|
|
1992
|
+
};
|
|
1993
|
+
unique_skills.insert(skill.name.clone(), (skill, loc));
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
if unique_skills.is_empty() {
|
|
1997
|
+
println!("No learned skills found. Use '/learn <path>' to add one.\n");
|
|
1998
|
+
} else {
|
|
1999
|
+
println!("Learned AI Skills:");
|
|
2000
|
+
for (name, (skill, loc)) in &unique_skills {
|
|
2001
|
+
if *loc == "Taught" {
|
|
2002
|
+
println!(" ● [{}] {}", loc, name);
|
|
2003
|
+
} else {
|
|
2004
|
+
println!(" ● [{}] {} (Source: {})", loc, name, skill.source_path);
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
println!();
|
|
2008
|
+
}
|
|
1796
2009
|
} else {
|
|
1797
2010
|
let path = PathBuf::from(rest);
|
|
1798
2011
|
let path = if path.is_absolute() {
|
|
@@ -1834,8 +2047,10 @@ async fn handle_slash_command(
|
|
|
1834
2047
|
println!(
|
|
1835
2048
|
" {DIM}[{}]{RESET} {BLUE}You:{RESET} {}",
|
|
1836
2049
|
&item.created_at[..16.min(item.created_at.len())],
|
|
1837
|
-
if item.user_text.
|
|
1838
|
-
|
|
2050
|
+
if item.user_text.chars().count() > 80 {
|
|
2051
|
+
let short: String =
|
|
2052
|
+
item.user_text.chars().take(80).collect();
|
|
2053
|
+
format!("{}…", short)
|
|
1839
2054
|
} else {
|
|
1840
2055
|
item.user_text.clone()
|
|
1841
2056
|
}
|
|
@@ -1903,15 +2118,9 @@ async fn handle_slash_command(
|
|
|
1903
2118
|
.unwrap_or((rest, ""));
|
|
1904
2119
|
match subcmd {
|
|
1905
2120
|
"list" | "" => match mcp::list() {
|
|
1906
|
-
Ok(servers) if servers.is_empty() => {
|
|
1907
|
-
println!("{DIM}No MCP servers configured.{RESET}\n");
|
|
1908
|
-
}
|
|
1909
2121
|
Ok(servers) => {
|
|
1910
2122
|
println!("\n{BLUE}MCP servers:{RESET}");
|
|
1911
|
-
|
|
1912
|
-
Ok(json) => println!("{json}\n"),
|
|
1913
|
-
Err(e) => println!("{ERROR}Error:{RESET} {e}\n"),
|
|
1914
|
-
}
|
|
2123
|
+
print_mcp_servers(&servers);
|
|
1915
2124
|
}
|
|
1916
2125
|
Err(e) => println!("{ERROR}MCP error:{RESET} {e}\n"),
|
|
1917
2126
|
},
|
|
@@ -2291,7 +2500,7 @@ async fn run_interactive_chat() -> Result<()> {
|
|
|
2291
2500
|
.to_owned();
|
|
2292
2501
|
|
|
2293
2502
|
println!();
|
|
2294
|
-
print!("{MINT}Mint:{RESET} {DIM}Thinking...{RESET}");
|
|
2503
|
+
print!(" {MINT}Mint:{RESET} {DIM}Thinking...{RESET}");
|
|
2295
2504
|
let _ = io::stdout().flush();
|
|
2296
2505
|
|
|
2297
2506
|
let mut system_instruction = format!(
|
|
@@ -2337,7 +2546,7 @@ async fn run_interactive_chat() -> Result<()> {
|
|
|
2337
2546
|
|chunk| {
|
|
2338
2547
|
if first_chunk {
|
|
2339
2548
|
first_chunk = false;
|
|
2340
|
-
print!("\r\x1b[2K{MINT}Mint:{RESET} ");
|
|
2549
|
+
print!("\r\x1b[2K {MINT}Mint:{RESET} ");
|
|
2341
2550
|
}
|
|
2342
2551
|
filter.process_chunk(&chunk, |text| {
|
|
2343
2552
|
for c in text.chars() {
|
|
@@ -2926,7 +3135,7 @@ fn read_line_interactive(
|
|
|
2926
3135
|
expanded_str = expanded_str.replace(placeholder_str, content);
|
|
2927
3136
|
}
|
|
2928
3137
|
|
|
2929
|
-
println!("{BLUE}You ›{RESET} {}", expanded_str);
|
|
3138
|
+
println!(" {BLUE}You ›{RESET} {}", expanded_str);
|
|
2930
3139
|
let _ = io::stdout().flush();
|
|
2931
3140
|
|
|
2932
3141
|
break Some(InteractiveInput {
|
|
@@ -3157,7 +3366,7 @@ pub fn confirm(prompt: &str) -> Result<bool> {
|
|
|
3157
3366
|
.to_string();
|
|
3158
3367
|
|
|
3159
3368
|
if SESSION_APPROVED.load(std::sync::atomic::Ordering::Relaxed) {
|
|
3160
|
-
println!("{} {MINT}Approve (session-wide){RESET}", clean_prompt);
|
|
3369
|
+
println!(" {} {MINT}Approve (session-wide){RESET}", clean_prompt);
|
|
3161
3370
|
return Ok(true);
|
|
3162
3371
|
}
|
|
3163
3372
|
|
|
@@ -3166,7 +3375,7 @@ pub fn confirm(prompt: &str) -> Result<bool> {
|
|
|
3166
3375
|
use crossterm::tty::IsTty;
|
|
3167
3376
|
|
|
3168
3377
|
if !io::stdout().is_tty() || enable_raw_mode().is_err() {
|
|
3169
|
-
print!("{} [y/N] ", clean_prompt);
|
|
3378
|
+
print!(" {} [y/N] ", clean_prompt);
|
|
3170
3379
|
let _ = io::stdout().flush();
|
|
3171
3380
|
let mut answer = String::new();
|
|
3172
3381
|
io::stdin().read_line(&mut answer)?;
|
|
@@ -3177,7 +3386,7 @@ pub fn confirm(prompt: &str) -> Result<bool> {
|
|
|
3177
3386
|
}
|
|
3178
3387
|
|
|
3179
3388
|
let _ = disable_raw_mode();
|
|
3180
|
-
println!("{}", clean_prompt);
|
|
3389
|
+
println!(" {}", clean_prompt);
|
|
3181
3390
|
|
|
3182
3391
|
let options = ["Approve", "Approve this session", "No"];
|
|
3183
3392
|
let mut selected = 0;
|
|
@@ -3284,7 +3493,7 @@ pub fn confirm(prompt: &str) -> Result<bool> {
|
|
|
3284
3493
|
1 => format!("{MINT}Approve this session{RESET}"),
|
|
3285
3494
|
_ => format!("{ERROR}No{RESET}"),
|
|
3286
3495
|
};
|
|
3287
|
-
println!("{} {}", clean_prompt, result_str);
|
|
3496
|
+
println!(" {} {}", clean_prompt, result_str);
|
|
3288
3497
|
let _ = io::stdout().flush();
|
|
3289
3498
|
|
|
3290
3499
|
match choice {
|
|
@@ -147,6 +147,15 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
147
147
|
});
|
|
148
148
|
send_json_response(socket, "200 OK", &status_json.to_string()).await;
|
|
149
149
|
}
|
|
150
|
+
("GET", "/api/detect-tools") => {
|
|
151
|
+
let tools_json = serde_json::json!({
|
|
152
|
+
"docker": crate::config::which("docker"),
|
|
153
|
+
"git": crate::config::which("git"),
|
|
154
|
+
"gh": crate::config::which("gh"),
|
|
155
|
+
"node": crate::config::which("node")
|
|
156
|
+
});
|
|
157
|
+
send_json_response(socket, "200 OK", &tools_json.to_string()).await;
|
|
158
|
+
}
|
|
150
159
|
("GET", "/api/system-info") => {
|
|
151
160
|
send_json_response(socket, "200 OK", &system_info().to_string()).await;
|
|
152
161
|
}
|
|
@@ -272,6 +281,58 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
272
281
|
.await;
|
|
273
282
|
}
|
|
274
283
|
}
|
|
284
|
+
("POST", "/api/interactions") => {
|
|
285
|
+
#[derive(Deserialize)]
|
|
286
|
+
#[serde(rename_all = "camelCase")]
|
|
287
|
+
struct ApiSaveInteraction {
|
|
288
|
+
chat_id: String,
|
|
289
|
+
user_text: String,
|
|
290
|
+
provider: String,
|
|
291
|
+
model: String,
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if let Ok(req) = serde_json::from_str::<ApiSaveInteraction>(body) {
|
|
295
|
+
if let Ok(memory) = MemoryStore::open_default() {
|
|
296
|
+
match memory.add_interaction_for_chat(
|
|
297
|
+
&req.chat_id,
|
|
298
|
+
&req.user_text,
|
|
299
|
+
"",
|
|
300
|
+
&req.provider,
|
|
301
|
+
&req.model,
|
|
302
|
+
) {
|
|
303
|
+
Ok(row_id) => {
|
|
304
|
+
let res_json = json!({ "success": true, "id": row_id });
|
|
305
|
+
send_json_response(socket, "200 OK", &res_json.to_string())
|
|
306
|
+
.await;
|
|
307
|
+
}
|
|
308
|
+
Err(error) => {
|
|
309
|
+
let err_json =
|
|
310
|
+
json!({ "success": false, "message": error.to_string() });
|
|
311
|
+
send_json_response(
|
|
312
|
+
socket,
|
|
313
|
+
"500 Internal Server Error",
|
|
314
|
+
&err_json.to_string(),
|
|
315
|
+
)
|
|
316
|
+
.await;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
send_json_response(
|
|
321
|
+
socket,
|
|
322
|
+
"500 Internal Server Error",
|
|
323
|
+
"{\"success\":false,\"message\":\"db error\"}",
|
|
324
|
+
)
|
|
325
|
+
.await;
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
send_json_response(
|
|
329
|
+
socket,
|
|
330
|
+
"400 Bad Request",
|
|
331
|
+
"{\"success\":false,\"message\":\"invalid body\"}",
|
|
332
|
+
)
|
|
333
|
+
.await;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
275
336
|
("POST", "/api/interactions/agent-activity") => {
|
|
276
337
|
#[derive(Deserialize)]
|
|
277
338
|
#[serde(rename_all = "camelCase")]
|
|
@@ -414,19 +475,22 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
414
475
|
chat_id: Option<String>,
|
|
415
476
|
image_data_uri: Option<String>,
|
|
416
477
|
audio_data_uri: Option<String>,
|
|
478
|
+
document_attachment: Option<crate::chat::DocumentAttachment>,
|
|
417
479
|
}
|
|
418
480
|
|
|
419
481
|
if let Ok(req) = serde_json::from_str::<ApiChatRequest>(body) {
|
|
420
482
|
let config = load_config().unwrap_or_default();
|
|
421
|
-
let
|
|
483
|
+
let chat_req = ChatRequest {
|
|
422
484
|
message: req.message,
|
|
423
485
|
system_instruction: req.system_instruction.unwrap_or_default(),
|
|
424
486
|
chat_id: req.chat_id,
|
|
425
487
|
image_data_uri: req.image_data_uri,
|
|
426
488
|
audio_data_uri: req.audio_data_uri,
|
|
427
|
-
document_attachment:
|
|
489
|
+
document_attachment: req.document_attachment,
|
|
428
490
|
workspace_path: None,
|
|
429
491
|
};
|
|
492
|
+
let mut chat_req =
|
|
493
|
+
chat_req.with_document_context(&config).unwrap_or(chat_req);
|
|
430
494
|
let sent_image = chat_req.image_data_uri.clone();
|
|
431
495
|
let sent_message = chat_req.message.clone();
|
|
432
496
|
|
|
@@ -495,19 +559,22 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
495
559
|
chat_id: Option<String>,
|
|
496
560
|
image_data_uri: Option<String>,
|
|
497
561
|
audio_data_uri: Option<String>,
|
|
562
|
+
document_attachment: Option<crate::chat::DocumentAttachment>,
|
|
498
563
|
}
|
|
499
564
|
|
|
500
565
|
if let Ok(req) = serde_json::from_str::<ApiChatRequest>(body) {
|
|
501
566
|
let config = load_config().unwrap_or_default();
|
|
502
|
-
let
|
|
567
|
+
let chat_req = ChatRequest {
|
|
503
568
|
message: req.message,
|
|
504
569
|
system_instruction: req.system_instruction.unwrap_or_default(),
|
|
505
570
|
chat_id: req.chat_id,
|
|
506
571
|
image_data_uri: req.image_data_uri,
|
|
507
572
|
audio_data_uri: req.audio_data_uri,
|
|
508
|
-
document_attachment:
|
|
573
|
+
document_attachment: req.document_attachment,
|
|
509
574
|
workspace_path: None,
|
|
510
575
|
};
|
|
576
|
+
let mut chat_req =
|
|
577
|
+
chat_req.with_document_context(&config).unwrap_or(chat_req);
|
|
511
578
|
let sent_image = chat_req.image_data_uri.clone();
|
|
512
579
|
let sent_message = chat_req.message.clone();
|
|
513
580
|
|