@pheem49/mint 1.6.3 → 1.6.4

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 (50) 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 +6 -2
  21. package/crates/mint-cli/src/main.rs +145 -5
  22. package/crates/mint-cli/src/onboard.rs +134 -1
  23. package/crates/mint-core/src/api_server.rs +108 -3
  24. package/crates/mint-core/src/config.rs +20 -1
  25. package/crates/mint-core/src/image_gen.rs +895 -0
  26. package/crates/mint-core/src/lib.rs +4 -0
  27. package/package.json +1 -1
  28. package/src/bin/index.js +0 -0
  29. package/src/renderer/src/components/ChatPanel.tsx +252 -26
  30. package/src/renderer/src/components/DashboardSidebar.tsx +9 -1
  31. package/src/renderer/src/components/ImageStudioPanel.tsx +638 -0
  32. package/src/renderer/src/components/MintDashboard.tsx +13 -4
  33. package/src/renderer/src/components/SettingsWindow.tsx +29 -20
  34. package/src/renderer/src/css/image-studio.css +722 -0
  35. package/src/renderer/src/css/settings.css +13 -3
  36. package/src/renderer/src/css/styles.css +50 -26
  37. package/src/renderer/src/index.css +106 -0
  38. package/src/renderer/src/tauri.ts +123 -0
  39. package/src/renderer/src-web/components/ChatPanel.tsx +272 -24
  40. package/src/renderer/src-web/components/DashboardSidebar.tsx +9 -1
  41. package/src/renderer/src-web/components/ImageStudioPanel.tsx +638 -0
  42. package/src/renderer/src-web/components/MintDashboard.tsx +12 -3
  43. package/src/renderer/src-web/components/SettingsWindow.tsx +29 -20
  44. package/src/renderer/src-web/css/image-studio.css +722 -0
  45. package/src/renderer/src-web/css/settings.css +14 -4
  46. package/src/renderer/src-web/css/styles.css +50 -26
  47. package/src/renderer/src-web/index.css +106 -0
  48. package/src/renderer/src-web/tauri.ts +124 -0
  49. package/src-tauri/src/lib.rs +48 -5
  50. package/src-tauri/tauri.conf.json +1 -1
@@ -255,7 +255,7 @@ h1 {
255
255
 
256
256
  .web-safe-notice {
257
257
  width: 100%;
258
- max-width: 624px;
258
+ max-width: 1000px;
259
259
  margin: 0 auto 18px;
260
260
  padding: 12px 14px;
261
261
  border: 1px solid color-mix(in srgb, var(--accent) 34%, var(--border));
@@ -297,7 +297,7 @@ h1 {
297
297
  flex-direction: column;
298
298
  gap: 18px;
299
299
  width: 100%;
300
- max-width: 624px;
300
+ max-width: 1000px;
301
301
  margin: 0 auto;
302
302
  animation: fadeTab 0.18s ease;
303
303
  }
@@ -369,7 +369,7 @@ h1 {
369
369
 
370
370
  .form-grid.single {
371
371
  grid-template-columns: minmax(0, 1fr);
372
- max-width: 540px;
372
+ max-width: 100%;
373
373
  }
374
374
 
375
375
  .setting-row {
@@ -623,7 +623,7 @@ input:checked + .toggle-slider::before {
623
623
  .slider-stack {
624
624
  display: grid;
625
625
  gap: 16px;
626
- max-width: 660px;
626
+ max-width: 100%;
627
627
  }
628
628
 
629
629
  .slider-group {
@@ -1098,3 +1098,13 @@ kbd {
1098
1098
  grid-template-columns: minmax(0, 1fr);
1099
1099
  }
1100
1100
  }
1101
+
1102
+ .memory-field-container {
1103
+ display: flex;
1104
+ flex-direction: column;
1105
+ gap: 8px;
1106
+ width: 100%;
1107
+ align-items: flex-start;
1108
+ }
1109
+
1110
+
@@ -18,6 +18,16 @@
18
18
  --border-light: rgba(255, 255, 255, 0.11);
19
19
  --shadow: none;
20
20
  --glass-blur: none;
21
+ --status-listening: #7dd3fc;
22
+ --status-thinking: var(--accent);
23
+ --status-speaking: #22c55e;
24
+ --status-error: #ef4444;
25
+ --hover-delete: #fca5a5;
26
+ --hover-delete-bg: rgba(248, 113, 113, 0.12);
27
+ --hover-edit: #c7d2fe;
28
+ --hover-edit-bg: rgba(99, 102, 241, 0.12);
29
+ --close-btn-hover-bg: #3a2020;
30
+ --close-btn-hover-color: #ff8a8a;
21
31
  }
22
32
 
23
33
  [data-theme="light"] {
@@ -37,6 +47,16 @@
37
47
  --border: rgba(0, 0, 0, 0.06);
38
48
  --border-light: rgba(255, 255, 255, 0.6);
39
49
  --shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
50
+ --status-listening: #0284c7;
51
+ --status-thinking: var(--accent);
52
+ --status-speaking: #16a34a;
53
+ --status-error: #dc2626;
54
+ --hover-delete: #dc2626;
55
+ --hover-delete-bg: rgba(220, 38, 38, 0.08);
56
+ --hover-edit: #4f46e5;
57
+ --hover-edit-bg: rgba(79, 70, 229, 0.08);
58
+ --close-btn-hover-bg: #fee2e2;
59
+ --close-btn-hover-color: #dc2626;
40
60
  }
41
61
 
42
62
  [data-theme="midnight"] {
@@ -56,6 +76,16 @@
56
76
  --border: rgba(99, 102, 241, 0.16);
57
77
  --border-light: rgba(99, 102, 241, 0.26);
58
78
  --shadow: none;
79
+ --status-listening: #7dd3fc;
80
+ --status-thinking: var(--accent);
81
+ --status-speaking: #22c55e;
82
+ --status-error: #fca5a5;
83
+ --hover-delete: #fca5a5;
84
+ --hover-delete-bg: rgba(248, 113, 113, 0.12);
85
+ --hover-edit: #c7d2fe;
86
+ --hover-edit-bg: rgba(99, 102, 241, 0.12);
87
+ --close-btn-hover-bg: #3a2020;
88
+ --close-btn-hover-color: #ff8a8a;
59
89
  }
60
90
 
61
91
  * {
@@ -107,7 +137,6 @@ body {
107
137
 
108
138
  /* Header - Drag Region */
109
139
  .drag-region {
110
- app-region: no-drag;
111
140
  -webkit-app-region: no-drag;
112
141
  min-height: 34px;
113
142
  padding: 0 8px;
@@ -124,7 +153,6 @@ body {
124
153
  }
125
154
 
126
155
  .header-content {
127
- app-region: drag;
128
156
  -webkit-app-region: drag;
129
157
  display: flex;
130
158
  align-items: center;
@@ -134,7 +162,6 @@ body {
134
162
  }
135
163
 
136
164
  .titlebar-drag-space {
137
- app-region: drag;
138
165
  -webkit-app-region: drag;
139
166
  align-self: stretch;
140
167
  min-width: 0;
@@ -165,7 +192,6 @@ h1 {
165
192
  .header-actions,
166
193
  .header-utility,
167
194
  .window-controls {
168
- app-region: no-drag;
169
195
  -webkit-app-region: no-drag;
170
196
  display: flex;
171
197
  align-items: center;
@@ -197,7 +223,6 @@ h1 {
197
223
  .clear-btn,
198
224
  .settings-btn {
199
225
  -webkit-app-region: no-drag;
200
- app-region: no-drag;
201
226
  min-width: 0;
202
227
  height: 28px;
203
228
  border: 1px solid transparent;
@@ -231,7 +256,6 @@ h1 {
231
256
  .maximize-btn,
232
257
  .minimize-btn {
233
258
  -webkit-app-region: no-drag;
234
- app-region: no-drag;
235
259
  background: transparent;
236
260
  border: none;
237
261
  color: var(--text-muted);
@@ -1282,8 +1306,8 @@ h1 {
1282
1306
  .approval-card {
1283
1307
  align-items: stretch;
1284
1308
  flex-direction: column;
1285
- background: rgba(245, 158, 11, 0.12);
1286
- border-color: rgba(245, 158, 11, 0.38);
1309
+ background: transparent;
1310
+ border-color: transparent;
1287
1311
  }
1288
1312
 
1289
1313
  .approval-card[data-tier="dangerous"] {
@@ -2190,13 +2214,13 @@ input:checked + .slider:before {
2190
2214
  }
2191
2215
 
2192
2216
  .sidebar-chat-delete:hover {
2193
- color: #fca5a5;
2194
- background: rgba(248, 113, 113, 0.12);
2217
+ color: var(--hover-delete);
2218
+ background: var(--hover-delete-bg);
2195
2219
  }
2196
2220
 
2197
2221
  .sidebar-chat-edit:hover {
2198
- color: #c7d2fe;
2199
- background: rgba(99, 102, 241, 0.12);
2222
+ color: var(--hover-edit);
2223
+ background: var(--hover-edit-bg);
2200
2224
  }
2201
2225
 
2202
2226
  .sidebar-chat-rename-input {
@@ -2288,49 +2312,49 @@ input:checked + .slider:before {
2288
2312
 
2289
2313
  .mint-status-pill[data-state="listening"],
2290
2314
  .model-activity-badge[data-state="listening"] {
2291
- color: #7dd3fc;
2315
+ color: var(--status-listening);
2292
2316
  }
2293
2317
 
2294
2318
  .mint-status-pill[data-state="listening"] .mint-status-dot,
2295
2319
  .model-activity-badge[data-state="listening"] .mint-status-dot {
2296
- background: #38bdf8;
2297
- box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.16);
2320
+ background: var(--status-listening);
2321
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--status-listening) 16%, transparent);
2298
2322
  animation: mintStatusPulse 1.2s ease-in-out infinite;
2299
2323
  }
2300
2324
 
2301
2325
  .mint-status-pill[data-state="thinking"],
2302
2326
  .model-activity-badge[data-state="thinking"] {
2303
- color: #a7f3d0;
2327
+ color: var(--status-thinking);
2304
2328
  }
2305
2329
 
2306
2330
  .mint-status-pill[data-state="thinking"] .mint-status-dot,
2307
2331
  .model-activity-badge[data-state="thinking"] .mint-status-dot {
2308
- background: var(--accent);
2309
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
2332
+ background: var(--status-thinking);
2333
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--status-thinking) 20%, transparent);
2310
2334
  animation: mintStatusPulse 1s ease-in-out infinite;
2311
2335
  }
2312
2336
 
2313
2337
  .mint-status-pill[data-state="speaking"],
2314
2338
  .model-activity-badge[data-state="speaking"] {
2315
- color: #86efac;
2339
+ color: var(--status-speaking);
2316
2340
  }
2317
2341
 
2318
2342
  .mint-status-pill[data-state="speaking"] .mint-status-dot,
2319
2343
  .model-activity-badge[data-state="speaking"] .mint-status-dot {
2320
- background: #22c55e;
2321
- box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.16);
2344
+ background: var(--status-speaking);
2345
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--status-speaking) 16%, transparent);
2322
2346
  animation: mintStatusPulse 0.9s ease-in-out infinite;
2323
2347
  }
2324
2348
 
2325
2349
  .mint-status-pill[data-state="error"],
2326
2350
  .model-activity-badge[data-state="error"] {
2327
- color: #fca5a5;
2351
+ color: var(--status-error);
2328
2352
  }
2329
2353
 
2330
2354
  .mint-status-pill[data-state="error"] .mint-status-dot,
2331
2355
  .model-activity-badge[data-state="error"] .mint-status-dot {
2332
- background: #ef4444;
2333
- box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.16);
2356
+ background: var(--status-error);
2357
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--status-error) 16%, transparent);
2334
2358
  }
2335
2359
 
2336
2360
  @keyframes mintStatusPulse {
@@ -3319,8 +3343,8 @@ input:checked + .slider:before {
3319
3343
  }
3320
3344
 
3321
3345
  .close-btn:hover {
3322
- background: #3a2020;
3323
- color: #ff8a8a;
3346
+ background: var(--close-btn-hover-bg);
3347
+ color: var(--close-btn-hover-color);
3324
3348
  }
3325
3349
 
3326
3350
  .assistant-workspace.layout-horizontal {
@@ -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,108 @@ 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
+ }
@@ -66,6 +66,30 @@ export interface PictureEntry {
66
66
  thumbnailUrl?: string
67
67
  }
68
68
 
69
+ export interface ImageGenRequest {
70
+ prompt: string
71
+ negativePrompt?: string
72
+ aspectRatio?: '1:1' | '16:9' | '9:16' | '4:3'
73
+ numImages?: number
74
+ model?: string
75
+ /** Which image generation provider to use. Falls back to config default when omitted. */
76
+ provider?: string
77
+ }
78
+
79
+ export interface ImageGenProviders {
80
+ active: string
81
+ available: string[]
82
+ }
83
+
84
+ export interface ImageGenResponse {
85
+ images: PictureEntry[]
86
+ model: string
87
+ provider: string
88
+ prompt: string
89
+ description?: string | null
90
+ }
91
+
92
+
69
93
  export interface CodeEdit {
70
94
  path: string
71
95
  content: string
@@ -404,6 +428,106 @@ export async function listSavedPictures(): Promise<PictureEntry[]> {
404
428
  return invoke<PictureEntry[]>('list_pictures')
405
429
  }
406
430
 
431
+ export async function generateImages(
432
+ request: ImageGenRequest
433
+ ): Promise<ImageGenResponse> {
434
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
435
+ const API_BASE = getApiBase()
436
+ const res = await fetch(`${API_BASE}/image-generate`, {
437
+ method: 'POST',
438
+ headers: { 'Content-Type': 'application/json' },
439
+ body: JSON.stringify({
440
+ prompt: request.prompt,
441
+ negativePrompt: request.negativePrompt,
442
+ aspectRatio: request.aspectRatio ?? '1:1',
443
+ numImages: request.numImages ?? 1,
444
+ model: request.model,
445
+ provider: request.provider,
446
+ }),
447
+ })
448
+ if (!res.ok) {
449
+ const err = await res.json().catch(() => null)
450
+ throw new Error(err?.error || `Image generation failed: HTTP ${res.status}`)
451
+ }
452
+ const data = await res.json()
453
+ // Normalize image URLs for web mode
454
+ const images: PictureEntry[] = Array.isArray(data.images)
455
+ ? data.images.map((pic: PictureEntry) => {
456
+ const pictureUrl = pic.url ? `${API_BASE.replace('/api', '')}${pic.url}` : pic.url
457
+ return { ...pic, url: pictureUrl, thumbnailUrl: pictureUrl }
458
+ })
459
+ : []
460
+ return { ...data, images }
461
+ }
462
+ // Desktop / Tauri mode
463
+ const { invoke } = await import('@tauri-apps/api/core')
464
+ return invoke<ImageGenResponse>('generate_images', { request })
465
+ }
466
+
467
+ /** Fetch which image-generation providers are currently configured on the backend. */
468
+ export async function getImageGenProviders(): Promise<ImageGenProviders> {
469
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
470
+ const API_BASE = getApiBase()
471
+ try {
472
+ const res = await fetch(`${API_BASE}/image-gen/providers`)
473
+ if (res.ok) return await res.json()
474
+ } catch (_) { /* ignore */ }
475
+ return { active: 'nanobanana', available: ['nanobanana'] }
476
+ }
477
+ // Desktop / Tauri: read config to know which keys are set
478
+ try {
479
+ const { invoke } = await import('@tauri-apps/api/core')
480
+ const config = await invoke<Record<string, string>>('get_config')
481
+ const available: string[] = []
482
+ if (config.api_key) available.push('nanobanana')
483
+ if (config.openai_api_key) available.push('dalle')
484
+ if (config.stability_api_key) available.push('stability')
485
+ if (config.ideogram_api_key) available.push('ideogram')
486
+ if (config.replicate_api_key) available.push('replicate')
487
+ if (available.length === 0) available.push('nanobanana')
488
+ const active = available.includes(config.image_gen_provider)
489
+ ? config.image_gen_provider
490
+ : available[0]
491
+ return { active, available }
492
+ } catch (_) {
493
+ return { active: 'nanobanana', available: ['nanobanana'] }
494
+ }
495
+ }
496
+
497
+ /** Updates the default image generation provider in the configuration. */
498
+ export async function setDefaultImageProvider(provider: string): Promise<boolean> {
499
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
500
+ const API_BASE = getApiBase()
501
+ try {
502
+ const getRes = await fetch(`${API_BASE}/config`)
503
+ if (!getRes.ok) return false
504
+ const config = await getRes.json()
505
+ config.image_gen_provider = provider
506
+ const saveRes = await fetch(`${API_BASE}/config`, {
507
+ method: 'POST',
508
+ headers: { 'Content-Type': 'application/json' },
509
+ body: JSON.stringify(config)
510
+ })
511
+ return saveRes.ok
512
+ } catch (_) {
513
+ return false
514
+ }
515
+ }
516
+
517
+ // Desktop / Tauri
518
+ try {
519
+ const { invoke } = await import('@tauri-apps/api/core')
520
+ const config = await invoke<any>('get_config')
521
+ config.image_gen_provider = provider
522
+ await invoke('update_config', { config })
523
+ return true
524
+ } catch (_) {
525
+ return false
526
+ }
527
+ }
528
+
529
+
530
+
407
531
  export async function submitToolApproval(token: string, approved: boolean): Promise<void> {
408
532
  if (typeof window === 'undefined' || !isTauriRuntime()) {
409
533
  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
 
@@ -626,6 +627,47 @@ async fn submit_tool_approval(
626
627
  }
627
628
  }
628
629
 
630
+ #[derive(serde::Serialize)]
631
+ #[serde(rename_all = "camelCase")]
632
+ struct DesktopImageGenResponse {
633
+ images: Vec<PictureEntry>,
634
+ model: String,
635
+ provider: String,
636
+ prompt: String,
637
+ description: Option<String>,
638
+ }
639
+
640
+ #[tauri::command]
641
+ async fn generate_images(request: ImageGenRequest) -> Result<DesktopImageGenResponse, String> {
642
+ let config = load_config().map_err(|error| error.to_string())?;
643
+
644
+ // Call core generate_images logic
645
+ let result = mint_core::generate_images(&config, &request)
646
+ .await
647
+ .map_err(|error| error.to_string())?;
648
+
649
+ // Save images to Pictures library (just like api_server does)
650
+ let data_uris: Vec<String> = result
651
+ .images
652
+ .iter()
653
+ .map(|img| img.data_uri.clone())
654
+ .collect();
655
+ let saved = save_chat_images(
656
+ data_uris,
657
+ Some("nanobanana".into()),
658
+ Some(request.prompt.clone()),
659
+ )
660
+ .map_err(|error| error.to_string())?;
661
+
662
+ Ok(DesktopImageGenResponse {
663
+ images: saved,
664
+ model: result.model,
665
+ provider: result.provider,
666
+ prompt: result.prompt,
667
+ description: result.description,
668
+ })
669
+ }
670
+
629
671
  #[tauri::command]
630
672
  fn list_pictures() -> Result<Vec<PictureEntry>, String> {
631
673
  list_saved_pictures().map_err(|error| error.to_string())
@@ -978,6 +1020,7 @@ pub fn run() {
978
1020
  .invoke_handler(tauri::generate_handler![
979
1021
  get_runtime_status,
980
1022
  get_workspace_tree,
1023
+ generate_images,
981
1024
  select_workspace_directory,
982
1025
  get_config,
983
1026
  get_updater_status,
@@ -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.6.4",
5
5
  "identifier": "com.pheem49.mint",
6
6
  "build": {
7
7
  "beforeDevCommand": "npm run dev:desktop",