@pheem49/mint 1.6.3 → 1.7.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/Cargo.lock +3 -3
- package/Cargo.toml +1 -1
- package/README.md +82 -21
- package/assets/Mint_CLI.png +0 -0
- package/assets/Mint_Desktop.png +0 -0
- package/assets/Mint_Web.png +0 -0
- package/assets/Mint_WorkSpace.png +0 -0
- package/assets/Setting.png +0 -0
- package/assets/bridges.svg +4 -0
- package/assets/chat.svg +3 -0
- package/assets/code.svg +4 -0
- package/assets/features.svg +3 -0
- package/assets/imagegen.svg +5 -0
- package/assets/live2d.svg +6 -0
- package/assets/memory.svg +5 -0
- package/assets/screencapture.svg +4 -0
- package/assets/setup.svg +3 -0
- package/assets/tools.svg +4 -0
- package/bin/mint +0 -0
- package/crates/mint-cli/src/agent.rs +47 -15
- package/crates/mint-cli/src/main.rs +534 -7
- package/crates/mint-cli/src/onboard.rs +134 -1
- package/crates/mint-cli/src/setup.rs +173 -32
- package/crates/mint-core/src/api_server.rs +147 -3
- package/crates/mint-core/src/config.rs +22 -3
- package/crates/mint-core/src/image_gen.rs +895 -0
- package/crates/mint-core/src/lib.rs +4 -0
- package/crates/mint-core/src/memory.rs +27 -1
- package/crates/mint-core/src/orchestration.rs +1 -1
- package/crates/mint-core/src/plugins.rs +32 -0
- package/crates/mint-core/tests/memory_persistence.rs +28 -0
- package/package.json +1 -1
- package/src/bin/index.js +0 -0
- package/src/renderer/src/agentProgress.ts +39 -0
- package/src/renderer/src/components/ChatPanel.tsx +547 -47
- package/src/renderer/src/components/DashboardSidebar.tsx +12 -4
- package/src/renderer/src/components/ImageStudioPanel.tsx +638 -0
- package/src/renderer/src/components/MintDashboard.tsx +84 -31
- package/src/renderer/src/components/ModelPanel.tsx +2 -2
- package/src/renderer/src/components/PicturesLibrary.tsx +5 -1
- package/src/renderer/src/components/SettingsWindow.tsx +29 -20
- package/src/renderer/src/components/SpotlightWindow.tsx +8 -8
- package/src/renderer/src/components/WorkspacePanel.tsx +1 -1
- package/src/renderer/src/css/image-studio.css +722 -0
- package/src/renderer/src/css/settings.css +13 -3
- package/src/renderer/src/css/styles.css +245 -43
- package/src/renderer/src/index.css +152 -0
- package/src/renderer/src/tauri.ts +148 -0
- package/src/renderer/src-web/App.tsx +16 -1
- package/src/renderer/src-web/agentProgress.ts +39 -0
- package/src/renderer/src-web/components/ChatPanel.tsx +568 -46
- package/src/renderer/src-web/components/DashboardSidebar.tsx +10 -2
- package/src/renderer/src-web/components/ImageStudioPanel.tsx +638 -0
- package/src/renderer/src-web/components/MintDashboard.tsx +65 -19
- package/src/renderer/src-web/components/PicturesLibrary.tsx +5 -1
- package/src/renderer/src-web/components/SettingsWindow.tsx +29 -20
- package/src/renderer/src-web/css/image-studio.css +722 -0
- package/src/renderer/src-web/css/settings.css +14 -4
- package/src/renderer/src-web/css/styles.css +292 -44
- package/src/renderer/src-web/index.css +152 -0
- package/src/renderer/src-web/tauri.ts +149 -0
- package/src-tauri/src/lib.rs +65 -5
- package/src-tauri/tauri.conf.json +1 -1
|
@@ -60,6 +60,24 @@ const HUGGINGFACE_MODEL_PRESETS: &[&str] = &[
|
|
|
60
60
|
"google/gemma-3-27b-it",
|
|
61
61
|
];
|
|
62
62
|
|
|
63
|
+
// ── Image Generation Providers ──────────────────────────────────────────────
|
|
64
|
+
const NANOBANANA_IMAGE_MODEL_PRESETS: &[&str] =
|
|
65
|
+
&["gemini-2.5-flash-image", "gemini-2.0-flash-image"];
|
|
66
|
+
|
|
67
|
+
const DALLE_MODEL_PRESETS: &[&str] = &["dall-e-3", "gpt-image-1", "dall-e-2"];
|
|
68
|
+
|
|
69
|
+
const STABILITY_MODEL_PRESETS: &[&str] =
|
|
70
|
+
&["sd3.5-large", "sd3.5-large-turbo", "sd3-medium", "core"];
|
|
71
|
+
|
|
72
|
+
const IDEOGRAM_MODEL_PRESETS: &[&str] = &["V_3", "V_2", "V_2_TURBO"];
|
|
73
|
+
|
|
74
|
+
const REPLICATE_MODEL_PRESETS: &[&str] = &[
|
|
75
|
+
"black-forest-labs/flux-1.1-pro",
|
|
76
|
+
"black-forest-labs/flux-schnell",
|
|
77
|
+
"stability-ai/sdxl",
|
|
78
|
+
"bytedance/sdxl-lightning-4step",
|
|
79
|
+
];
|
|
80
|
+
|
|
63
81
|
pub async fn run() -> Result<()> {
|
|
64
82
|
let mut config = load_config()?;
|
|
65
83
|
|
|
@@ -231,6 +249,37 @@ pub async fn run() -> Result<()> {
|
|
|
231
249
|
.and_then(|v| v.as_bool())
|
|
232
250
|
.unwrap_or(false),
|
|
233
251
|
},
|
|
252
|
+
// ── Image Generation ─────────────────────────────────────────────────
|
|
253
|
+
OnboardService {
|
|
254
|
+
category: "Image Generation",
|
|
255
|
+
name: "Google NanoBanana (Gemini Images) [uses Gemini key]",
|
|
256
|
+
key: "img_nanobanana",
|
|
257
|
+
enabled: !config.api_key.is_empty() && !config.nanobanana_model.is_empty(),
|
|
258
|
+
},
|
|
259
|
+
OnboardService {
|
|
260
|
+
category: "Image Generation",
|
|
261
|
+
name: "OpenAI DALL·E [uses OpenAI key]",
|
|
262
|
+
key: "img_dalle",
|
|
263
|
+
enabled: !config.openai_api_key.is_empty() && !config.dalle_model.is_empty(),
|
|
264
|
+
},
|
|
265
|
+
OnboardService {
|
|
266
|
+
category: "Image Generation",
|
|
267
|
+
name: "Stability AI (Stable Diffusion)",
|
|
268
|
+
key: "img_stability",
|
|
269
|
+
enabled: !config.stability_api_key.is_empty(),
|
|
270
|
+
},
|
|
271
|
+
OnboardService {
|
|
272
|
+
category: "Image Generation",
|
|
273
|
+
name: "Ideogram v3",
|
|
274
|
+
key: "img_ideogram",
|
|
275
|
+
enabled: !config.ideogram_api_key.is_empty(),
|
|
276
|
+
},
|
|
277
|
+
OnboardService {
|
|
278
|
+
category: "Image Generation",
|
|
279
|
+
name: "Replicate (FLUX / SDXL / custom)",
|
|
280
|
+
key: "img_replicate",
|
|
281
|
+
enabled: !config.replicate_api_key.is_empty(),
|
|
282
|
+
},
|
|
234
283
|
];
|
|
235
284
|
|
|
236
285
|
let mut cursor = 0;
|
|
@@ -842,7 +891,91 @@ pub async fn run() -> Result<()> {
|
|
|
842
891
|
);
|
|
843
892
|
}
|
|
844
893
|
|
|
845
|
-
|
|
894
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
895
|
+
// Image Generation providers
|
|
896
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
897
|
+
|
|
898
|
+
// NanoBanana (Gemini Images)
|
|
899
|
+
if is_selected("img_nanobanana", &services) {
|
|
900
|
+
println!("\n\x1b[36m--- Google NanoBanana (Gemini Images) ---\x1b[0m");
|
|
901
|
+
println!(
|
|
902
|
+
"\x1b[90mUses the same Gemini API key as Step 1. Select the image generation model.\x1b[0m"
|
|
903
|
+
);
|
|
904
|
+
config.nanobanana_model = prompt_select_or_custom(
|
|
905
|
+
"NanoBanana Model",
|
|
906
|
+
static_model_options(NANOBANANA_IMAGE_MODEL_PRESETS),
|
|
907
|
+
Some(&config.nanobanana_model),
|
|
908
|
+
"Custom NanoBanana model...",
|
|
909
|
+
)?;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// DALL·E
|
|
913
|
+
if is_selected("img_dalle", &services) {
|
|
914
|
+
println!("\n\x1b[36m--- OpenAI DALL·E ---\x1b[0m");
|
|
915
|
+
println!(
|
|
916
|
+
"\x1b[90mUses the same OpenAI API key. DALL·E 3 supports only 1 image per request; DALL·E 2 supports up to 10.\x1b[0m"
|
|
917
|
+
);
|
|
918
|
+
config.dalle_model = prompt_select_or_custom(
|
|
919
|
+
"DALL·E Model",
|
|
920
|
+
static_model_options(DALLE_MODEL_PRESETS),
|
|
921
|
+
Some(&config.dalle_model),
|
|
922
|
+
"Custom DALL·E model...",
|
|
923
|
+
)?;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// Stability AI
|
|
927
|
+
if is_selected("img_stability", &services) {
|
|
928
|
+
println!("\n\x1b[36m--- Stability AI ---\x1b[0m");
|
|
929
|
+
println!(
|
|
930
|
+
"\x1b[90mGet your API key at https://platform.stability.ai/. Supports SD3.5 Large, SD3 Medium, and Stable Image Core.\x1b[0m"
|
|
931
|
+
);
|
|
932
|
+
config.stability_api_key =
|
|
933
|
+
prompt_sensitive("Stability AI API Key", &config.stability_api_key)?;
|
|
934
|
+
config.stability_model = prompt_select_or_custom(
|
|
935
|
+
"Stability Model",
|
|
936
|
+
static_model_options(STABILITY_MODEL_PRESETS),
|
|
937
|
+
Some(&config.stability_model),
|
|
938
|
+
"Custom Stability model...",
|
|
939
|
+
)?;
|
|
940
|
+
} else {
|
|
941
|
+
config.stability_api_key = String::new();
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// Ideogram
|
|
945
|
+
if is_selected("img_ideogram", &services) {
|
|
946
|
+
println!("\n\x1b[36m--- Ideogram ---\x1b[0m");
|
|
947
|
+
println!(
|
|
948
|
+
"\x1b[90mGet your API key at https://ideogram.ai/api. Supports V_3, V_2, and V_2_TURBO.\x1b[0m"
|
|
949
|
+
);
|
|
950
|
+
config.ideogram_api_key = prompt_sensitive("Ideogram API Key", &config.ideogram_api_key)?;
|
|
951
|
+
config.ideogram_model = prompt_select_or_custom(
|
|
952
|
+
"Ideogram Model",
|
|
953
|
+
static_model_options(IDEOGRAM_MODEL_PRESETS),
|
|
954
|
+
Some(&config.ideogram_model),
|
|
955
|
+
"Custom Ideogram model...",
|
|
956
|
+
)?;
|
|
957
|
+
} else {
|
|
958
|
+
config.ideogram_api_key = String::new();
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Replicate
|
|
962
|
+
if is_selected("img_replicate", &services) {
|
|
963
|
+
println!("\n\x1b[36m--- Replicate ---\x1b[0m");
|
|
964
|
+
println!(
|
|
965
|
+
"\x1b[90mGet your API token at https://replicate.com/account/api-tokens. Works with FLUX, SDXL, and any public image model.\x1b[0m"
|
|
966
|
+
);
|
|
967
|
+
config.replicate_api_key =
|
|
968
|
+
prompt_sensitive("Replicate API Token", &config.replicate_api_key)?;
|
|
969
|
+
config.replicate_model = prompt_select_or_custom(
|
|
970
|
+
"Replicate Model (owner/model-name)",
|
|
971
|
+
static_model_options(REPLICATE_MODEL_PRESETS),
|
|
972
|
+
Some(&config.replicate_model),
|
|
973
|
+
"Custom Replicate model...",
|
|
974
|
+
)?;
|
|
975
|
+
} else {
|
|
976
|
+
config.replicate_api_key = String::new();
|
|
977
|
+
}
|
|
978
|
+
|
|
846
979
|
save_config(&config)?;
|
|
847
980
|
println!("\x1b[32m✅ Configuration saved successfully!\x1b[0m");
|
|
848
981
|
Ok(())
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use anyhow::Result;
|
|
2
2
|
use crossterm::event::{self, Event, KeyCode};
|
|
3
3
|
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
|
4
|
-
use mint_core::{load_config, save_config};
|
|
4
|
+
use mint_core::{load_config, save_config, native_plugins};
|
|
5
5
|
use std::io::{self, Write};
|
|
6
6
|
|
|
7
7
|
struct ToolOption {
|
|
@@ -13,15 +13,6 @@ struct ToolOption {
|
|
|
13
13
|
pub async fn run() -> Result<Option<String>> {
|
|
14
14
|
let mut config = load_config()?;
|
|
15
15
|
|
|
16
|
-
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
17
|
-
println!("\x1b[32m Mint CLI Tool Manager Wizard\x1b[0m");
|
|
18
|
-
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
19
|
-
println!("Configure which agent tools are enabled or disabled:");
|
|
20
|
-
println!(
|
|
21
|
-
" \x1b[90m[Keyboard Controls: ↑/↓: Navigate | Space: Toggle | a: All | i: Invert | Enter: Confirm]\x1b[0m"
|
|
22
|
-
);
|
|
23
|
-
println!();
|
|
24
|
-
|
|
25
16
|
let mut options = vec![
|
|
26
17
|
ToolOption {
|
|
27
18
|
name: "list_files (List Workspace Files)",
|
|
@@ -178,7 +169,7 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
178
169
|
];
|
|
179
170
|
|
|
180
171
|
let mut cursor = 0;
|
|
181
|
-
|
|
172
|
+
redraw_phase_1(&options, cursor);
|
|
182
173
|
enable_raw_mode()?;
|
|
183
174
|
|
|
184
175
|
loop {
|
|
@@ -204,8 +195,7 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
204
195
|
cursor = options.len() - 1;
|
|
205
196
|
}
|
|
206
197
|
disable_raw_mode()?;
|
|
207
|
-
|
|
208
|
-
print_options(&options, cursor);
|
|
198
|
+
redraw_phase_1(&options, cursor);
|
|
209
199
|
enable_raw_mode()?;
|
|
210
200
|
}
|
|
211
201
|
KeyCode::Down => {
|
|
@@ -215,15 +205,13 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
215
205
|
cursor = 0;
|
|
216
206
|
}
|
|
217
207
|
disable_raw_mode()?;
|
|
218
|
-
|
|
219
|
-
print_options(&options, cursor);
|
|
208
|
+
redraw_phase_1(&options, cursor);
|
|
220
209
|
enable_raw_mode()?;
|
|
221
210
|
}
|
|
222
211
|
KeyCode::Char(' ') => {
|
|
223
212
|
options[cursor].enabled = !options[cursor].enabled;
|
|
224
213
|
disable_raw_mode()?;
|
|
225
|
-
|
|
226
|
-
print_options(&options, cursor);
|
|
214
|
+
redraw_phase_1(&options, cursor);
|
|
227
215
|
enable_raw_mode()?;
|
|
228
216
|
}
|
|
229
217
|
KeyCode::Char('a') => {
|
|
@@ -231,8 +219,7 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
231
219
|
opt.enabled = true;
|
|
232
220
|
}
|
|
233
221
|
disable_raw_mode()?;
|
|
234
|
-
|
|
235
|
-
print_options(&options, cursor);
|
|
222
|
+
redraw_phase_1(&options, cursor);
|
|
236
223
|
enable_raw_mode()?;
|
|
237
224
|
}
|
|
238
225
|
KeyCode::Char('i') => {
|
|
@@ -240,8 +227,7 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
240
227
|
opt.enabled = !opt.enabled;
|
|
241
228
|
}
|
|
242
229
|
disable_raw_mode()?;
|
|
243
|
-
|
|
244
|
-
print_options(&options, cursor);
|
|
230
|
+
redraw_phase_1(&options, cursor);
|
|
245
231
|
enable_raw_mode()?;
|
|
246
232
|
}
|
|
247
233
|
KeyCode::Enter => {
|
|
@@ -270,15 +256,135 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
270
256
|
config.disabled_tools = disabled;
|
|
271
257
|
save_config(&config)?;
|
|
272
258
|
|
|
273
|
-
println!("\x1b[32mSuccessfully updated tool configurations!\x1b[0m\n");
|
|
259
|
+
println!("\x1b[32mSuccessfully updated agent tool configurations!\x1b[0m\n");
|
|
274
260
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
261
|
+
let all_native = native_plugins();
|
|
262
|
+
let allowed_plugins: std::collections::HashSet<String> = config
|
|
263
|
+
.extra
|
|
264
|
+
.get("allowedNativePlugins")
|
|
265
|
+
.and_then(|v| v.as_array())
|
|
266
|
+
.map(|arr| {
|
|
267
|
+
arr.iter()
|
|
268
|
+
.filter_map(|v| v.as_str().map(|s| s.to_string()))
|
|
269
|
+
.collect()
|
|
270
|
+
})
|
|
271
|
+
.unwrap_or_else(|| {
|
|
272
|
+
vec![
|
|
273
|
+
"dev_tools".to_string(),
|
|
274
|
+
"system_metrics".to_string(),
|
|
275
|
+
"github".to_string(),
|
|
276
|
+
]
|
|
277
|
+
.into_iter()
|
|
278
|
+
.collect()
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
let mut native_options: Vec<ToolOption> = all_native
|
|
282
|
+
.iter()
|
|
283
|
+
.map(|p| {
|
|
284
|
+
let has_all_permission = allowed_plugins.contains("*");
|
|
285
|
+
let is_enabled = has_all_permission || allowed_plugins.contains(p.name);
|
|
286
|
+
ToolOption {
|
|
287
|
+
name: p.name,
|
|
288
|
+
key: p.name,
|
|
289
|
+
enabled: is_enabled,
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
.collect();
|
|
293
|
+
|
|
294
|
+
let mut native_cursor = 0;
|
|
295
|
+
redraw_phase_2(&native_options, native_cursor);
|
|
296
|
+
enable_raw_mode()?;
|
|
297
|
+
|
|
298
|
+
loop {
|
|
299
|
+
match event::poll(std::time::Duration::from_millis(100)) {
|
|
300
|
+
Ok(true) => {
|
|
301
|
+
if let Event::Key(key_event) = event::read()? {
|
|
302
|
+
if key_event.kind == event::KeyEventKind::Press {
|
|
303
|
+
let is_ctrl_c = matches!(key_event.code, KeyCode::Char('c'))
|
|
304
|
+
&& key_event
|
|
305
|
+
.modifiers
|
|
306
|
+
.contains(crossterm::event::KeyModifiers::CONTROL);
|
|
307
|
+
if is_ctrl_c {
|
|
308
|
+
disable_raw_mode()?;
|
|
309
|
+
println!("\n\x1b[31mSetup cancelled.\x1b[0m");
|
|
310
|
+
return Ok(None);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
match key_event.code {
|
|
314
|
+
KeyCode::Up => {
|
|
315
|
+
if native_cursor > 0 {
|
|
316
|
+
native_cursor -= 1;
|
|
317
|
+
} else {
|
|
318
|
+
native_cursor = native_options.len() - 1;
|
|
319
|
+
}
|
|
320
|
+
disable_raw_mode()?;
|
|
321
|
+
redraw_phase_2(&native_options, native_cursor);
|
|
322
|
+
enable_raw_mode()?;
|
|
323
|
+
}
|
|
324
|
+
KeyCode::Down => {
|
|
325
|
+
if native_cursor < native_options.len() - 1 {
|
|
326
|
+
native_cursor += 1;
|
|
327
|
+
} else {
|
|
328
|
+
native_cursor = 0;
|
|
329
|
+
}
|
|
330
|
+
disable_raw_mode()?;
|
|
331
|
+
redraw_phase_2(&native_options, native_cursor);
|
|
332
|
+
enable_raw_mode()?;
|
|
333
|
+
}
|
|
334
|
+
KeyCode::Char(' ') => {
|
|
335
|
+
native_options[native_cursor].enabled =
|
|
336
|
+
!native_options[native_cursor].enabled;
|
|
337
|
+
disable_raw_mode()?;
|
|
338
|
+
redraw_phase_2(&native_options, native_cursor);
|
|
339
|
+
enable_raw_mode()?;
|
|
340
|
+
}
|
|
341
|
+
KeyCode::Char('a') => {
|
|
342
|
+
for opt in &mut native_options {
|
|
343
|
+
opt.enabled = true;
|
|
344
|
+
}
|
|
345
|
+
disable_raw_mode()?;
|
|
346
|
+
redraw_phase_2(&native_options, native_cursor);
|
|
347
|
+
enable_raw_mode()?;
|
|
348
|
+
}
|
|
349
|
+
KeyCode::Char('i') => {
|
|
350
|
+
for opt in &mut native_options {
|
|
351
|
+
opt.enabled = !opt.enabled;
|
|
352
|
+
}
|
|
353
|
+
disable_raw_mode()?;
|
|
354
|
+
redraw_phase_2(&native_options, native_cursor);
|
|
355
|
+
enable_raw_mode()?;
|
|
356
|
+
}
|
|
357
|
+
KeyCode::Enter => {
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
_ => {}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
Ok(false) => {}
|
|
366
|
+
Err(_) => {
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
disable_raw_mode()?;
|
|
280
372
|
println!();
|
|
281
373
|
|
|
374
|
+
let enabled_natives: Vec<serde_json::Value> = native_options
|
|
375
|
+
.iter()
|
|
376
|
+
.filter(|o| o.enabled)
|
|
377
|
+
.map(|o| serde_json::Value::String(o.key.to_string()))
|
|
378
|
+
.collect();
|
|
379
|
+
|
|
380
|
+
config.extra.insert(
|
|
381
|
+
"allowedNativePlugins".to_string(),
|
|
382
|
+
serde_json::Value::Array(enabled_natives),
|
|
383
|
+
);
|
|
384
|
+
save_config(&config)?;
|
|
385
|
+
|
|
386
|
+
println!("\x1b[32mSuccessfully updated native plugins configurations!\x1b[0m\n");
|
|
387
|
+
|
|
282
388
|
let run_options = vec![
|
|
283
389
|
ToolOption {
|
|
284
390
|
name: "1. CLI (Interactive Terminal Assistant)",
|
|
@@ -298,7 +404,7 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
298
404
|
];
|
|
299
405
|
|
|
300
406
|
let mut run_cursor = 0;
|
|
301
|
-
|
|
407
|
+
redraw_phase_3(&run_options, run_cursor);
|
|
302
408
|
enable_raw_mode()?;
|
|
303
409
|
|
|
304
410
|
loop {
|
|
@@ -324,8 +430,7 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
324
430
|
run_cursor = run_options.len() - 1;
|
|
325
431
|
}
|
|
326
432
|
disable_raw_mode()?;
|
|
327
|
-
|
|
328
|
-
print_run_options(&run_options, run_cursor);
|
|
433
|
+
redraw_phase_3(&run_options, run_cursor);
|
|
329
434
|
enable_raw_mode()?;
|
|
330
435
|
}
|
|
331
436
|
KeyCode::Down => {
|
|
@@ -335,8 +440,7 @@ pub async fn run() -> Result<Option<String>> {
|
|
|
335
440
|
run_cursor = 0;
|
|
336
441
|
}
|
|
337
442
|
disable_raw_mode()?;
|
|
338
|
-
|
|
339
|
-
print_run_options(&run_options, run_cursor);
|
|
443
|
+
redraw_phase_3(&run_options, run_cursor);
|
|
340
444
|
enable_raw_mode()?;
|
|
341
445
|
}
|
|
342
446
|
KeyCode::Enter => {
|
|
@@ -388,3 +492,40 @@ fn print_run_options(options: &[ToolOption], cursor: usize) {
|
|
|
388
492
|
}
|
|
389
493
|
let _ = io::stdout().flush();
|
|
390
494
|
}
|
|
495
|
+
|
|
496
|
+
fn redraw_phase_1(options: &[ToolOption], cursor: usize) {
|
|
497
|
+
print!("\x1b[2J\x1b[1;1H");
|
|
498
|
+
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
499
|
+
println!("\x1b[32m Mint CLI Tool Manager Wizard\x1b[0m");
|
|
500
|
+
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
501
|
+
println!("Configure which agent tools are enabled or disabled:");
|
|
502
|
+
println!(
|
|
503
|
+
" \x1b[90m[Keyboard Controls: ↑/↓: Navigate | Space: Toggle | a: All | i: Invert | Enter: Confirm]\x1b[0m"
|
|
504
|
+
);
|
|
505
|
+
println!();
|
|
506
|
+
print_options(options, cursor);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
fn redraw_phase_2(options: &[ToolOption], cursor: usize) {
|
|
510
|
+
print!("\x1b[2J\x1b[1;1H");
|
|
511
|
+
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
512
|
+
println!("\x1b[32m Configure Native Plugins Access\x1b[0m");
|
|
513
|
+
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
514
|
+
println!("Select which native plugins are allowed to run:");
|
|
515
|
+
println!(
|
|
516
|
+
" \x1b[90m[Keyboard Controls: ↑/↓: Navigate | Space: Toggle | a: All | i: Invert | Enter: Confirm]\x1b[0m"
|
|
517
|
+
);
|
|
518
|
+
println!();
|
|
519
|
+
print_options(options, cursor);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
fn redraw_phase_3(options: &[ToolOption], cursor: usize) {
|
|
523
|
+
print!("\x1b[2J\x1b[1;1H");
|
|
524
|
+
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
525
|
+
println!("\x1b[32m Choose where to run Mint AI Agent\x1b[0m");
|
|
526
|
+
println!("\x1b[36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
|
|
527
|
+
println!("Select the environment you want to launch:");
|
|
528
|
+
println!(" \x1b[90m[Keyboard Controls: ↑/↓: Navigate | Enter: Confirm]\x1b[0m");
|
|
529
|
+
println!();
|
|
530
|
+
print_run_options(options, cursor);
|
|
531
|
+
}
|
|
@@ -11,9 +11,10 @@ use tokio::net::TcpListener;
|
|
|
11
11
|
|
|
12
12
|
use crate::{
|
|
13
13
|
AgentProgress, ApprovalOutcome, ChatRequest, ChatResponse, DEFAULT_CONVERSATION_ID,
|
|
14
|
-
MemoryStore, MintConfig, config_path, create_folder, find_paths,
|
|
15
|
-
load_config, orchestrate_agent_loop,
|
|
16
|
-
orchestrate_chat_with_fallback, save_chat_images,
|
|
14
|
+
ImageGenRequest, MemoryStore, MintConfig, config_path, create_folder, find_paths,
|
|
15
|
+
generate_images, list_saved_pictures, load_config, orchestrate_agent_loop,
|
|
16
|
+
orchestrate_chat_stream_with_fallback, orchestrate_chat_with_fallback, save_chat_images,
|
|
17
|
+
save_config, weather,
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
const MAX_API_REQUEST_BYTES: usize = 32 * 1024 * 1024;
|
|
@@ -271,6 +272,45 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
271
272
|
.await;
|
|
272
273
|
}
|
|
273
274
|
}
|
|
275
|
+
("POST", "/api/interactions/agent-activity") => {
|
|
276
|
+
#[derive(Deserialize)]
|
|
277
|
+
#[serde(rename_all = "camelCase")]
|
|
278
|
+
struct SaveAgentActivityRequest {
|
|
279
|
+
interaction_id: i64,
|
|
280
|
+
activity: Vec<AgentProgress>,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if let Ok(payload) = serde_json::from_str::<SaveAgentActivityRequest>(body) {
|
|
284
|
+
if let Ok(memory) = MemoryStore::open_default() {
|
|
285
|
+
match serde_json::to_string(&payload.activity) {
|
|
286
|
+
Ok(activity_json) => {
|
|
287
|
+
if memory
|
|
288
|
+
.set_interaction_agent_activity_json(
|
|
289
|
+
payload.interaction_id,
|
|
290
|
+
&activity_json,
|
|
291
|
+
)
|
|
292
|
+
.is_ok()
|
|
293
|
+
{
|
|
294
|
+
send_json_response(
|
|
295
|
+
socket,
|
|
296
|
+
"200 OK",
|
|
297
|
+
"{\"status\":\"ok\"}",
|
|
298
|
+
)
|
|
299
|
+
.await;
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
Err(_) => {}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
send_json_response(
|
|
308
|
+
socket,
|
|
309
|
+
"400 Bad Request",
|
|
310
|
+
"{\"status\":\"invalid agent activity payload\"}",
|
|
311
|
+
)
|
|
312
|
+
.await;
|
|
313
|
+
}
|
|
274
314
|
("GET", "/api/pictures") => match list_saved_pictures() {
|
|
275
315
|
Ok(mut pictures) => {
|
|
276
316
|
for picture in &mut pictures {
|
|
@@ -663,6 +703,110 @@ pub async fn start_api_server(port: u16) -> Result<(), std::io::Error> {
|
|
|
663
703
|
)
|
|
664
704
|
.await;
|
|
665
705
|
}
|
|
706
|
+
("POST", "/api/image-generate") => {
|
|
707
|
+
#[derive(serde::Deserialize)]
|
|
708
|
+
#[serde(rename_all = "camelCase")]
|
|
709
|
+
struct ImageGenApiRequest {
|
|
710
|
+
prompt: String,
|
|
711
|
+
#[serde(default)]
|
|
712
|
+
negative_prompt: Option<String>,
|
|
713
|
+
#[serde(default)]
|
|
714
|
+
aspect_ratio: Option<String>,
|
|
715
|
+
#[serde(default)]
|
|
716
|
+
num_images: Option<u8>,
|
|
717
|
+
#[serde(default)]
|
|
718
|
+
model: Option<String>,
|
|
719
|
+
/// Which image provider to use (overrides config.image_gen_provider).
|
|
720
|
+
#[serde(default)]
|
|
721
|
+
provider: Option<String>,
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if let Ok(req) = serde_json::from_str::<ImageGenApiRequest>(body) {
|
|
725
|
+
let config = load_config().unwrap_or_default();
|
|
726
|
+
let gen_request = ImageGenRequest {
|
|
727
|
+
prompt: req.prompt.clone(),
|
|
728
|
+
negative_prompt: req.negative_prompt,
|
|
729
|
+
aspect_ratio: req.aspect_ratio,
|
|
730
|
+
num_images: req.num_images,
|
|
731
|
+
model: req.model,
|
|
732
|
+
provider: req.provider,
|
|
733
|
+
};
|
|
734
|
+
match generate_images(&config, &gen_request).await {
|
|
735
|
+
Ok(result) => {
|
|
736
|
+
let data_uris: Vec<String> = result
|
|
737
|
+
.images
|
|
738
|
+
.iter()
|
|
739
|
+
.map(|img| img.data_uri.clone())
|
|
740
|
+
.collect();
|
|
741
|
+
let mut saved = save_chat_images(
|
|
742
|
+
data_uris,
|
|
743
|
+
Some(result.provider.clone()),
|
|
744
|
+
Some(req.prompt.clone()),
|
|
745
|
+
)
|
|
746
|
+
.unwrap_or_default();
|
|
747
|
+
for picture in &mut saved {
|
|
748
|
+
picture.url =
|
|
749
|
+
Some(format!("/api/pictures/{}", picture.filename));
|
|
750
|
+
picture.thumbnail_url =
|
|
751
|
+
Some(format!("/api/pictures/{}", picture.filename));
|
|
752
|
+
}
|
|
753
|
+
let response = json!({
|
|
754
|
+
"images": saved,
|
|
755
|
+
"model": result.model,
|
|
756
|
+
"provider": result.provider,
|
|
757
|
+
"prompt": result.prompt,
|
|
758
|
+
"description": result.description
|
|
759
|
+
});
|
|
760
|
+
send_json_response(socket, "200 OK", &response.to_string()).await;
|
|
761
|
+
}
|
|
762
|
+
Err(e) => {
|
|
763
|
+
let err = json!({ "error": e.to_string() });
|
|
764
|
+
send_json_response(
|
|
765
|
+
socket,
|
|
766
|
+
"500 Internal Server Error",
|
|
767
|
+
&err.to_string(),
|
|
768
|
+
)
|
|
769
|
+
.await;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
} else {
|
|
773
|
+
send_json_response(
|
|
774
|
+
socket,
|
|
775
|
+
"400 Bad Request",
|
|
776
|
+
"{\"error\":\"invalid image generation request body\"}",
|
|
777
|
+
)
|
|
778
|
+
.await;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
("GET", "/api/image-gen/providers") => {
|
|
782
|
+
let config = load_config().unwrap_or_default();
|
|
783
|
+
let mut available: Vec<String> = Vec::new();
|
|
784
|
+
if !config.api_key.trim().is_empty() {
|
|
785
|
+
available.push("nanobanana".into());
|
|
786
|
+
}
|
|
787
|
+
if !config.openai_api_key.trim().is_empty() {
|
|
788
|
+
available.push("dalle".into());
|
|
789
|
+
}
|
|
790
|
+
if !config.stability_api_key.trim().is_empty() {
|
|
791
|
+
available.push("stability".into());
|
|
792
|
+
}
|
|
793
|
+
if !config.ideogram_api_key.trim().is_empty() {
|
|
794
|
+
available.push("ideogram".into());
|
|
795
|
+
}
|
|
796
|
+
if !config.replicate_api_key.trim().is_empty() {
|
|
797
|
+
available.push("replicate".into());
|
|
798
|
+
}
|
|
799
|
+
let active = if available.contains(&config.image_gen_provider) {
|
|
800
|
+
config.image_gen_provider.clone()
|
|
801
|
+
} else {
|
|
802
|
+
available
|
|
803
|
+
.first()
|
|
804
|
+
.cloned()
|
|
805
|
+
.unwrap_or_else(|| "nanobanana".into())
|
|
806
|
+
};
|
|
807
|
+
let response = json!({ "active": active, "available": available });
|
|
808
|
+
send_json_response(socket, "200 OK", &response.to_string()).await;
|
|
809
|
+
}
|
|
666
810
|
_ => {
|
|
667
811
|
send_json_response(socket, "404 Not Found", "{\"error\":\"Not Found\"}").await;
|
|
668
812
|
}
|
|
@@ -60,6 +60,16 @@ pub struct MintConfig {
|
|
|
60
60
|
pub local_model_name: String,
|
|
61
61
|
pub ollama_host: String,
|
|
62
62
|
pub ollama_model: String,
|
|
63
|
+
pub nanobanana_model: String,
|
|
64
|
+
/// Active image-generation provider: "nanobanana" | "dalle" | "stability" | "ideogram" | "replicate"
|
|
65
|
+
pub image_gen_provider: String,
|
|
66
|
+
pub dalle_model: String,
|
|
67
|
+
pub stability_api_key: String,
|
|
68
|
+
pub stability_model: String,
|
|
69
|
+
pub ideogram_api_key: String,
|
|
70
|
+
pub ideogram_model: String,
|
|
71
|
+
pub replicate_api_key: String,
|
|
72
|
+
pub replicate_model: String,
|
|
63
73
|
pub show_desktop_widget: bool,
|
|
64
74
|
pub safety_enabled: bool,
|
|
65
75
|
pub sandbox_mode: String,
|
|
@@ -89,7 +99,7 @@ impl Default for MintConfig {
|
|
|
89
99
|
];
|
|
90
100
|
Self {
|
|
91
101
|
theme: "dark".into(),
|
|
92
|
-
accent_color: "#
|
|
102
|
+
accent_color: "#10b981".into(),
|
|
93
103
|
language: "th-TH".into(),
|
|
94
104
|
assistant_mode: "chat".into(),
|
|
95
105
|
ai_provider: "gemini".into(),
|
|
@@ -109,6 +119,15 @@ impl Default for MintConfig {
|
|
|
109
119
|
local_model_name: "local-model".into(),
|
|
110
120
|
ollama_host: String::new(),
|
|
111
121
|
ollama_model: "llama3:latest".into(),
|
|
122
|
+
nanobanana_model: "gemini-2.5-flash-image".into(),
|
|
123
|
+
image_gen_provider: "nanobanana".into(),
|
|
124
|
+
dalle_model: "dall-e-3".into(),
|
|
125
|
+
stability_api_key: String::new(),
|
|
126
|
+
stability_model: "sd3.5-large".into(),
|
|
127
|
+
ideogram_api_key: String::new(),
|
|
128
|
+
ideogram_model: "V_3".into(),
|
|
129
|
+
replicate_api_key: String::new(),
|
|
130
|
+
replicate_model: "black-forest-labs/flux-1.1-pro".into(),
|
|
112
131
|
show_desktop_widget: true,
|
|
113
132
|
safety_enabled: true,
|
|
114
133
|
sandbox_mode: "prefer".into(),
|
|
@@ -291,7 +310,7 @@ fn runtime_extra_defaults() -> BTreeMap<String, Value> {
|
|
|
291
310
|
"notionPageId": "",
|
|
292
311
|
"notionTitleProperty": "Name",
|
|
293
312
|
"allowedShellModes": ["readOnly", "test", "mutating", "network"],
|
|
294
|
-
"allowedNativePlugins": ["dev_tools", "system_metrics"],
|
|
313
|
+
"allowedNativePlugins": ["dev_tools", "system_metrics", "github"],
|
|
295
314
|
"allowedMcpTools": {},
|
|
296
315
|
"mcpServers": {}
|
|
297
316
|
}))
|
|
@@ -361,7 +380,7 @@ mod tests {
|
|
|
361
380
|
);
|
|
362
381
|
assert_eq!(
|
|
363
382
|
config.extra["allowedNativePlugins"],
|
|
364
|
-
serde_json::json!(["dev_tools", "system_metrics"])
|
|
383
|
+
serde_json::json!(["dev_tools", "system_metrics", "github"])
|
|
365
384
|
);
|
|
366
385
|
assert_eq!(config.extra["allowedMcpTools"], serde_json::json!({}));
|
|
367
386
|
}
|