@pheem49/mint 1.8.2 → 1.10.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 (106) hide show
  1. package/Cargo.lock +55 -3
  2. package/Cargo.toml +2 -1
  3. package/Release_Note.md +297 -107
  4. package/bin/mint +0 -0
  5. package/crates/mint-cli/Cargo.toml +1 -0
  6. package/crates/mint-cli/src/actions.rs +233 -0
  7. package/crates/mint-cli/src/agent.rs +382 -127
  8. package/crates/mint-cli/src/gmail.rs +22 -2
  9. package/crates/mint-cli/src/image.rs +8 -8
  10. package/crates/mint-cli/src/interactive.rs +2888 -0
  11. package/crates/mint-cli/src/main.rs +391 -2506
  12. package/crates/mint-cli/src/markdown.rs +294 -0
  13. package/crates/mint-cli/src/onboard.rs +152 -109
  14. package/crates/mint-cli/src/setup.rs +143 -143
  15. package/crates/mint-cli/src/updater.rs +48 -6
  16. package/crates/mint-core/Cargo.toml +1 -0
  17. package/crates/mint-core/src/agent_loop.rs +1 -0
  18. package/crates/mint-core/src/api_server.rs +431 -65
  19. package/crates/mint-core/src/browser.rs +630 -102
  20. package/crates/mint-core/src/channels.rs +105 -6
  21. package/crates/mint-core/src/chat.rs +174 -11
  22. package/crates/mint-core/src/code_tools.rs +47 -31
  23. package/crates/mint-core/src/config.rs +302 -17
  24. package/crates/mint-core/src/image_gen.rs +299 -38
  25. package/crates/mint-core/src/lib.rs +10 -7
  26. package/crates/mint-core/src/mcp.rs +119 -6
  27. package/crates/mint-core/src/memory.rs +6 -1
  28. package/crates/mint-core/src/orchestration.rs +237 -96
  29. package/crates/mint-core/src/pictures.rs +67 -5
  30. package/crates/mint-core/src/semantic.rs +1 -1
  31. package/crates/mint-core/src/shell.rs +1 -1
  32. package/crates/mint-core/src/skills.rs +128 -25
  33. package/crates/mint-core/src/video_gen.rs +235 -0
  34. package/package.json +8 -2
  35. package/shared-modules/linux-audio/fluidsynth2-static.json +9 -3
  36. package/shared-modules/linux-audio/fluidsynth2.json +8 -2
  37. package/shared-modules/linux-audio/lilv.json +26 -19
  38. package/shared-modules/linux-audio/lv2.json +0 -1
  39. package/shared-modules/luajit/luajit.json +2 -2
  40. package/skills-lock.json +41 -0
  41. package/src/renderer/index-web.html +1 -1
  42. package/src/renderer/index.html +1 -1
  43. package/src/renderer/shared/components/AgentActivityTable.tsx +25 -6
  44. package/src/renderer/shared/constants/slashCommands.ts +27 -0
  45. package/src/renderer/shared/platform.ts +3 -0
  46. package/src/renderer/shared/types.ts +4 -0
  47. package/src/renderer/shared/utils/approval.tsx +86 -7
  48. package/src/renderer/shared/utils/fileIcons.ts +135 -0
  49. package/src/renderer/shared/utils/markdown.tsx +217 -88
  50. package/src/renderer/shared/utils/slashCommandProcessor.ts +239 -0
  51. package/src/renderer/src/components/ChatPanel.tsx +486 -69
  52. package/src/renderer/src/components/DashboardSidebar.tsx +11 -2
  53. package/src/renderer/src/components/ImageStudioPanel.tsx +87 -4
  54. package/src/renderer/src/components/MintDashboard.tsx +195 -7
  55. package/src/renderer/src/components/PicturesLibrary.tsx +156 -15
  56. package/src/renderer/src/components/Settings/AgentsTab.tsx +21 -2
  57. package/src/renderer/src/components/Settings/AutomationTab.tsx +316 -18
  58. package/src/renderer/src/components/Settings/GeneralTab.tsx +795 -413
  59. package/src/renderer/src/components/Settings/PluginsTab.tsx +304 -70
  60. package/src/renderer/src/components/Settings/ThemeTab.tsx +6 -1
  61. package/src/renderer/src/components/SettingsWindow.tsx +106 -3
  62. package/src/renderer/src/components/VeoStudioPanel.tsx +549 -0
  63. package/src/renderer/src/components/WorkspacePanel.tsx +7 -125
  64. package/src/renderer/src/css/image-studio.css +106 -0
  65. package/src/renderer/src/css/settings.css +158 -0
  66. package/src/renderer/src/css/styles.css +368 -1
  67. package/src/renderer/src/css/veo-studio.css +626 -0
  68. package/src/renderer/src/index.css +1 -0
  69. package/src/renderer/src/tauri.ts +144 -17
  70. package/src/renderer/src-web/components/ChatPanel.tsx +385 -93
  71. package/src/renderer/src-web/components/DashboardSidebar.tsx +16 -1
  72. package/src/renderer/src-web/components/ImageStudioPanel.tsx +87 -4
  73. package/src/renderer/src-web/components/MintDashboard.tsx +156 -5
  74. package/src/renderer/src-web/components/PicturesLibrary.tsx +155 -15
  75. package/src/renderer/src-web/components/Settings/AgentsTab.tsx +21 -2
  76. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +316 -18
  77. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +795 -411
  78. package/src/renderer/src-web/components/Settings/PluginsTab.tsx +304 -70
  79. package/src/renderer/src-web/components/Settings/ThemeTab.tsx +6 -1
  80. package/src/renderer/src-web/components/SettingsWindow.tsx +110 -3
  81. package/src/renderer/src-web/components/VeoStudioPanel.tsx +549 -0
  82. package/src/renderer/src-web/css/image-studio.css +106 -0
  83. package/src/renderer/src-web/css/settings.css +158 -0
  84. package/src/renderer/src-web/css/styles.css +369 -1
  85. package/src/renderer/src-web/css/veo-studio.css +650 -0
  86. package/src/renderer/src-web/index.css +1 -0
  87. package/src/renderer/src-web/tauri.ts +135 -16
  88. package/src-tauri/src/desktop.rs +18 -16
  89. package/src-tauri/src/discord_rpc.rs +1 -1
  90. package/src-tauri/src/lib.rs +155 -25
  91. package/src-tauri/src/webhooks.rs +45 -31
  92. package/src-tauri/src/workflows.rs +1 -0
  93. package/src-tauri/tauri.conf.json +1 -1
  94. package/temp_gridgeist/AGENTS.md +8 -0
  95. package/temp_gridgeist/CHANGELOG.md +130 -0
  96. package/temp_gridgeist/LICENSE +21 -0
  97. package/temp_gridgeist/README.md +323 -0
  98. package/temp_gridgeist/README.th.md +325 -0
  99. package/temp_gridgeist/skills/gridgeist/SKILL.md +88 -0
  100. package/temp_gridgeist/skills/gridgeist/agents/openai.yaml +9 -0
  101. package/temp_gridgeist/skills/gridgeist/assets/gridgeist-small.svg +10 -0
  102. package/temp_gridgeist/skills/gridgeist/assets/gridgeist.png +0 -0
  103. package/temp_gridgeist/skills/gridgeist/assets/gridgeist.svg +18 -0
  104. package/temp_gridgeist/skills/gridgeist/references/design-language.md +88 -0
  105. package/temp_gridgeist/skills/gridgeist/references/review-checklist.md +128 -0
  106. package/temp_gridgeist/skills/gridgeist/references/system-contract.md +109 -0
@@ -10,8 +10,8 @@ use crate::MintConfig;
10
10
  // Public request / response types
11
11
  // ─────────────────────────────────────────────────────────────────────────────
12
12
 
13
- /// A request to generate images from any supported provider.
14
- #[derive(Debug, Clone, Deserialize)]
13
+ /// A request to generate or edit images from any supported provider.
14
+ #[derive(Debug, Clone, Deserialize, Default)]
15
15
  #[serde(rename_all = "camelCase")]
16
16
  pub struct ImageGenRequest {
17
17
  pub prompt: String,
@@ -30,6 +30,15 @@ pub struct ImageGenRequest {
30
30
  /// Falls back to `config.image_gen_provider` when omitted.
31
31
  #[serde(default)]
32
32
  pub provider: Option<String>,
33
+ /// Optional input image data URI (`data:image/png;base64,...`) for Image-to-Image / Inpainting
34
+ #[serde(default)]
35
+ pub image_data_uri: Option<String>,
36
+ /// Optional mask image data URI for Inpainting
37
+ #[serde(default)]
38
+ pub mask_data_uri: Option<String>,
39
+ /// Optional mode: "generate" | "edit" | "inpaint". Inferred if omitted.
40
+ #[serde(default)]
41
+ pub mode: Option<String>,
33
42
  }
34
43
 
35
44
  /// One generated image returned from the model.
@@ -76,6 +85,10 @@ pub enum ImageGenError {
76
85
  // ── Generic ──────────────────────────────────────────────────────────────
77
86
  #[error("unsupported image generation provider: {0}")]
78
87
  UnsupportedProvider(String),
88
+ #[error(
89
+ "image-to-image editing is not supported by {0} — please use Replicate, Stability AI, or DALL·E"
90
+ )]
91
+ UnsupportedMode(String),
79
92
  #[error("API request failed: {0}")]
80
93
  Request(#[from] reqwest::Error),
81
94
  #[error("the model returned no images for this prompt")]
@@ -129,6 +142,15 @@ async fn call_nanobanana(
129
142
  config: &MintConfig,
130
143
  request: &ImageGenRequest,
131
144
  ) -> Result<ImageGenResponse, ImageGenError> {
145
+ if request.image_data_uri.is_some()
146
+ || request.mode.as_deref() == Some("edit")
147
+ || request.mode.as_deref() == Some("inpaint")
148
+ {
149
+ return Err(ImageGenError::UnsupportedMode(
150
+ "NanoBanana (Gemini Text-to-Image)".to_owned(),
151
+ ));
152
+ }
153
+
132
154
  let api_key = if config.api_key.trim().is_empty() {
133
155
  std::env::var("GEMINI_API_KEY").unwrap_or_default()
134
156
  } else {
@@ -270,6 +292,105 @@ async fn call_dalle(
270
292
  return Err(ImageGenError::MissingOpenAiKey);
271
293
  }
272
294
 
295
+ if let Some(ref img_data_uri) = request.image_data_uri {
296
+ let (mime_type, img_bytes) = parse_data_uri(img_data_uri)
297
+ .ok_or_else(|| ImageGenError::ModelError("invalid base64 image data URI".to_owned()))?;
298
+
299
+ let size = aspect_ratio_to_dalle_size(request.aspect_ratio.as_deref());
300
+ let boundary = format!("MintBoundary{}", uuid_v4_hex());
301
+
302
+ let mut text_fields = vec![
303
+ ("prompt".to_owned(), request.prompt.clone()),
304
+ ("n".to_owned(), "1".to_owned()),
305
+ ("size".to_owned(), size.to_owned()),
306
+ ("response_format".to_owned(), "b64_json".to_owned()),
307
+ ("model".to_owned(), "dall-e-2".to_owned()),
308
+ ];
309
+ if let Some(ref neg) = request.negative_prompt {
310
+ if !neg.trim().is_empty() {
311
+ text_fields.push((
312
+ "prompt".to_owned(),
313
+ format!("{} (avoid: {})", request.prompt, neg),
314
+ ));
315
+ }
316
+ }
317
+
318
+ let mut file_fields = vec![(
319
+ "image",
320
+ "image.png",
321
+ mime_type.as_str(),
322
+ img_bytes.as_slice(),
323
+ )];
324
+
325
+ let mask_bytes_storage;
326
+ if let Some(ref mask_data_uri) = request.mask_data_uri {
327
+ if let Some((mask_mime, mask_bytes)) = parse_data_uri(mask_data_uri) {
328
+ mask_bytes_storage = (mask_mime, mask_bytes);
329
+ file_fields.push((
330
+ "mask",
331
+ "mask.png",
332
+ mask_bytes_storage.0.as_str(),
333
+ mask_bytes_storage.1.as_slice(),
334
+ ));
335
+ }
336
+ }
337
+
338
+ let body = build_multipart_binary(&boundary, &text_fields, &file_fields);
339
+
340
+ let response: Value = client
341
+ .post("https://api.openai.com/v1/images/edits")
342
+ .bearer_auth(&api_key)
343
+ .header(
344
+ "content-type",
345
+ format!("multipart/form-data; boundary={boundary}"),
346
+ )
347
+ .body(body)
348
+ .send()
349
+ .await?
350
+ .error_for_status()
351
+ .map_err(ImageGenError::Request)?
352
+ .json()
353
+ .await?;
354
+
355
+ if let Some(err) = response.get("error") {
356
+ let msg = err["message"]
357
+ .as_str()
358
+ .unwrap_or("unknown OpenAI API error");
359
+ return Err(ImageGenError::ModelError(msg.to_owned()));
360
+ }
361
+
362
+ let data = response["data"]
363
+ .as_array()
364
+ .ok_or(ImageGenError::UnexpectedResponse)?;
365
+
366
+ let images: Vec<GeneratedImage> = data
367
+ .iter()
368
+ .filter_map(|item| {
369
+ let b64 = item["b64_json"].as_str()?;
370
+ if b64.is_empty() || STANDARD.decode(b64).is_err() {
371
+ return None;
372
+ }
373
+ let data_uri = format!("data:image/png;base64,{b64}");
374
+ Some(GeneratedImage {
375
+ data_uri,
376
+ mime_type: "image/png".to_owned(),
377
+ })
378
+ })
379
+ .collect();
380
+
381
+ if images.is_empty() {
382
+ return Err(ImageGenError::NoImagesReturned);
383
+ }
384
+
385
+ return Ok(ImageGenResponse {
386
+ images,
387
+ model: "dall-e-2".to_owned(),
388
+ provider: "dalle".to_owned(),
389
+ prompt: request.prompt.clone(),
390
+ description: None,
391
+ });
392
+ }
393
+
273
394
  let model = request
274
395
  .model
275
396
  .as_deref()
@@ -393,42 +514,98 @@ async fn call_stability(
393
514
  }
394
515
  });
395
516
 
396
- // Route: Stable Image Core uses /core; SD3.x models use /sd3
397
- let (endpoint, model_param) = if model == "core" {
517
+ let boundary = format!("MintBoundary{}", uuid_v4_hex());
518
+
519
+ let (endpoint, raw_body) = if let Some(ref img_uri) = request.image_data_uri {
520
+ let (mime_type, img_bytes) = parse_data_uri(img_uri)
521
+ .ok_or_else(|| ImageGenError::ModelError("invalid base64 image data URI".to_owned()))?;
522
+
523
+ let is_inpaint =
524
+ request.mask_data_uri.is_some() || request.mode.as_deref() == Some("inpaint");
525
+ let endpoint = if is_inpaint {
526
+ "https://api.stability.ai/v2beta/stable-image/edit/inpaint".to_owned()
527
+ } else {
528
+ "https://api.stability.ai/v2beta/stable-image/generate/sd3".to_owned()
529
+ };
530
+
531
+ let mut body_parts: Vec<(String, String)> = vec![
532
+ ("prompt".into(), request.prompt.clone()),
533
+ ("output_format".into(), "png".to_owned()),
534
+ ];
535
+ if !is_inpaint && model != "core" {
536
+ body_parts.push(("model".into(), model.clone()));
537
+ body_parts.push(("mode".into(), "image-to-image".to_owned()));
538
+ body_parts.push(("strength".into(), "0.7".to_owned()));
539
+ }
540
+
541
+ if let Some(neg) = &request.negative_prompt {
542
+ let neg = neg.trim();
543
+ if !neg.is_empty() {
544
+ body_parts.push(("negative_prompt".into(), neg.to_owned()));
545
+ }
546
+ }
547
+
548
+ let mut file_fields = vec![(
549
+ "image",
550
+ "input.png",
551
+ mime_type.as_str(),
552
+ img_bytes.as_slice(),
553
+ )];
554
+ let mask_storage;
555
+ if let Some(ref mask_uri) = request.mask_data_uri {
556
+ if let Some((mask_mime, mask_bytes)) = parse_data_uri(mask_uri) {
557
+ mask_storage = (mask_mime, mask_bytes);
558
+ file_fields.push((
559
+ "mask",
560
+ "mask.png",
561
+ mask_storage.0.as_str(),
562
+ mask_storage.1.as_slice(),
563
+ ));
564
+ }
565
+ }
566
+
398
567
  (
399
- "https://api.stability.ai/v2beta/stable-image/generate/core".to_owned(),
400
- None,
568
+ endpoint,
569
+ build_multipart_binary(&boundary, &body_parts, &file_fields),
401
570
  )
402
571
  } else {
403
- (
404
- "https://api.stability.ai/v2beta/stable-image/generate/sd3".to_owned(),
405
- Some(model.clone()),
406
- )
407
- };
408
-
409
- let aspect_ratio = request.aspect_ratio.as_deref().unwrap_or("1:1");
572
+ // Route: Stable Image Core uses /core; SD3.x models use /sd3
573
+ let (endpoint, model_param) = if model == "core" {
574
+ (
575
+ "https://api.stability.ai/v2beta/stable-image/generate/core".to_owned(),
576
+ None,
577
+ )
578
+ } else {
579
+ (
580
+ "https://api.stability.ai/v2beta/stable-image/generate/sd3".to_owned(),
581
+ Some(model.clone()),
582
+ )
583
+ };
410
584
 
411
- // Build multipart/form-data manually (reqwest multipart feature not enabled).
412
- let boundary = format!("MintBoundary{}", uuid_v4_hex());
585
+ let aspect_ratio = request.aspect_ratio.as_deref().unwrap_or("1:1");
413
586
 
414
- let mut body_parts: Vec<(String, String)> = vec![
415
- ("prompt".into(), request.prompt.clone()),
416
- ("aspect_ratio".into(), aspect_ratio.to_owned()),
417
- ("output_format".into(), "png".to_owned()),
418
- ];
587
+ let mut body_parts: Vec<(String, String)> = vec![
588
+ ("prompt".into(), request.prompt.clone()),
589
+ ("aspect_ratio".into(), aspect_ratio.to_owned()),
590
+ ("output_format".into(), "png".to_owned()),
591
+ ];
419
592
 
420
- if let Some(neg) = &request.negative_prompt {
421
- let neg = neg.trim();
422
- if !neg.is_empty() {
423
- body_parts.push(("negative_prompt".into(), neg.to_owned()));
593
+ if let Some(neg) = &request.negative_prompt {
594
+ let neg = neg.trim();
595
+ if !neg.is_empty() {
596
+ body_parts.push(("negative_prompt".into(), neg.to_owned()));
597
+ }
424
598
  }
425
- }
426
599
 
427
- if let Some(m) = model_param {
428
- body_parts.push(("model".into(), m));
429
- }
600
+ if let Some(m) = model_param {
601
+ body_parts.push(("model".into(), m));
602
+ }
430
603
 
431
- let raw_body = build_multipart_text(&boundary, &body_parts);
604
+ (
605
+ endpoint,
606
+ build_multipart_text(&boundary, &body_parts).into_bytes(),
607
+ )
608
+ };
432
609
 
433
610
  let response = client
434
611
  .post(&endpoint)
@@ -604,17 +781,23 @@ async fn call_replicate(
604
781
  .map(str::to_owned)
605
782
  .unwrap_or_else(|| {
606
783
  let m = config.replicate_model.trim();
607
- if m.is_empty() {
608
- "black-forest-labs/flux-1.1-pro".to_owned()
609
- } else {
784
+ if !m.is_empty() {
610
785
  m.to_owned()
786
+ } else if request.image_data_uri.is_some() {
787
+ if request.mask_data_uri.is_some() {
788
+ "black-forest-labs/flux-fill-dev".to_owned()
789
+ } else {
790
+ "timbrooks/instruct-pix2pix".to_owned()
791
+ }
792
+ } else {
793
+ "black-forest-labs/flux-1.1-pro".to_owned()
611
794
  }
612
795
  });
613
796
 
614
797
  let num_images = request.num_images.unwrap_or(1).clamp(1, 4);
615
798
  let aspect_ratio = request.aspect_ratio.as_deref().unwrap_or("1:1");
616
799
 
617
- // Build input — common fields used by FLUX and SDXL families
800
+ // Build input — common fields used by FLUX, InstructPix2Pix, and SDXL families
618
801
  let mut input = json!({
619
802
  "prompt": request.prompt,
620
803
  "num_outputs": num_images,
@@ -623,6 +806,13 @@ async fn call_replicate(
623
806
  "output_quality": 90
624
807
  });
625
808
 
809
+ if let Some(ref img_uri) = request.image_data_uri {
810
+ input["image"] = json!(img_uri);
811
+ if let Some(ref mask_uri) = request.mask_data_uri {
812
+ input["mask"] = json!(mask_uri);
813
+ }
814
+ }
815
+
626
816
  if let Some(neg) = &request.negative_prompt {
627
817
  let neg = neg.trim();
628
818
  if !neg.is_empty() {
@@ -659,11 +849,11 @@ async fn call_replicate(
659
849
  poll_replicate(client, &api_key, &prediction_url).await?
660
850
  };
661
851
 
662
- if let Some(err) = prediction.get("error") {
663
- if !err.is_null() {
664
- let msg = err.as_str().unwrap_or("unknown Replicate error");
665
- return Err(ImageGenError::ModelError(msg.to_owned()));
666
- }
852
+ if let Some(err) = prediction.get("error")
853
+ && !err.is_null()
854
+ {
855
+ let msg = err.as_str().unwrap_or("unknown Replicate error");
856
+ return Err(ImageGenError::ModelError(msg.to_owned()));
667
857
  }
668
858
 
669
859
  // Output is an array of image URLs
@@ -790,6 +980,58 @@ fn build_multipart_text(boundary: &str, fields: &[(String, String)]) -> String {
790
980
  body
791
981
  }
792
982
 
983
+ /// Parse base64 data URI (e.g. data:image/png;base64,...) into (mime_type, bytes).
984
+ fn parse_data_uri(data_uri: &str) -> Option<(String, Vec<u8>)> {
985
+ if !data_uri.starts_with("data:") {
986
+ return None;
987
+ }
988
+ let parts: Vec<&str> = data_uri.splitn(2, ',').collect();
989
+ if parts.len() != 2 {
990
+ return None;
991
+ }
992
+ let header = parts[0];
993
+ let b64 = parts[1];
994
+
995
+ let mime_type = header
996
+ .trim_start_matches("data:")
997
+ .split(';')
998
+ .next()
999
+ .unwrap_or("image/png")
1000
+ .trim()
1001
+ .to_string();
1002
+
1003
+ let bytes = STANDARD.decode(b64.trim()).ok()?;
1004
+ Some((mime_type, bytes))
1005
+ }
1006
+
1007
+ /// Build multipart body containing both text fields and binary file fields.
1008
+ fn build_multipart_binary(
1009
+ boundary: &str,
1010
+ text_fields: &[(String, String)],
1011
+ file_fields: &[(&str, &str, &str, &[u8])],
1012
+ ) -> Vec<u8> {
1013
+ let mut body = Vec::new();
1014
+ for (name, value) in text_fields {
1015
+ body.extend_from_slice(format!("--{boundary}\r\n").as_bytes());
1016
+ body.extend_from_slice(
1017
+ format!("Content-Disposition: form-data; name=\"{name}\"\r\n\r\n").as_bytes(),
1018
+ );
1019
+ body.extend_from_slice(value.as_bytes());
1020
+ body.extend_from_slice(b"\r\n");
1021
+ }
1022
+ for (param_name, file_name, mime_type, bytes) in file_fields {
1023
+ body.extend_from_slice(format!("--{boundary}\r\n").as_bytes());
1024
+ body.extend_from_slice(
1025
+ format!("Content-Disposition: form-data; name=\"{param_name}\"; filename=\"{file_name}\"\r\n").as_bytes(),
1026
+ );
1027
+ body.extend_from_slice(format!("Content-Type: {mime_type}\r\n\r\n").as_bytes());
1028
+ body.extend_from_slice(bytes);
1029
+ body.extend_from_slice(b"\r\n");
1030
+ }
1031
+ body.extend_from_slice(format!("--{boundary}--\r\n").as_bytes());
1032
+ body
1033
+ }
1034
+
793
1035
  /// Build the final prompt string, optionally embedding aspect ratio and negative
794
1036
  /// prompt guidance. Used by providers that accept a plain text prompt.
795
1037
  fn build_prompt(request: &ImageGenRequest) -> String {
@@ -832,6 +1074,9 @@ mod tests {
832
1074
  num_images: None,
833
1075
  model: None,
834
1076
  provider: None,
1077
+ image_data_uri: None,
1078
+ mask_data_uri: None,
1079
+ mode: None,
835
1080
  }
836
1081
  }
837
1082
 
@@ -850,6 +1095,9 @@ mod tests {
850
1095
  num_images: Some(2),
851
1096
  model: None,
852
1097
  provider: None,
1098
+ image_data_uri: None,
1099
+ mask_data_uri: None,
1100
+ mode: None,
853
1101
  };
854
1102
  let result = build_prompt(&req);
855
1103
  assert!(result.contains("a cat"));
@@ -866,6 +1114,9 @@ mod tests {
866
1114
  num_images: None,
867
1115
  model: None,
868
1116
  provider: None,
1117
+ image_data_uri: None,
1118
+ mask_data_uri: None,
1119
+ mode: None,
869
1120
  };
870
1121
  assert!(!build_prompt(&req).contains("aspect ratio"));
871
1122
  }
@@ -892,4 +1143,14 @@ mod tests {
892
1143
  assert_eq!(aspect_ratio_to_ideogram(Some("1:1")), "ASPECT_1_1");
893
1144
  assert_eq!(aspect_ratio_to_ideogram(None), "ASPECT_1_1");
894
1145
  }
1146
+
1147
+ #[test]
1148
+ fn parse_data_uri_valid() {
1149
+ let uri = "data:image/png;base64,aGVsbG8=";
1150
+ let parsed = parse_data_uri(uri);
1151
+ assert!(parsed.is_some());
1152
+ let (mime, bytes) = parsed.unwrap();
1153
+ assert_eq!(mime, "image/png");
1154
+ assert_eq!(bytes, b"hello");
1155
+ }
895
1156
  }
@@ -20,6 +20,7 @@ pub mod skills;
20
20
  pub mod symbols;
21
21
  pub mod tasks;
22
22
  pub mod tts;
23
+ pub mod video_gen;
23
24
  pub mod weather;
24
25
  pub mod web_search;
25
26
  pub mod workflows;
@@ -31,8 +32,9 @@ pub use chat::{
31
32
  };
32
33
 
33
34
  pub use browser::{
34
- BrowserTab, click, is_browser_running, list_tabs, navigate, read_page_text,
35
- spawn_automation_browser, type_text,
35
+ BrowserTab, click, get_element_coordinates, is_browser_running, key_press, list_tabs,
36
+ mouse_click, mouse_move, navigate, read_page_text, screenshot, spawn_automation_browser,
37
+ type_text, type_text_native,
36
38
  };
37
39
  pub use code_tools::{
38
40
  AppliedCodeEdit, CodeEdit, CodeEditPreview, CodeEditProposal, CodeFile, CodeInspectionError,
@@ -41,8 +43,8 @@ pub use code_tools::{
41
43
  propose_code_edits, read_code_file, repository_summary, search_code,
42
44
  };
43
45
  pub use config::{
44
- ConfigError, MintConfig, config_path, initialize_config, load_config, save_config,
45
- set_config_value,
46
+ AgentConfig, ConfigError, CustomProvider, CustomProviderHeader, CustomProviderModel,
47
+ MintConfig, config_path, initialize_config, load_config, save_config, set_config_value,
46
48
  };
47
49
  pub use files::{FileOperationError, PathKind, PathMatch, create_folder, find_paths};
48
50
  pub use image_gen::{
@@ -82,6 +84,7 @@ pub use skills::{SkillError, learn_skill, learned_skills_context};
82
84
  pub use symbols::{CodeSymbol, SymbolError, SymbolIndex, build_symbol_index};
83
85
  pub use tasks::{Task, TaskError, TaskStore, tasks_path};
84
86
  pub use tts::{TtsUrl, google_tts_urls};
87
+ pub use video_gen::{VideoGenError, VideoGenRequest, VideoGenResponse, generate_video};
85
88
  pub use weather::{WeatherError, WeatherReport, weather};
86
89
  pub use workflows::{WorkflowError, load_workflows, save_workflows, workflows_path};
87
90
  pub mod api_server;
@@ -92,8 +95,9 @@ pub use channels::start_channels;
92
95
  pub static HTTP_CLIENT: std::sync::LazyLock<reqwest::Client> =
93
96
  std::sync::LazyLock::new(reqwest::Client::new);
94
97
 
95
- pub static ACTIVE_AGENTS: std::sync::LazyLock<std::sync::Mutex<std::collections::HashMap<String, tokio::task::AbortHandle>>> =
96
- std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
98
+ pub static ACTIVE_AGENTS: std::sync::LazyLock<
99
+ std::sync::Mutex<std::collections::HashMap<String, tokio::task::AbortHandle>>,
100
+ > = std::sync::LazyLock::new(|| std::sync::Mutex::new(std::collections::HashMap::new()));
97
101
 
98
102
  pub fn cancel_agent(chat_id: &str) -> bool {
99
103
  let mut agents = ACTIVE_AGENTS.lock().unwrap();
@@ -104,4 +108,3 @@ pub fn cancel_agent(chat_id: &str) -> bool {
104
108
  false
105
109
  }
106
110
  }
107
-