@pheem49/mint 1.7.0 → 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.
Files changed (55) hide show
  1. package/Cargo.lock +3 -3
  2. package/Cargo.toml +1 -1
  3. package/README.md +9 -0
  4. package/crates/mint-cli/src/agent.rs +1 -1
  5. package/crates/mint-cli/src/main.rs +47 -24
  6. package/crates/mint-cli/src/setup.rs +1 -1
  7. package/crates/mint-core/src/api_server.rs +64 -10
  8. package/crates/mint-core/src/chat.rs +79 -0
  9. package/crates/mint-core/src/lib.rs +1 -1
  10. package/crates/mint-core/src/plugins.rs +4 -1
  11. package/crates/mint-core/src/workflows.rs +8 -0
  12. package/package.json +13 -2
  13. package/src/renderer/src/App.tsx +1 -1
  14. package/src/renderer/src/components/ChatPanel.tsx +27 -14
  15. package/src/renderer/src/components/DashboardSidebar.tsx +71 -12
  16. package/src/renderer/src/components/ImageStudioPanel.tsx +7 -3
  17. package/src/renderer/src/components/MintDashboard.tsx +199 -5
  18. package/src/renderer/src/components/PicturesLibrary.tsx +1 -3
  19. package/src/renderer/src/components/Settings/AudioTab.tsx +97 -0
  20. package/src/renderer/src/components/Settings/AutomationTab.tsx +195 -0
  21. package/src/renderer/src/components/Settings/GeneralTab.tsx +950 -0
  22. package/src/renderer/src/components/Settings/MemoryTab.tsx +60 -0
  23. package/src/renderer/src/components/Settings/PluginsTab.tsx +452 -0
  24. package/src/renderer/src/components/Settings/ShortcutsTab.tsx +30 -0
  25. package/src/renderer/src/components/Settings/ThemeTab.tsx +129 -0
  26. package/src/renderer/src/components/SettingsWindow.tsx +175 -962
  27. package/src/renderer/src/components/WorkflowBuilderPanel.tsx +536 -0
  28. package/src/renderer/src/components/WorkflowsWindow.tsx +46 -0
  29. package/src/renderer/src/css/image-studio.css +9 -4
  30. package/src/renderer/src/css/settings.css +75 -6
  31. package/src/renderer/src/css/styles.css +355 -38
  32. package/src/renderer/src/css/workflows.css +542 -0
  33. package/src/renderer/src/env.d.ts +2 -0
  34. package/src/renderer/src/index.css +1 -0
  35. package/src/renderer/src/tauri.ts +97 -0
  36. package/src/renderer/src-web/components/ChatPanel.tsx +27 -14
  37. package/src/renderer/src-web/components/DashboardSidebar.tsx +17 -1
  38. package/src/renderer/src-web/components/ImageStudioPanel.tsx +7 -3
  39. package/src/renderer/src-web/components/MintDashboard.tsx +189 -0
  40. package/src/renderer/src-web/components/PicturesLibrary.tsx +1 -3
  41. package/src/renderer/src-web/components/Settings/AudioTab.tsx +97 -0
  42. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +196 -0
  43. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +948 -0
  44. package/src/renderer/src-web/components/Settings/MemoryTab.tsx +60 -0
  45. package/src/renderer/src-web/components/Settings/PluginsTab.tsx +452 -0
  46. package/src/renderer/src-web/components/Settings/ShortcutsTab.tsx +30 -0
  47. package/src/renderer/src-web/components/Settings/ThemeTab.tsx +129 -0
  48. package/src/renderer/src-web/components/SettingsWindow.tsx +187 -968
  49. package/src/renderer/src-web/css/image-studio.css +9 -4
  50. package/src/renderer/src-web/css/settings.css +75 -6
  51. package/src/renderer/src-web/css/styles.css +294 -38
  52. package/src/renderer/src-web/env.d.ts +2 -0
  53. package/src/renderer/src-web/tauri.ts +36 -0
  54. package/src-tauri/src/lib.rs +84 -92
  55. package/src-tauri/tauri.conf.json +1 -1
@@ -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, extract_document_text, google_tts_urls, list_saved_pictures, load_config,
38
- load_workflows, orchestrate_agent_loop, orchestrate_chat_stream_with_fallback,
39
- orchestrate_chat_with_fallback, propose_code_edits, save_chat_images, save_config,
40
- start_channels, weather, workflows_path,
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 = request_with_document_context(&config, &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 = request_with_document_context(&config, &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();
@@ -552,8 +469,7 @@ fn save_interaction_agent_activity(
552
469
  interaction_id: i64,
553
470
  activity: Vec<AgentProgress>,
554
471
  ) -> Result<(), String> {
555
- let activity_json =
556
- serde_json::to_string(&activity).map_err(|error| error.to_string())?;
472
+ let activity_json = serde_json::to_string(&activity).map_err(|error| error.to_string())?;
557
473
  MemoryStore::open_default()
558
474
  .and_then(|memory| {
559
475
  memory
@@ -563,6 +479,20 @@ fn save_interaction_agent_activity(
563
479
  .map_err(|error| error.to_string())
564
480
  }
565
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
+
566
496
  #[tauri::command]
567
497
  fn get_recent_interactions(
568
498
  limit: Option<usize>,
@@ -615,6 +545,54 @@ fn set_profile_value(key: String, value: String) -> Result<(), String> {
615
545
  .map_err(|error| error.to_string())
616
546
  }
617
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
+
618
596
  #[tauri::command]
619
597
  fn clear_chat_history(chat_id: Option<String>) -> Result<usize, String> {
620
598
  MemoryStore::open_default()
@@ -930,6 +908,15 @@ fn reload_custom_workflows() -> Result<Value, String> {
930
908
  }))
931
909
  }
932
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
+
933
920
  fn install_tray(app: &AppHandle) -> tauri::Result<()> {
934
921
  let show = MenuItem::with_id(app, "show", "Show Mint", true, None::<&str>)?;
935
922
  let settings = MenuItem::with_id(app, "settings", "Settings", true, None::<&str>)?;
@@ -1055,6 +1042,9 @@ pub fn run() {
1055
1042
  get_profile_value,
1056
1043
  set_profile_value,
1057
1044
  clear_chat_history,
1045
+ list_learned_skills,
1046
+ add_learned_skill,
1047
+ delete_learned_skill,
1058
1048
  list_pictures,
1059
1049
  save_pictures,
1060
1050
  open_folder,
@@ -1086,7 +1076,9 @@ pub fn run() {
1086
1076
  run_next_queued_task,
1087
1077
  exit_app,
1088
1078
  open_workflows_file,
1089
- reload_custom_workflows
1079
+ reload_custom_workflows,
1080
+ save_custom_workflows,
1081
+ save_system_interaction
1090
1082
  ])
1091
1083
  .run(tauri::generate_context!())
1092
1084
  .expect("error while running Mint desktop");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://schema.tauri.app/config/2",
3
3
  "productName": "Mint",
4
- "version": "1.7.0",
4
+ "version": "1.8.0",
5
5
  "identifier": "com.pheem49.mint",
6
6
  "build": {
7
7
  "beforeDevCommand": "npm run dev:desktop",