@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
@@ -0,0 +1,542 @@
1
+ /* CSS for Mint Workflow Builder Panel */
2
+
3
+ .workflows-builder-container {
4
+ position: absolute;
5
+ top: 0;
6
+ bottom: 0;
7
+ right: 0;
8
+ left: var(--sidebar-expanded-width, 264px);
9
+ z-index: 5;
10
+ display: grid;
11
+ grid-template-columns: 280px minmax(0, 1fr);
12
+ overflow: hidden;
13
+ background: var(--bg-gradient);
14
+ opacity: 0;
15
+ pointer-events: none;
16
+ transform: translateY(12px) scale(0.995);
17
+ transition: left 0.22s ease, opacity 0.22s cubic-bezier(0.4, 0, 0.2, 1), transform 0.22s cubic-bezier(0.4, 0, 0.2, 1);
18
+ }
19
+
20
+ .workflows-builder-container.is-visible {
21
+ opacity: 1;
22
+ pointer-events: auto;
23
+ transform: translateY(0) scale(1);
24
+ }
25
+
26
+ .app-body.sidebar-collapsed .workflows-builder-container {
27
+ left: calc(var(--sidebar-collapsed-width, 56px) + var(--sidebar-content-gap, 10px));
28
+ }
29
+
30
+ /* Master Sidebar List */
31
+ .workflows-sidebar {
32
+ display: flex;
33
+ flex-direction: column;
34
+ border-right: 1px solid rgba(148, 163, 184, 0.12);
35
+ background: var(--chrome-bg);
36
+ backdrop-filter: var(--glass-blur);
37
+ height: 100%;
38
+ overflow: hidden;
39
+ }
40
+
41
+ .workflows-sidebar-header {
42
+ padding: 16px;
43
+ border-bottom: 1px solid rgba(148, 163, 184, 0.1);
44
+ display: flex;
45
+ flex-direction: column;
46
+ gap: 10px;
47
+ }
48
+
49
+ .workflows-sidebar-header h3 {
50
+ margin: 0;
51
+ font-size: 1.1rem;
52
+ color: var(--accent);
53
+ font-weight: 600;
54
+ }
55
+
56
+ .workflows-list {
57
+ flex: 1;
58
+ overflow-y: auto;
59
+ padding: 12px;
60
+ display: flex;
61
+ flex-direction: column;
62
+ gap: 8px;
63
+ }
64
+
65
+ .workflow-list-item {
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: space-between;
69
+ padding: 10px 12px;
70
+ background: rgba(30, 41, 59, 0.4);
71
+ border: 1px solid rgba(148, 163, 184, 0.1);
72
+ border-radius: 8px;
73
+ cursor: pointer;
74
+ transition: all 0.2s ease;
75
+ }
76
+
77
+ .workflow-list-item:hover {
78
+ background: rgba(30, 41, 59, 0.7);
79
+ border-color: rgba(16, 185, 129, 0.3);
80
+ }
81
+
82
+ .workflow-list-item.active {
83
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
84
+ border-color: var(--accent);
85
+ }
86
+
87
+ .workflow-item-info {
88
+ display: flex;
89
+ flex-direction: column;
90
+ gap: 4px;
91
+ min-width: 0;
92
+ flex: 1;
93
+ }
94
+
95
+ .workflow-item-name {
96
+ font-size: 0.9rem;
97
+ font-weight: 500;
98
+ color: #f1f5f9;
99
+ white-space: nowrap;
100
+ overflow: hidden;
101
+ text-overflow: ellipsis;
102
+ }
103
+
104
+ .workflow-item-desc {
105
+ font-size: 0.75rem;
106
+ color: #94a3b8;
107
+ }
108
+
109
+ .workflow-delete-btn {
110
+ background: transparent;
111
+ border: none;
112
+ color: #ef4444;
113
+ opacity: 0.6;
114
+ padding: 4px;
115
+ cursor: pointer;
116
+ border-radius: 4px;
117
+ display: flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ transition: all 0.2s ease;
121
+ }
122
+
123
+ .workflow-delete-btn:hover {
124
+ opacity: 1;
125
+ background: rgba(239, 68, 68, 0.1);
126
+ }
127
+
128
+ /* Canvas Panel */
129
+ .workflows-canvas-panel {
130
+ display: flex;
131
+ flex-direction: column;
132
+ height: 100%;
133
+ position: relative;
134
+ overflow: hidden;
135
+ }
136
+
137
+ .workflows-canvas-header {
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: space-between;
141
+ padding: 14px 20px;
142
+ background: rgba(15, 23, 42, 0.6);
143
+ border-bottom: 1px solid rgba(148, 163, 184, 0.1);
144
+ backdrop-filter: blur(12px);
145
+ z-index: 10;
146
+ }
147
+
148
+ .workflows-canvas-title {
149
+ display: flex;
150
+ align-items: center;
151
+ gap: 12px;
152
+ flex: 1;
153
+ min-width: 0;
154
+ }
155
+
156
+ .workflow-name-input {
157
+ background: transparent;
158
+ border: 1px solid transparent;
159
+ border-radius: 6px;
160
+ color: #f8fafc;
161
+ font-size: 1.1rem;
162
+ font-weight: 600;
163
+ padding: 4px 8px;
164
+ width: 260px;
165
+ transition: all 0.2s;
166
+ }
167
+
168
+ .workflow-name-input:hover {
169
+ background: rgba(255, 255, 255, 0.05);
170
+ }
171
+
172
+ .workflow-name-input:focus {
173
+ background: rgba(15, 23, 42, 0.6);
174
+ border-color: var(--accent);
175
+ outline: none;
176
+ }
177
+
178
+ .workflows-canvas-actions {
179
+ display: flex;
180
+ align-items: center;
181
+ gap: 10px;
182
+ }
183
+
184
+ /* React Flow Custom Nodes Styling */
185
+ .custom-node {
186
+ background: var(--panel-bg);
187
+ border: 1px solid var(--border);
188
+ border-radius: 12px;
189
+ padding: 14px;
190
+ color: var(--text-main);
191
+ width: 280px;
192
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
193
+ backdrop-filter: var(--glass-blur);
194
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
195
+ }
196
+
197
+ .custom-node:hover {
198
+ border-color: rgba(255, 255, 255, 0.4);
199
+ }
200
+
201
+ .custom-node.selected {
202
+ box-shadow: 0 0 15px color-mix(in srgb, var(--accent) 35%, transparent);
203
+ }
204
+
205
+ .custom-node.trigger-node {
206
+ border-left: 5px solid var(--accent);
207
+ }
208
+
209
+ .custom-node.action-node {
210
+ border-left: 5px solid #8b5cf6;
211
+ }
212
+
213
+ .node-header {
214
+ display: flex;
215
+ align-items: center;
216
+ gap: 8px;
217
+ margin-bottom: 12px;
218
+ border-bottom: 1px solid rgba(148, 163, 184, 0.1);
219
+ padding-bottom: 8px;
220
+ }
221
+
222
+ .node-header svg {
223
+ color: var(--accent);
224
+ }
225
+
226
+ .action-node .node-header svg {
227
+ color: #8b5cf6;
228
+ }
229
+
230
+ .node-title {
231
+ font-size: 0.95rem;
232
+ font-weight: 600;
233
+ color: #f8fafc;
234
+ }
235
+
236
+ .node-body {
237
+ display: flex;
238
+ flex-direction: column;
239
+ gap: 10px;
240
+ }
241
+
242
+ .node-body label {
243
+ font-size: 0.75rem;
244
+ color: #94a3b8;
245
+ font-weight: 500;
246
+ text-transform: uppercase;
247
+ letter-spacing: 0.05em;
248
+ }
249
+
250
+ .node-input,
251
+ .node-select {
252
+ background: var(--input-bg);
253
+ border: 1px solid var(--border);
254
+ border-radius: 6px;
255
+ padding: 8px 10px;
256
+ color: #f1f5f9;
257
+ font-size: 0.85rem;
258
+ width: 100%;
259
+ transition: border-color 0.2s;
260
+ }
261
+
262
+ .node-input:focus,
263
+ .node-select:focus {
264
+ border-color: var(--accent);
265
+ outline: none;
266
+ }
267
+
268
+ .action-node .node-input:focus,
269
+ .action-node .node-select:focus {
270
+ border-color: #8b5cf6;
271
+ }
272
+
273
+ .node-description {
274
+ font-size: 0.75rem;
275
+ color: #64748b;
276
+ line-height: 1.4;
277
+ }
278
+
279
+ /* React Flow Handles */
280
+ .react-flow__handle {
281
+ width: 10px !important;
282
+ height: 10px !important;
283
+ background: var(--accent) !important;
284
+ border: 2px solid var(--bg-color) !important;
285
+ }
286
+
287
+ .action-node .react-flow__handle {
288
+ background: #8b5cf6 !important;
289
+ }
290
+
291
+ .react-flow__edge-path {
292
+ stroke: var(--accent) !important;
293
+ stroke-width: 3px !important;
294
+ opacity: 0.8;
295
+ }
296
+
297
+ /* Empty Canvas State */
298
+ .workflows-empty-canvas {
299
+ display: flex;
300
+ flex-direction: column;
301
+ align-items: center;
302
+ justify-content: center;
303
+ height: 100%;
304
+ color: #64748b;
305
+ gap: 16px;
306
+ padding: 40px;
307
+ text-align: center;
308
+ }
309
+
310
+ .workflows-empty-canvas svg {
311
+ opacity: 0.2;
312
+ }
313
+
314
+ .workflows-empty-canvas h4 {
315
+ margin: 0;
316
+ color: #94a3b8;
317
+ font-size: 1.1rem;
318
+ }
319
+
320
+ .workflows-empty-canvas p {
321
+ margin: 0;
322
+ font-size: 0.85rem;
323
+ max-width: 320px;
324
+ line-height: 1.5;
325
+ }
326
+
327
+ /* Buttons style matching dashboard */
328
+ .btn-workflows-add {
329
+ display: flex;
330
+ align-items: center;
331
+ justify-content: center;
332
+ gap: 6px;
333
+ width: 100%;
334
+ padding: 10px;
335
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
336
+ border: 1px dashed color-mix(in srgb, var(--accent) 40%, transparent);
337
+ border-radius: 8px;
338
+ color: var(--accent);
339
+ font-size: 0.85rem;
340
+ font-weight: 500;
341
+ cursor: pointer;
342
+ transition: all 0.2s;
343
+ }
344
+
345
+ .btn-workflows-add:hover {
346
+ background: color-mix(in srgb, var(--accent) 20%, transparent);
347
+ border-color: var(--accent);
348
+ }
349
+
350
+ .btn-save-workflows {
351
+ background: var(--accent);
352
+ color: white;
353
+ border: none;
354
+ border-radius: 6px;
355
+ padding: 8px 16px;
356
+ font-size: 0.85rem;
357
+ font-weight: 500;
358
+ cursor: pointer;
359
+ transition: background 0.2s;
360
+ }
361
+
362
+ .btn-save-workflows:hover {
363
+ background: var(--accent-hover);
364
+ }
365
+
366
+ .btn-reload-rules {
367
+ background: rgba(30, 41, 59, 0.6);
368
+ border: 1px solid rgba(148, 163, 184, 0.2);
369
+ color: #cbd5e1;
370
+ border-radius: 6px;
371
+ padding: 8px 16px;
372
+ font-size: 0.85rem;
373
+ font-weight: 500;
374
+ cursor: pointer;
375
+ transition: all 0.2s;
376
+ }
377
+
378
+ .btn-reload-rules:hover {
379
+ background: color-mix(in srgb, var(--accent) 10%, transparent);
380
+ border-color: var(--accent);
381
+ color: #f8fafc;
382
+ }
383
+
384
+ /* Node field hints */
385
+ .field-hint {
386
+ font-size: 0.72rem;
387
+ color: #94a3b8;
388
+ opacity: 0.85;
389
+ margin-top: 4px;
390
+ line-height: 1.4;
391
+ }
392
+
393
+ .field-hint code {
394
+ background: rgba(15, 23, 42, 0.6);
395
+ padding: 1px 4px;
396
+ border-radius: 4px;
397
+ font-family: monospace;
398
+ color: var(--accent);
399
+ }
400
+
401
+ /* Help Panel toggle */
402
+ .btn-help-toggle {
403
+ background: rgba(30, 41, 59, 0.6);
404
+ border: 1px solid rgba(148, 163, 184, 0.2);
405
+ color: #cbd5e1;
406
+ border-radius: 6px;
407
+ padding: 8px 12px;
408
+ font-size: 0.85rem;
409
+ font-weight: 500;
410
+ cursor: pointer;
411
+ display: flex;
412
+ align-items: center;
413
+ gap: 6px;
414
+ transition: all 0.2s;
415
+ }
416
+
417
+ .btn-help-toggle:hover {
418
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
419
+ border-color: var(--accent);
420
+ color: var(--accent);
421
+ }
422
+
423
+ /* Side by Side layout wrapper */
424
+ .workflows-canvas-wrapper {
425
+ display: flex;
426
+ flex: 1;
427
+ height: 100%;
428
+ position: relative;
429
+ overflow: hidden;
430
+ }
431
+
432
+ /* Sliding Help/Guide Panel */
433
+ .workflow-help-panel {
434
+ width: 320px;
435
+ background: var(--panel-bg);
436
+ border-left: 1px solid rgba(148, 163, 184, 0.12);
437
+ display: flex;
438
+ flex-direction: column;
439
+ height: 100%;
440
+ backdrop-filter: var(--glass-blur);
441
+ z-index: 10;
442
+ box-shadow: -8px 0 32px rgba(0, 0, 0, 0.5);
443
+ animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
444
+ }
445
+
446
+ @keyframes slideIn {
447
+ from {
448
+ transform: translateX(100%);
449
+ }
450
+ to {
451
+ transform: translateX(0);
452
+ }
453
+ }
454
+
455
+ .workflow-help-header {
456
+ padding: 16px 20px;
457
+ border-bottom: 1px solid rgba(148, 163, 184, 0.1);
458
+ display: flex;
459
+ align-items: center;
460
+ justify-content: space-between;
461
+ }
462
+
463
+ .workflow-help-header h3 {
464
+ margin: 0;
465
+ font-size: 1rem;
466
+ color: var(--accent);
467
+ display: flex;
468
+ align-items: center;
469
+ gap: 8px;
470
+ }
471
+
472
+ .workflow-help-close-btn {
473
+ background: transparent;
474
+ border: none;
475
+ color: #94a3b8;
476
+ cursor: pointer;
477
+ padding: 4px;
478
+ display: flex;
479
+ align-items: center;
480
+ border-radius: 4px;
481
+ }
482
+
483
+ .workflow-help-close-btn:hover {
484
+ background: rgba(255, 255, 255, 0.05);
485
+ color: #f1f5f9;
486
+ }
487
+
488
+ .workflow-help-body {
489
+ flex: 1;
490
+ overflow-y: auto;
491
+ padding: 20px;
492
+ display: flex;
493
+ flex-direction: column;
494
+ gap: 16px;
495
+ }
496
+
497
+ .help-section {
498
+ display: flex;
499
+ flex-direction: column;
500
+ gap: 8px;
501
+ }
502
+
503
+ .help-section h4 {
504
+ margin: 0;
505
+ font-size: 0.88rem;
506
+ color: #f1f5f9;
507
+ font-weight: 600;
508
+ border-left: 3px solid var(--accent);
509
+ padding-left: 8px;
510
+ }
511
+
512
+ .help-section p {
513
+ margin: 0;
514
+ font-size: 0.8rem;
515
+ color: #94a3b8;
516
+ line-height: 1.5;
517
+ }
518
+
519
+ .help-steps {
520
+ margin: 0;
521
+ padding-left: 20px;
522
+ font-size: 0.8rem;
523
+ color: #94a3b8;
524
+ display: flex;
525
+ flex-direction: column;
526
+ gap: 8px;
527
+ }
528
+
529
+ .help-steps li {
530
+ line-height: 1.5;
531
+ }
532
+
533
+ .help-steps strong {
534
+ color: #f1f5f9;
535
+ }
536
+
537
+ .help-steps code {
538
+ background: rgba(255, 255, 255, 0.06);
539
+ padding: 1px 4px;
540
+ border-radius: 4px;
541
+ color: var(--accent);
542
+ }
@@ -16,6 +16,7 @@ interface Window {
16
16
  getChatHistory: () => Promise<any>;
17
17
  listSavedPictures: () => Promise<any>;
18
18
  openSettings: () => Promise<any>;
19
+ openWorkflows: () => Promise<any>;
19
20
  readClipboard: () => Promise<string>;
20
21
  writeClipboard: (text: string) => Promise<void>;
21
22
  getSystemInfo: () => Promise<any>;
@@ -51,6 +52,7 @@ interface Window {
51
52
  openFolder: (path: string) => Promise<void>;
52
53
  openCustomWorkflows: () => Promise<any>;
53
54
  reloadCustomWorkflows: () => Promise<any>;
55
+ saveCustomWorkflows: (workflows: any[]) => Promise<any>;
54
56
  };
55
57
  spotlightAPI: {
56
58
  submit: (query: string) => void;
@@ -3,6 +3,7 @@
3
3
  @import './css/spotlight.css';
4
4
  @import './css/widget.css';
5
5
  @import './css/image-studio.css';
6
+ @import './css/workflows.css';
6
7
 
7
8
  * {
8
9
  box-sizing: border-box;
@@ -282,6 +282,30 @@ export async function getRecentInteractions(limit = 50, chatId?: string | null):
282
282
  return invoke<InteractionMemory[]>('get_recent_interactions', { limit, chatId })
283
283
  }
284
284
 
285
+ export async function saveSystemInteraction(
286
+ chatId: string,
287
+ userText: string,
288
+ provider: string,
289
+ model: string,
290
+ ): Promise<any> {
291
+ if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
292
+ const API_BASE = "http://localhost:3000/api";
293
+ try {
294
+ const res = await fetch(`${API_BASE}/interactions`, {
295
+ method: 'POST',
296
+ headers: { 'Content-Type': 'application/json' },
297
+ body: JSON.stringify({ chatId, userText, provider, model }),
298
+ });
299
+ return await res.json();
300
+ } catch (e) {
301
+ console.error("Failed to save system interaction on local server:", e);
302
+ return { success: false };
303
+ }
304
+ }
305
+ const { invoke } = await import('@tauri-apps/api/core')
306
+ return invoke('save_system_interaction', { chatId, userText, provider, model })
307
+ }
308
+
285
309
  export async function saveInteractionAgentActivity(
286
310
  interactionId: number,
287
311
  activity: AgentProgress[],
@@ -396,6 +420,71 @@ export async function setProfileValue(key: string, value: string): Promise<boole
396
420
  return invoke<void>('set_profile_value', { key, value }).then(() => true).catch(() => false)
397
421
  }
398
422
 
423
+ export interface LearnedSkill {
424
+ id: number
425
+ name: string
426
+ sourcePath: string
427
+ content: string
428
+ updatedAt: string
429
+ }
430
+
431
+ export async function listLearnedSkills(): Promise<LearnedSkill[]> {
432
+ if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
433
+ try {
434
+ const val = await getProfileValue('learned-skills-web-mock')
435
+ if (val) {
436
+ return JSON.parse(val)
437
+ }
438
+ return []
439
+ } catch (e) {
440
+ console.error("Failed to load web mock skills:", e)
441
+ return []
442
+ }
443
+ }
444
+ const { invoke } = await import('@tauri-apps/api/core')
445
+ return invoke<LearnedSkill[]>('list_learned_skills')
446
+ }
447
+
448
+ export async function addLearnedSkill(name: string, content: string): Promise<LearnedSkill> {
449
+ if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
450
+ try {
451
+ const list = await listLearnedSkills()
452
+ const newSkill: LearnedSkill = {
453
+ id: Date.now(),
454
+ name,
455
+ sourcePath: 'ui_manual',
456
+ content,
457
+ updatedAt: new Date().toISOString()
458
+ }
459
+ list.push(newSkill)
460
+ await setProfileValue('learned-skills-web-mock', JSON.stringify(list))
461
+ return newSkill
462
+ } catch (e) {
463
+ console.error("Failed to add web mock skill:", e)
464
+ throw e
465
+ }
466
+ }
467
+ const { invoke } = await import('@tauri-apps/api/core')
468
+ return invoke<LearnedSkill>('add_learned_skill', { name, content })
469
+ }
470
+
471
+ export async function deleteLearnedSkill(name: string): Promise<number> {
472
+ if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
473
+ try {
474
+ const list = await listLearnedSkills()
475
+ const filtered = list.filter(s => s.name !== name)
476
+ const deletedCount = list.length - filtered.length
477
+ await setProfileValue('learned-skills-web-mock', JSON.stringify(filtered))
478
+ return deletedCount
479
+ } catch (e) {
480
+ console.error("Failed to delete web mock skill:", e)
481
+ return 0
482
+ }
483
+ }
484
+ const { invoke } = await import('@tauri-apps/api/core')
485
+ return invoke<number>('delete_learned_skill', { name })
486
+ }
487
+
399
488
  export async function clearChatHistory(chatId?: string | null): Promise<number> {
400
489
  if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
401
490
  const API_BASE = "http://localhost:3000/api";
@@ -889,6 +978,10 @@ export function installTauriAdapters() {
889
978
  const { invoke } = await import('@tauri-apps/api/core')
890
979
  return invoke('reload_custom_workflows')
891
980
  },
981
+ saveCustomWorkflows: async (workflows) => {
982
+ const { invoke } = await import('@tauri-apps/api/core')
983
+ return invoke('save_custom_workflows', { workflows })
984
+ },
892
985
  }
893
986
 
894
987
  window.spotlightAPI = {
@@ -1015,6 +1108,10 @@ export function installTauriAdapters() {
1015
1108
  const { invoke } = await import('@tauri-apps/api/core')
1016
1109
  return invoke('open_window', { kind: 'settings' })
1017
1110
  },
1111
+ openWorkflows: async () => {
1112
+ const { invoke } = await import('@tauri-apps/api/core')
1113
+ return invoke('open_window', { kind: 'workflows' })
1114
+ },
1018
1115
  readClipboard: () => navigator.clipboard.readText(),
1019
1116
  writeClipboard: (text) => navigator.clipboard.writeText(text),
1020
1117
  getSystemInfo: async () => ({ backend: 'rust' }),