@pheem49/mint 1.7.1 → 1.8.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 +9 -0
- package/crates/mint-core/src/api_server.rs +62 -4
- package/crates/mint-core/src/chat.rs +79 -0
- package/crates/mint-core/src/lib.rs +1 -1
- package/crates/mint-core/src/workflows.rs +8 -0
- package/package.json +2 -1
- package/src/renderer/src/App.tsx +1 -1
- package/src/renderer/src/components/ChatPanel.tsx +27 -14
- package/src/renderer/src/components/DashboardSidebar.tsx +71 -12
- package/src/renderer/src/components/ImageStudioPanel.tsx +7 -3
- package/src/renderer/src/components/MintDashboard.tsx +199 -5
- 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 +452 -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 -962
- package/src/renderer/src/components/WorkflowBuilderPanel.tsx +536 -0
- package/src/renderer/src/components/WorkflowsWindow.tsx +46 -0
- package/src/renderer/src/css/image-studio.css +9 -4
- package/src/renderer/src/css/settings.css +75 -6
- package/src/renderer/src/css/styles.css +355 -38
- package/src/renderer/src/css/workflows.css +542 -0
- package/src/renderer/src/env.d.ts +2 -0
- package/src/renderer/src/index.css +1 -0
- package/src/renderer/src/tauri.ts +97 -0
- package/src/renderer/src-web/components/ChatPanel.tsx +27 -14
- package/src/renderer/src-web/components/DashboardSidebar.tsx +17 -1
- package/src/renderer/src-web/components/ImageStudioPanel.tsx +7 -3
- package/src/renderer/src-web/components/MintDashboard.tsx +189 -0
- 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 +452 -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 -968
- package/src/renderer/src-web/css/image-studio.css +9 -4
- package/src/renderer/src-web/css/settings.css +75 -6
- package/src/renderer/src-web/css/styles.css +294 -38
- package/src/renderer/src-web/env.d.ts +2 -0
- package/src/renderer/src-web/tauri.ts +36 -0
- package/src-tauri/src/lib.rs +83 -90
- package/src-tauri/tauri.conf.json +1 -1
package/src-tauri/src/lib.rs
CHANGED
|
@@ -11,7 +11,6 @@ mod updater;
|
|
|
11
11
|
mod webhooks;
|
|
12
12
|
mod workflows;
|
|
13
13
|
|
|
14
|
-
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD};
|
|
15
14
|
use browser::{
|
|
16
15
|
BrowserTab, click as browser_click, list_tabs as browser_list_tabs,
|
|
17
16
|
navigate as browser_navigate, read_page_text,
|
|
@@ -34,10 +33,10 @@ use mint_core::{
|
|
|
34
33
|
AgentApproval, AgentProgress, AppliedCodeEdit, ApprovalOutcome, ChatRequest, ChatResponse,
|
|
35
34
|
ChatSession, CodeEdit, CodeEditProposal, ImageGenRequest, InteractionMemory, MemoryStore,
|
|
36
35
|
MintConfig, PictureEntry, TtsUrl, WeatherReport, apply_code_edits, classify_shell_command,
|
|
37
|
-
config_path,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
config_path, google_tts_urls, list_saved_pictures, load_config, load_workflows,
|
|
37
|
+
orchestrate_agent_loop, orchestrate_chat_stream_with_fallback, orchestrate_chat_with_fallback,
|
|
38
|
+
propose_code_edits, save_chat_images, save_config, save_workflows, start_channels, weather,
|
|
39
|
+
workflows_path,
|
|
41
40
|
};
|
|
42
41
|
use plugins::execute_plugin;
|
|
43
42
|
|
|
@@ -94,9 +93,6 @@ enum DesktopStreamEvent {
|
|
|
94
93
|
Chunk { chunk: String },
|
|
95
94
|
Progress { progress: AgentProgress },
|
|
96
95
|
}
|
|
97
|
-
|
|
98
|
-
const MAX_DOCUMENT_BYTES: usize = 10 * 1024 * 1024;
|
|
99
|
-
const MAX_DOCUMENT_CONTEXT_CHARS: usize = 30_000;
|
|
100
96
|
const WORKSPACE_TREE_MAX_DEPTH: usize = 9;
|
|
101
97
|
const WORKSPACE_TREE_MAX_CHILDREN: usize = 400;
|
|
102
98
|
const WORKSPACE_TREE_COLLAPSED_DIRS: &[&str] = &[
|
|
@@ -114,85 +110,6 @@ const WORKSPACE_TREE_COLLAPSED_DIRS: &[&str] = &[
|
|
|
114
110
|
"target",
|
|
115
111
|
];
|
|
116
112
|
|
|
117
|
-
fn request_with_document_context(
|
|
118
|
-
config: &MintConfig,
|
|
119
|
-
request: &ChatRequest,
|
|
120
|
-
) -> Result<ChatRequest, String> {
|
|
121
|
-
let Some(document) = &request.document_attachment else {
|
|
122
|
-
return Ok(request.clone());
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
let (mime_type, encoded) = document
|
|
126
|
-
.data_uri
|
|
127
|
-
.strip_prefix("data:")
|
|
128
|
-
.and_then(|payload| payload.split_once(";base64,"))
|
|
129
|
-
.ok_or_else(|| "invalid PDF attachment data URI".to_string())?;
|
|
130
|
-
|
|
131
|
-
if !mime_type.eq_ignore_ascii_case("application/pdf") {
|
|
132
|
-
return Err("only PDF document attachments are supported".into());
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
let bytes = BASE64_STANDARD
|
|
136
|
-
.decode(encoded)
|
|
137
|
-
.map_err(|error| format!("invalid PDF attachment encoding: {error}"))?;
|
|
138
|
-
if bytes.len() > MAX_DOCUMENT_BYTES {
|
|
139
|
-
return Err("PDF attachment is too large (> 10 MiB)".into());
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
let directory = config_path()
|
|
143
|
-
.map_err(|error| error.to_string())?
|
|
144
|
-
.with_file_name("Attachments");
|
|
145
|
-
fs::create_dir_all(&directory).map_err(|error| error.to_string())?;
|
|
146
|
-
let path = directory.join(format!(
|
|
147
|
-
"mint-pdf-{}-{}.pdf",
|
|
148
|
-
COUNTER.fetch_add(1, Ordering::SeqCst),
|
|
149
|
-
sanitize_attachment_name(&document.filename)
|
|
150
|
-
));
|
|
151
|
-
fs::write(&path, bytes).map_err(|error| error.to_string())?;
|
|
152
|
-
|
|
153
|
-
let extracted = extract_document_text(&path, config).map_err(|error| error.to_string());
|
|
154
|
-
let _ = fs::remove_file(&path);
|
|
155
|
-
let extracted = extracted?;
|
|
156
|
-
let context = truncate_document_context(&extracted);
|
|
157
|
-
let filename = document.filename.trim();
|
|
158
|
-
let filename = if filename.is_empty() {
|
|
159
|
-
"attached.pdf"
|
|
160
|
-
} else {
|
|
161
|
-
filename
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
let mut enriched = request.clone();
|
|
165
|
-
enriched.document_attachment = None;
|
|
166
|
-
enriched.message = format!(
|
|
167
|
-
"{}\n\nAttached PDF: {filename}\nUse the extracted PDF text below when answering. If the user asks for a summary, summarize this document.\n\n--- Extracted PDF text ---\n{context}\n--- End extracted PDF text ---",
|
|
168
|
-
request.message
|
|
169
|
-
);
|
|
170
|
-
Ok(enriched)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
fn truncate_document_context(text: &str) -> String {
|
|
174
|
-
let mut output: String = text.chars().take(MAX_DOCUMENT_CONTEXT_CHARS).collect();
|
|
175
|
-
if text.chars().count() > MAX_DOCUMENT_CONTEXT_CHARS {
|
|
176
|
-
output.push_str("\n\n[PDF text truncated because it is long.]");
|
|
177
|
-
}
|
|
178
|
-
output
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
fn sanitize_attachment_name(filename: &str) -> String {
|
|
182
|
-
let sanitized: String = filename
|
|
183
|
-
.chars()
|
|
184
|
-
.filter(|character| {
|
|
185
|
-
character.is_ascii_alphanumeric() || matches!(character, '.' | '-' | '_')
|
|
186
|
-
})
|
|
187
|
-
.take(80)
|
|
188
|
-
.collect();
|
|
189
|
-
if sanitized.is_empty() {
|
|
190
|
-
"document".into()
|
|
191
|
-
} else {
|
|
192
|
-
sanitized
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
113
|
#[tauri::command]
|
|
197
114
|
fn get_runtime_status() -> Result<RuntimeStatus, String> {
|
|
198
115
|
let config = load_config().map_err(|error| error.to_string())?;
|
|
@@ -376,7 +293,7 @@ fn inspect_shell_command(command: String) -> mint_core::ShellClassification {
|
|
|
376
293
|
#[tauri::command]
|
|
377
294
|
async fn send_chat_message(app: AppHandle, request: ChatRequest) -> Result<ChatResponse, String> {
|
|
378
295
|
let config = load_config().map_err(|error| error.to_string())?;
|
|
379
|
-
let request =
|
|
296
|
+
let request = request.with_document_context(&config)?;
|
|
380
297
|
|
|
381
298
|
if request.message.starts_with("/chat ") {
|
|
382
299
|
let mut clean_request = request.clone();
|
|
@@ -456,7 +373,7 @@ async fn stream_chat_message(
|
|
|
456
373
|
on_event: Channel<DesktopStreamEvent>,
|
|
457
374
|
) -> Result<ChatResponse, String> {
|
|
458
375
|
let config = load_config().map_err(|error| error.to_string())?;
|
|
459
|
-
let request =
|
|
376
|
+
let request = request.with_document_context(&config)?;
|
|
460
377
|
|
|
461
378
|
if request.message.starts_with("/chat ") {
|
|
462
379
|
let mut clean_request = request.clone();
|
|
@@ -562,6 +479,20 @@ fn save_interaction_agent_activity(
|
|
|
562
479
|
.map_err(|error| error.to_string())
|
|
563
480
|
}
|
|
564
481
|
|
|
482
|
+
#[tauri::command]
|
|
483
|
+
fn save_system_interaction(
|
|
484
|
+
chat_id: String,
|
|
485
|
+
user_text: String,
|
|
486
|
+
provider: String,
|
|
487
|
+
model: String,
|
|
488
|
+
) -> Result<i64, String> {
|
|
489
|
+
MemoryStore::open_default()
|
|
490
|
+
.and_then(|memory| {
|
|
491
|
+
memory.add_interaction_for_chat(&chat_id, &user_text, "", &provider, &model)
|
|
492
|
+
})
|
|
493
|
+
.map_err(|error| error.to_string())
|
|
494
|
+
}
|
|
495
|
+
|
|
565
496
|
#[tauri::command]
|
|
566
497
|
fn get_recent_interactions(
|
|
567
498
|
limit: Option<usize>,
|
|
@@ -614,6 +545,54 @@ fn set_profile_value(key: String, value: String) -> Result<(), String> {
|
|
|
614
545
|
.map_err(|error| error.to_string())
|
|
615
546
|
}
|
|
616
547
|
|
|
548
|
+
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
|
549
|
+
#[serde(rename_all = "camelCase")]
|
|
550
|
+
struct LearnedSkillDto {
|
|
551
|
+
id: i64,
|
|
552
|
+
name: String,
|
|
553
|
+
source_path: String,
|
|
554
|
+
content: String,
|
|
555
|
+
updated_at: String,
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
#[tauri::command]
|
|
559
|
+
fn list_learned_skills() -> Result<Vec<LearnedSkillDto>, String> {
|
|
560
|
+
let store = MemoryStore::open_default().map_err(|e| e.to_string())?;
|
|
561
|
+
let skills = store.learned_skills(100).map_err(|e| e.to_string())?;
|
|
562
|
+
let dtos = skills
|
|
563
|
+
.into_iter()
|
|
564
|
+
.map(|s| LearnedSkillDto {
|
|
565
|
+
id: s.id,
|
|
566
|
+
name: s.name,
|
|
567
|
+
source_path: s.source_path,
|
|
568
|
+
content: s.content,
|
|
569
|
+
updated_at: s.created_at,
|
|
570
|
+
})
|
|
571
|
+
.collect();
|
|
572
|
+
Ok(dtos)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
#[tauri::command]
|
|
576
|
+
fn add_learned_skill(name: String, content: String) -> Result<LearnedSkillDto, String> {
|
|
577
|
+
let store = MemoryStore::open_default().map_err(|e| e.to_string())?;
|
|
578
|
+
let skill = store
|
|
579
|
+
.add_learned_skill(&name, "ui_manual", &content)
|
|
580
|
+
.map_err(|e| e.to_string())?;
|
|
581
|
+
Ok(LearnedSkillDto {
|
|
582
|
+
id: skill.id,
|
|
583
|
+
name: skill.name,
|
|
584
|
+
source_path: skill.source_path,
|
|
585
|
+
content: skill.content,
|
|
586
|
+
updated_at: skill.created_at,
|
|
587
|
+
})
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
#[tauri::command]
|
|
591
|
+
fn delete_learned_skill(name: String) -> Result<usize, String> {
|
|
592
|
+
let store = MemoryStore::open_default().map_err(|e| e.to_string())?;
|
|
593
|
+
store.delete_learned_skill(&name).map_err(|e| e.to_string())
|
|
594
|
+
}
|
|
595
|
+
|
|
617
596
|
#[tauri::command]
|
|
618
597
|
fn clear_chat_history(chat_id: Option<String>) -> Result<usize, String> {
|
|
619
598
|
MemoryStore::open_default()
|
|
@@ -929,6 +908,15 @@ fn reload_custom_workflows() -> Result<Value, String> {
|
|
|
929
908
|
}))
|
|
930
909
|
}
|
|
931
910
|
|
|
911
|
+
#[tauri::command]
|
|
912
|
+
fn save_custom_workflows(workflows: Vec<serde_json::Value>) -> Result<ActionResult, String> {
|
|
913
|
+
save_workflows(&workflows).map_err(|error| error.to_string())?;
|
|
914
|
+
Ok(ActionResult {
|
|
915
|
+
success: true,
|
|
916
|
+
message: "workflows saved successfully".into(),
|
|
917
|
+
})
|
|
918
|
+
}
|
|
919
|
+
|
|
932
920
|
fn install_tray(app: &AppHandle) -> tauri::Result<()> {
|
|
933
921
|
let show = MenuItem::with_id(app, "show", "Show Mint", true, None::<&str>)?;
|
|
934
922
|
let settings = MenuItem::with_id(app, "settings", "Settings", true, None::<&str>)?;
|
|
@@ -1054,6 +1042,9 @@ pub fn run() {
|
|
|
1054
1042
|
get_profile_value,
|
|
1055
1043
|
set_profile_value,
|
|
1056
1044
|
clear_chat_history,
|
|
1045
|
+
list_learned_skills,
|
|
1046
|
+
add_learned_skill,
|
|
1047
|
+
delete_learned_skill,
|
|
1057
1048
|
list_pictures,
|
|
1058
1049
|
save_pictures,
|
|
1059
1050
|
open_folder,
|
|
@@ -1085,7 +1076,9 @@ pub fn run() {
|
|
|
1085
1076
|
run_next_queued_task,
|
|
1086
1077
|
exit_app,
|
|
1087
1078
|
open_workflows_file,
|
|
1088
|
-
reload_custom_workflows
|
|
1079
|
+
reload_custom_workflows,
|
|
1080
|
+
save_custom_workflows,
|
|
1081
|
+
save_system_interaction
|
|
1089
1082
|
])
|
|
1090
1083
|
.run(tauri::generate_context!())
|
|
1091
1084
|
.expect("error while running Mint desktop");
|