@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.
Files changed (63) hide show
  1. package/Cargo.lock +3 -3
  2. package/Cargo.toml +1 -1
  3. package/README.md +82 -21
  4. package/assets/Mint_CLI.png +0 -0
  5. package/assets/Mint_Desktop.png +0 -0
  6. package/assets/Mint_Web.png +0 -0
  7. package/assets/Mint_WorkSpace.png +0 -0
  8. package/assets/Setting.png +0 -0
  9. package/assets/bridges.svg +4 -0
  10. package/assets/chat.svg +3 -0
  11. package/assets/code.svg +4 -0
  12. package/assets/features.svg +3 -0
  13. package/assets/imagegen.svg +5 -0
  14. package/assets/live2d.svg +6 -0
  15. package/assets/memory.svg +5 -0
  16. package/assets/screencapture.svg +4 -0
  17. package/assets/setup.svg +3 -0
  18. package/assets/tools.svg +4 -0
  19. package/bin/mint +0 -0
  20. package/crates/mint-cli/src/agent.rs +47 -15
  21. package/crates/mint-cli/src/main.rs +534 -7
  22. package/crates/mint-cli/src/onboard.rs +134 -1
  23. package/crates/mint-cli/src/setup.rs +173 -32
  24. package/crates/mint-core/src/api_server.rs +147 -3
  25. package/crates/mint-core/src/config.rs +22 -3
  26. package/crates/mint-core/src/image_gen.rs +895 -0
  27. package/crates/mint-core/src/lib.rs +4 -0
  28. package/crates/mint-core/src/memory.rs +27 -1
  29. package/crates/mint-core/src/orchestration.rs +1 -1
  30. package/crates/mint-core/src/plugins.rs +32 -0
  31. package/crates/mint-core/tests/memory_persistence.rs +28 -0
  32. package/package.json +1 -1
  33. package/src/bin/index.js +0 -0
  34. package/src/renderer/src/agentProgress.ts +39 -0
  35. package/src/renderer/src/components/ChatPanel.tsx +547 -47
  36. package/src/renderer/src/components/DashboardSidebar.tsx +12 -4
  37. package/src/renderer/src/components/ImageStudioPanel.tsx +638 -0
  38. package/src/renderer/src/components/MintDashboard.tsx +84 -31
  39. package/src/renderer/src/components/ModelPanel.tsx +2 -2
  40. package/src/renderer/src/components/PicturesLibrary.tsx +5 -1
  41. package/src/renderer/src/components/SettingsWindow.tsx +29 -20
  42. package/src/renderer/src/components/SpotlightWindow.tsx +8 -8
  43. package/src/renderer/src/components/WorkspacePanel.tsx +1 -1
  44. package/src/renderer/src/css/image-studio.css +722 -0
  45. package/src/renderer/src/css/settings.css +13 -3
  46. package/src/renderer/src/css/styles.css +245 -43
  47. package/src/renderer/src/index.css +152 -0
  48. package/src/renderer/src/tauri.ts +148 -0
  49. package/src/renderer/src-web/App.tsx +16 -1
  50. package/src/renderer/src-web/agentProgress.ts +39 -0
  51. package/src/renderer/src-web/components/ChatPanel.tsx +568 -46
  52. package/src/renderer/src-web/components/DashboardSidebar.tsx +10 -2
  53. package/src/renderer/src-web/components/ImageStudioPanel.tsx +638 -0
  54. package/src/renderer/src-web/components/MintDashboard.tsx +65 -19
  55. package/src/renderer/src-web/components/PicturesLibrary.tsx +5 -1
  56. package/src/renderer/src-web/components/SettingsWindow.tsx +29 -20
  57. package/src/renderer/src-web/css/image-studio.css +722 -0
  58. package/src/renderer/src-web/css/settings.css +14 -4
  59. package/src/renderer/src-web/css/styles.css +292 -44
  60. package/src/renderer/src-web/index.css +152 -0
  61. package/src/renderer/src-web/tauri.ts +149 -0
  62. package/src-tauri/src/lib.rs +65 -5
  63. package/src-tauri/tauri.conf.json +1 -1
@@ -2,6 +2,7 @@
2
2
  @import './css/settings.css';
3
3
  @import './css/spotlight.css';
4
4
  @import './css/widget.css';
5
+ @import './css/image-studio.css';
5
6
 
6
7
  * {
7
8
  box-sizing: border-box;
@@ -377,3 +378,154 @@ button {
377
378
  text-align: center;
378
379
  }
379
380
  }
381
+
382
+ /* Premium Markdown Styles for Chat Messages */
383
+ .chat-inline-code {
384
+ background: rgba(16, 185, 129, 0.15);
385
+ border: 1px solid rgba(16, 185, 129, 0.25);
386
+ color: #34d399;
387
+ padding: 2px 6px;
388
+ border-radius: 6px;
389
+ font-family: 'Fira Code', 'Courier New', Courier, monospace;
390
+ font-size: 0.88em;
391
+ }
392
+
393
+ .chat-code-block-container {
394
+ display: flex;
395
+ flex-direction: column;
396
+ background: var(--input-bg, #0f172a);
397
+ border: 1px solid var(--border);
398
+ border-radius: 8px;
399
+ margin: 12px 0;
400
+ overflow: hidden;
401
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
402
+ }
403
+
404
+ .chat-code-block-header {
405
+ display: flex;
406
+ align-items: center;
407
+ justify-content: space-between;
408
+ background: var(--surface-bg, #1e293b);
409
+ padding: 8px 14px;
410
+ border-bottom: 1px solid var(--border);
411
+ }
412
+
413
+ .chat-code-block-lang {
414
+ color: var(--accent);
415
+ font-size: 0.78rem;
416
+ font-weight: 650;
417
+ text-transform: capitalize;
418
+ font-family: ui-sans-serif, system-ui, sans-serif;
419
+ }
420
+
421
+ .chat-code-block-actions {
422
+ display: flex;
423
+ gap: 10px;
424
+ }
425
+
426
+ .chat-code-action-btn {
427
+ background: transparent;
428
+ border: none;
429
+ color: var(--text-soft, #94a3b8);
430
+ cursor: pointer;
431
+ padding: 4px;
432
+ border-radius: 4px;
433
+ display: flex;
434
+ align-items: center;
435
+ justify-content: center;
436
+ transition: all 0.2s ease;
437
+ }
438
+
439
+ .chat-code-action-btn:hover {
440
+ background: rgba(255, 255, 255, 0.05);
441
+ color: var(--accent-hover, #34d399);
442
+ }
443
+
444
+ .chat-code-block-body {
445
+ margin: 0;
446
+ padding: 14px;
447
+ overflow-x: auto;
448
+ background: var(--input-bg, #0f172a);
449
+ }
450
+
451
+ .chat-code-block-body code {
452
+ font-family: 'Fira Code', 'Courier New', Courier, monospace;
453
+ font-size: 0.88rem;
454
+ color: var(--text-main, #e2e8f0);
455
+ line-height: 1.5;
456
+ white-space: pre;
457
+ }
458
+
459
+ .chat-ordered-list,
460
+ .chat-unordered-list {
461
+ margin: 8px 0;
462
+ padding-left: 24px;
463
+ }
464
+
465
+ .chat-ordered-list {
466
+ list-style-type: decimal;
467
+ }
468
+
469
+ .chat-unordered-list {
470
+ list-style-type: disc;
471
+ }
472
+
473
+ .chat-bold-highlight {
474
+ color: var(--accent) !important;
475
+ }
476
+
477
+ .chat-inline-code {
478
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
479
+ border: 1px solid color-mix(in srgb, var(--accent) 24%, transparent);
480
+ color: var(--accent);
481
+ padding: 2px 6px;
482
+ border-radius: 4px;
483
+ font-family: 'Fira Code', 'Courier New', Courier, monospace;
484
+ font-size: 0.9em;
485
+ }
486
+
487
+ /* Premium Table Styles for Chat Panel */
488
+ .chat-table-container {
489
+ overflow-x: auto;
490
+ margin: 14px 0;
491
+ width: 100%;
492
+ border-radius: 8px;
493
+ border: 1px solid rgba(255, 255, 255, 0.12);
494
+ background: rgba(30, 41, 59, 0.35);
495
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
496
+ }
497
+
498
+ .chat-table {
499
+ width: 100%;
500
+ border-collapse: collapse;
501
+ font-size: 0.86rem;
502
+ text-align: left;
503
+ line-height: 1.5;
504
+ }
505
+
506
+ .chat-table th {
507
+ padding: 12px 16px;
508
+ font-weight: 700;
509
+ color: var(--accent);
510
+ background: rgba(255, 255, 255, 0.04);
511
+ border-bottom: 2px solid rgba(255, 255, 255, 0.15);
512
+ font-family: ui-sans-serif, system-ui, sans-serif;
513
+ }
514
+
515
+ .chat-table td {
516
+ padding: 12px 16px;
517
+ color: var(--text-main, #e2e8f0);
518
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
519
+ }
520
+
521
+ .chat-table tbody tr {
522
+ transition: background 0.15s ease;
523
+ }
524
+
525
+ .chat-table tbody tr:hover {
526
+ background: rgba(255, 255, 255, 0.03);
527
+ }
528
+
529
+ .chat-table tbody tr:last-child td {
530
+ border-bottom: none;
531
+ }
@@ -43,6 +43,7 @@ export interface InteractionMemory {
43
43
  model: string
44
44
  fallbackProvider?: string | null
45
45
  createdAt: string
46
+ agentActivity?: AgentProgress[] | null
46
47
  }
47
48
 
48
49
  export interface ChatSession {
@@ -66,6 +67,30 @@ export interface PictureEntry {
66
67
  thumbnailUrl?: string
67
68
  }
68
69
 
70
+ export interface ImageGenRequest {
71
+ prompt: string
72
+ negativePrompt?: string
73
+ aspectRatio?: '1:1' | '16:9' | '9:16' | '4:3'
74
+ numImages?: number
75
+ model?: string
76
+ /** Which image generation provider to use. Falls back to config default when omitted. */
77
+ provider?: string
78
+ }
79
+
80
+ export interface ImageGenProviders {
81
+ active: string
82
+ available: string[]
83
+ }
84
+
85
+ export interface ImageGenResponse {
86
+ images: PictureEntry[]
87
+ model: string
88
+ provider: string
89
+ prompt: string
90
+ description?: string | null
91
+ }
92
+
93
+
69
94
  export interface CodeEdit {
70
95
  path: string
71
96
  content: string
@@ -268,6 +293,30 @@ export async function getRecentInteractions(limit = 50, chatId?: string | null):
268
293
  return invoke<InteractionMemory[]>('get_recent_interactions', { limit, chatId })
269
294
  }
270
295
 
296
+ export async function saveInteractionAgentActivity(
297
+ interactionId: number,
298
+ activity: AgentProgress[],
299
+ ): Promise<void> {
300
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
301
+ const API_BASE = getApiBase();
302
+ try {
303
+ const res = await fetch(`${API_BASE}/interactions/agent-activity`, {
304
+ method: 'POST',
305
+ headers: { 'Content-Type': 'application/json' },
306
+ body: JSON.stringify({ interactionId, activity }),
307
+ });
308
+ if (!res.ok) {
309
+ throw new Error(`HTTP ${res.status}`);
310
+ }
311
+ } catch (error) {
312
+ console.error('Failed to persist agent activity:', error);
313
+ }
314
+ return;
315
+ }
316
+ const { invoke } = await import('@tauri-apps/api/core')
317
+ await invoke('save_interaction_agent_activity', { interactionId, activity })
318
+ }
319
+
271
320
  export async function listChatSessions(): Promise<ChatSession[]> {
272
321
  if (typeof window === 'undefined' || !isTauriRuntime()) {
273
322
  const API_BASE = getApiBase();
@@ -404,6 +453,106 @@ export async function listSavedPictures(): Promise<PictureEntry[]> {
404
453
  return invoke<PictureEntry[]>('list_pictures')
405
454
  }
406
455
 
456
+ export async function generateImages(
457
+ request: ImageGenRequest
458
+ ): Promise<ImageGenResponse> {
459
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
460
+ const API_BASE = getApiBase()
461
+ const res = await fetch(`${API_BASE}/image-generate`, {
462
+ method: 'POST',
463
+ headers: { 'Content-Type': 'application/json' },
464
+ body: JSON.stringify({
465
+ prompt: request.prompt,
466
+ negativePrompt: request.negativePrompt,
467
+ aspectRatio: request.aspectRatio ?? '1:1',
468
+ numImages: request.numImages ?? 1,
469
+ model: request.model,
470
+ provider: request.provider,
471
+ }),
472
+ })
473
+ if (!res.ok) {
474
+ const err = await res.json().catch(() => null)
475
+ throw new Error(err?.error || `Image generation failed: HTTP ${res.status}`)
476
+ }
477
+ const data = await res.json()
478
+ // Normalize image URLs for web mode
479
+ const images: PictureEntry[] = Array.isArray(data.images)
480
+ ? data.images.map((pic: PictureEntry) => {
481
+ const pictureUrl = pic.url ? `${API_BASE.replace('/api', '')}${pic.url}` : pic.url
482
+ return { ...pic, url: pictureUrl, thumbnailUrl: pictureUrl }
483
+ })
484
+ : []
485
+ return { ...data, images }
486
+ }
487
+ // Desktop / Tauri mode
488
+ const { invoke } = await import('@tauri-apps/api/core')
489
+ return invoke<ImageGenResponse>('generate_images', { request })
490
+ }
491
+
492
+ /** Fetch which image-generation providers are currently configured on the backend. */
493
+ export async function getImageGenProviders(): Promise<ImageGenProviders> {
494
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
495
+ const API_BASE = getApiBase()
496
+ try {
497
+ const res = await fetch(`${API_BASE}/image-gen/providers`)
498
+ if (res.ok) return await res.json()
499
+ } catch (_) { /* ignore */ }
500
+ return { active: 'nanobanana', available: ['nanobanana'] }
501
+ }
502
+ // Desktop / Tauri: read config to know which keys are set
503
+ try {
504
+ const { invoke } = await import('@tauri-apps/api/core')
505
+ const config = await invoke<Record<string, string>>('get_config')
506
+ const available: string[] = []
507
+ if (config.api_key) available.push('nanobanana')
508
+ if (config.openai_api_key) available.push('dalle')
509
+ if (config.stability_api_key) available.push('stability')
510
+ if (config.ideogram_api_key) available.push('ideogram')
511
+ if (config.replicate_api_key) available.push('replicate')
512
+ if (available.length === 0) available.push('nanobanana')
513
+ const active = available.includes(config.image_gen_provider)
514
+ ? config.image_gen_provider
515
+ : available[0]
516
+ return { active, available }
517
+ } catch (_) {
518
+ return { active: 'nanobanana', available: ['nanobanana'] }
519
+ }
520
+ }
521
+
522
+ /** Updates the default image generation provider in the configuration. */
523
+ export async function setDefaultImageProvider(provider: string): Promise<boolean> {
524
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
525
+ const API_BASE = getApiBase()
526
+ try {
527
+ const getRes = await fetch(`${API_BASE}/config`)
528
+ if (!getRes.ok) return false
529
+ const config = await getRes.json()
530
+ config.image_gen_provider = provider
531
+ const saveRes = await fetch(`${API_BASE}/config`, {
532
+ method: 'POST',
533
+ headers: { 'Content-Type': 'application/json' },
534
+ body: JSON.stringify(config)
535
+ })
536
+ return saveRes.ok
537
+ } catch (_) {
538
+ return false
539
+ }
540
+ }
541
+
542
+ // Desktop / Tauri
543
+ try {
544
+ const { invoke } = await import('@tauri-apps/api/core')
545
+ const config = await invoke<any>('get_config')
546
+ config.image_gen_provider = provider
547
+ await invoke('update_config', { config })
548
+ return true
549
+ } catch (_) {
550
+ return false
551
+ }
552
+ }
553
+
554
+
555
+
407
556
  export async function submitToolApproval(token: string, approved: boolean): Promise<void> {
408
557
  if (typeof window === 'undefined' || !isTauriRuntime()) {
409
558
  return;
@@ -32,11 +32,12 @@ use tokio::sync::oneshot;
32
32
  use integrations::{channel_inventory, list_plugins};
33
33
  use mint_core::{
34
34
  AgentApproval, AgentProgress, AppliedCodeEdit, ApprovalOutcome, ChatRequest, ChatResponse,
35
- ChatSession, CodeEdit, CodeEditProposal, InteractionMemory, MemoryStore, MintConfig,
36
- PictureEntry, TtsUrl, WeatherReport, apply_code_edits, classify_shell_command, config_path,
37
- extract_document_text, google_tts_urls, list_saved_pictures, load_config, load_workflows,
38
- orchestrate_agent_loop, orchestrate_chat_stream_with_fallback, orchestrate_chat_with_fallback,
39
- propose_code_edits, save_chat_images, save_config, start_channels, weather, workflows_path,
35
+ ChatSession, CodeEdit, CodeEditProposal, ImageGenRequest, InteractionMemory, MemoryStore,
36
+ 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,
40
41
  };
41
42
  use plugins::execute_plugin;
42
43
 
@@ -546,6 +547,22 @@ async fn stream_chat_message(
546
547
  })
547
548
  }
548
549
 
550
+ #[tauri::command]
551
+ fn save_interaction_agent_activity(
552
+ interaction_id: i64,
553
+ activity: Vec<AgentProgress>,
554
+ ) -> Result<(), String> {
555
+ let activity_json =
556
+ serde_json::to_string(&activity).map_err(|error| error.to_string())?;
557
+ MemoryStore::open_default()
558
+ .and_then(|memory| {
559
+ memory
560
+ .set_interaction_agent_activity_json(interaction_id, &activity_json)
561
+ .map(|_| ())
562
+ })
563
+ .map_err(|error| error.to_string())
564
+ }
565
+
549
566
  #[tauri::command]
550
567
  fn get_recent_interactions(
551
568
  limit: Option<usize>,
@@ -626,6 +643,47 @@ async fn submit_tool_approval(
626
643
  }
627
644
  }
628
645
 
646
+ #[derive(serde::Serialize)]
647
+ #[serde(rename_all = "camelCase")]
648
+ struct DesktopImageGenResponse {
649
+ images: Vec<PictureEntry>,
650
+ model: String,
651
+ provider: String,
652
+ prompt: String,
653
+ description: Option<String>,
654
+ }
655
+
656
+ #[tauri::command]
657
+ async fn generate_images(request: ImageGenRequest) -> Result<DesktopImageGenResponse, String> {
658
+ let config = load_config().map_err(|error| error.to_string())?;
659
+
660
+ // Call core generate_images logic
661
+ let result = mint_core::generate_images(&config, &request)
662
+ .await
663
+ .map_err(|error| error.to_string())?;
664
+
665
+ // Save images to Pictures library (just like api_server does)
666
+ let data_uris: Vec<String> = result
667
+ .images
668
+ .iter()
669
+ .map(|img| img.data_uri.clone())
670
+ .collect();
671
+ let saved = save_chat_images(
672
+ data_uris,
673
+ Some("nanobanana".into()),
674
+ Some(request.prompt.clone()),
675
+ )
676
+ .map_err(|error| error.to_string())?;
677
+
678
+ Ok(DesktopImageGenResponse {
679
+ images: saved,
680
+ model: result.model,
681
+ provider: result.provider,
682
+ prompt: result.prompt,
683
+ description: result.description,
684
+ })
685
+ }
686
+
629
687
  #[tauri::command]
630
688
  fn list_pictures() -> Result<Vec<PictureEntry>, String> {
631
689
  list_saved_pictures().map_err(|error| error.to_string())
@@ -978,6 +1036,7 @@ pub fn run() {
978
1036
  .invoke_handler(tauri::generate_handler![
979
1037
  get_runtime_status,
980
1038
  get_workspace_tree,
1039
+ generate_images,
981
1040
  select_workspace_directory,
982
1041
  get_config,
983
1042
  get_updater_status,
@@ -989,6 +1048,7 @@ pub fn run() {
989
1048
  stream_chat_message,
990
1049
  submit_tool_approval,
991
1050
  get_recent_interactions,
1051
+ save_interaction_agent_activity,
992
1052
  list_chat_sessions,
993
1053
  delete_chat_session,
994
1054
  rename_chat_session,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://schema.tauri.app/config/2",
3
3
  "productName": "Mint",
4
- "version": "1.6.2",
4
+ "version": "1.7.0",
5
5
  "identifier": "com.pheem49.mint",
6
6
  "build": {
7
7
  "beforeDevCommand": "npm run dev:desktop",