@pheem49/mint 1.7.1 → 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 (51) hide show
  1. package/Cargo.lock +3 -3
  2. package/Cargo.toml +1 -1
  3. package/README.md +9 -0
  4. package/crates/mint-core/src/api_server.rs +62 -4
  5. package/crates/mint-core/src/chat.rs +79 -0
  6. package/crates/mint-core/src/lib.rs +1 -1
  7. package/crates/mint-core/src/workflows.rs +8 -0
  8. package/package.json +2 -1
  9. package/src/renderer/src/App.tsx +1 -1
  10. package/src/renderer/src/components/ChatPanel.tsx +27 -14
  11. package/src/renderer/src/components/DashboardSidebar.tsx +71 -12
  12. package/src/renderer/src/components/ImageStudioPanel.tsx +7 -3
  13. package/src/renderer/src/components/MintDashboard.tsx +199 -5
  14. package/src/renderer/src/components/PicturesLibrary.tsx +1 -3
  15. package/src/renderer/src/components/Settings/AudioTab.tsx +97 -0
  16. package/src/renderer/src/components/Settings/AutomationTab.tsx +195 -0
  17. package/src/renderer/src/components/Settings/GeneralTab.tsx +950 -0
  18. package/src/renderer/src/components/Settings/MemoryTab.tsx +60 -0
  19. package/src/renderer/src/components/Settings/PluginsTab.tsx +452 -0
  20. package/src/renderer/src/components/Settings/ShortcutsTab.tsx +30 -0
  21. package/src/renderer/src/components/Settings/ThemeTab.tsx +129 -0
  22. package/src/renderer/src/components/SettingsWindow.tsx +175 -962
  23. package/src/renderer/src/components/WorkflowBuilderPanel.tsx +536 -0
  24. package/src/renderer/src/components/WorkflowsWindow.tsx +46 -0
  25. package/src/renderer/src/css/image-studio.css +9 -4
  26. package/src/renderer/src/css/settings.css +75 -6
  27. package/src/renderer/src/css/styles.css +355 -38
  28. package/src/renderer/src/css/workflows.css +542 -0
  29. package/src/renderer/src/env.d.ts +2 -0
  30. package/src/renderer/src/index.css +1 -0
  31. package/src/renderer/src/tauri.ts +97 -0
  32. package/src/renderer/src-web/components/ChatPanel.tsx +27 -14
  33. package/src/renderer/src-web/components/DashboardSidebar.tsx +17 -1
  34. package/src/renderer/src-web/components/ImageStudioPanel.tsx +7 -3
  35. package/src/renderer/src-web/components/MintDashboard.tsx +189 -0
  36. package/src/renderer/src-web/components/PicturesLibrary.tsx +1 -3
  37. package/src/renderer/src-web/components/Settings/AudioTab.tsx +97 -0
  38. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +196 -0
  39. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +948 -0
  40. package/src/renderer/src-web/components/Settings/MemoryTab.tsx +60 -0
  41. package/src/renderer/src-web/components/Settings/PluginsTab.tsx +452 -0
  42. package/src/renderer/src-web/components/Settings/ShortcutsTab.tsx +30 -0
  43. package/src/renderer/src-web/components/Settings/ThemeTab.tsx +129 -0
  44. package/src/renderer/src-web/components/SettingsWindow.tsx +187 -968
  45. package/src/renderer/src-web/css/image-studio.css +9 -4
  46. package/src/renderer/src-web/css/settings.css +75 -6
  47. package/src/renderer/src-web/css/styles.css +294 -38
  48. package/src/renderer/src-web/env.d.ts +2 -0
  49. package/src/renderer/src-web/tauri.ts +36 -0
  50. package/src-tauri/src/lib.rs +83 -90
  51. package/src-tauri/tauri.conf.json +1 -1
@@ -0,0 +1,536 @@
1
+ import React, { useState, useEffect, useCallback, useMemo } from 'react'
2
+ import {
3
+ ReactFlow,
4
+ MiniMap,
5
+ Controls,
6
+ Background,
7
+ useNodesState,
8
+ useEdgesState,
9
+ addEdge,
10
+ type Node,
11
+ type Edge,
12
+ Position,
13
+ Handle
14
+ } from '@xyflow/react'
15
+ import '@xyflow/react/dist/style.css'
16
+ import type { DashboardView } from './DashboardSidebar'
17
+
18
+ interface WorkflowBuilderPanelProps {
19
+ view: DashboardView
20
+ onShowToast?: (message: string) => void
21
+ }
22
+
23
+ interface WorkflowTrigger {
24
+ type: string
25
+ processName: string
26
+ }
27
+
28
+ interface WorkflowAction {
29
+ type: string
30
+ message: string
31
+ target: string
32
+ }
33
+
34
+ interface Workflow {
35
+ id: string
36
+ name: string
37
+ trigger: WorkflowTrigger
38
+ action: WorkflowAction
39
+ }
40
+
41
+ /* Custom Node: Trigger Node */
42
+ interface TriggerNodeData {
43
+ processName: string
44
+ onChangeProcessName: (value: string) => void
45
+ }
46
+
47
+ function TriggerNode({ data }: { data: TriggerNodeData }) {
48
+ return (
49
+ <div className="custom-node trigger-node">
50
+ <div className="node-header">
51
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
52
+ <circle cx="12" cy="12" r="10"></circle>
53
+ <line x1="12" y1="8" x2="12" y2="12"></line>
54
+ <line x1="12" y1="16" x2="12.01" y2="16"></line>
55
+ </svg>
56
+ <span className="node-title">Trigger: Process Running</span>
57
+ </div>
58
+ <div className="node-body">
59
+ <label>Process Name</label>
60
+ <input
61
+ type="text"
62
+ className="node-input"
63
+ value={data.processName || ''}
64
+ onChange={(e) => data.onChangeProcessName(e.target.value)}
65
+ placeholder="e.g., code, zoom, chrome, steam, figma"
66
+ />
67
+ <span className="field-hint">
68
+ 💡 Enter the process executable name <strong>without the .exe</strong> (e.g., <code>code</code> for VS Code, <code>zoom</code>, <code>figma</code>, <code>spotify</code>).
69
+ </span>
70
+ </div>
71
+ <Handle type="source" position={Position.Right} id="trigger-out" />
72
+ </div>
73
+ )
74
+ }
75
+
76
+ /* Custom Node: Action Node */
77
+ interface ActionNodeData {
78
+ type: string
79
+ message: string
80
+ target: string
81
+ onChangeType: (value: string) => void
82
+ onChangeMessage: (value: string) => void
83
+ onChangeTarget: (value: string) => void
84
+ }
85
+
86
+ function ActionNode({ data }: { data: ActionNodeData }) {
87
+ const getTargetLabel = (type: string) => {
88
+ switch (type) {
89
+ case 'open_app': return 'App Name'
90
+ case 'open_url': return 'Web URL'
91
+ case 'run_command': return 'Shell Command'
92
+ default: return 'Target'
93
+ }
94
+ }
95
+
96
+ const getTargetPlaceholder = (type: string) => {
97
+ switch (type) {
98
+ case 'open_app': return 'e.g., spotify, slack, discord'
99
+ case 'open_url': return 'e.g., https://github.com'
100
+ case 'run_command': return 'e.g., npm run dev'
101
+ default: return ''
102
+ }
103
+ }
104
+
105
+ const getTargetHint = (type: string) => {
106
+ switch (type) {
107
+ case 'open_app': return <span>💡 Enter the application name, e.g., <code>spotify</code> or <code>discord</code>.</span>
108
+ case 'open_url': return <span>💡 Enter the target website URL, e.g., <code>https://google.com</code>.</span>
109
+ case 'run_command': return <span>💡 Enter a custom terminal command, e.g., <code>git pull</code>.</span>
110
+ default: return null
111
+ }
112
+ }
113
+
114
+ return (
115
+ <div className="custom-node action-node">
116
+ <Handle type="target" position={Position.Left} id="action-in" />
117
+ <div className="node-header">
118
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
119
+ <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
120
+ </svg>
121
+ <span className="node-title">Action: Proactive Assistant Action</span>
122
+ </div>
123
+ <div className="node-body">
124
+ <label>Action Type</label>
125
+ <select
126
+ className="node-select"
127
+ value={data.type || 'system_info'}
128
+ onChange={(e) => data.onChangeType(e.target.value)}
129
+ >
130
+ <option value="system_info">System Info</option>
131
+ <option value="open_app">Open Application</option>
132
+ <option value="open_url">Open URL / Website</option>
133
+ </select>
134
+
135
+ <label>Interactive Prompt</label>
136
+ <input
137
+ type="text"
138
+ className="node-input"
139
+ value={data.message || ''}
140
+ onChange={(e) => data.onChangeMessage(e.target.value)}
141
+ placeholder="e.g., Zoom is running. Do you want me to check system resources?"
142
+ />
143
+ <span className="field-hint">
144
+ 💡 The text the assistant will show as a pop-up on your desktop. We recommend ending with a question.
145
+ </span>
146
+
147
+ {data.type !== 'system_info' && (
148
+ <>
149
+ <label style={{ marginTop: '8px', display: 'block' }}>{getTargetLabel(data.type)}</label>
150
+ <input
151
+ type="text"
152
+ className="node-input"
153
+ value={data.target || ''}
154
+ onChange={(e) => data.onChangeTarget(e.target.value)}
155
+ placeholder={getTargetPlaceholder(data.type)}
156
+ />
157
+ <span className="field-hint">
158
+ {getTargetHint(data.type)}
159
+ </span>
160
+ </>
161
+ )}
162
+ </div>
163
+ </div>
164
+ )
165
+ }
166
+
167
+ export default function WorkflowBuilderPanel({ view, onShowToast }: WorkflowBuilderPanelProps) {
168
+ const [workflows, setWorkflows] = useState<Workflow[]>([])
169
+ const [selectedWorkflowId, setSelectedWorkflowId] = useState<string | null>(null)
170
+ const [showHelp, setShowHelp] = useState(true)
171
+ const [accentColor, setAccentColor] = useState('#10b981')
172
+
173
+ useEffect(() => {
174
+ const style = getComputedStyle(document.documentElement)
175
+ const accent = style.getPropertyValue('--accent').trim()
176
+ if (accent) {
177
+ setAccentColor(accent)
178
+ }
179
+ }, [view])
180
+
181
+ const [nodes, setNodes, onNodesChange] = useNodesState([])
182
+ const [edges, setEdges, onEdgesChange] = useEdgesState([])
183
+
184
+ const nodeTypes = useMemo(() => ({
185
+ triggerNode: TriggerNode,
186
+ actionNode: ActionNode
187
+ }), [])
188
+
189
+ // Load workflows on mount
190
+ const loadWorkflows = async () => {
191
+ try {
192
+ if (window.settingsApi) {
193
+ const res = await window.settingsApi.reloadCustomWorkflows()
194
+ if (res && res.workflows) {
195
+ setWorkflows(res.workflows)
196
+ if (res.workflows.length > 0 && !selectedWorkflowId) {
197
+ setSelectedWorkflowId(res.workflows[0].id)
198
+ }
199
+ }
200
+ }
201
+ } catch (e) {
202
+ console.error("Failed to load workflows:", e)
203
+ onShowToast?.("Failed to load workflows ⚠️")
204
+ }
205
+ }
206
+
207
+ useEffect(() => {
208
+ if (view === 'workflows') {
209
+ loadWorkflows()
210
+ }
211
+ }, [view])
212
+
213
+ const selectedWorkflow = useMemo(() => {
214
+ return workflows.find(w => w.id === selectedWorkflowId) || null
215
+ }, [workflows, selectedWorkflowId])
216
+
217
+ // Update backend workflows file
218
+ const handleSave = async () => {
219
+ try {
220
+ if (window.settingsApi) {
221
+ const res = await window.settingsApi.saveCustomWorkflows(workflows)
222
+ if (res && res.success) {
223
+ onShowToast?.("Workflows saved successfully! 💾")
224
+ // Reload rules in backend monitor
225
+ await window.settingsApi.reloadCustomWorkflows()
226
+ } else {
227
+ onShowToast?.("Failed to save workflows ⚠️")
228
+ }
229
+ }
230
+ } catch (e) {
231
+ console.error("Failed to save workflows:", e)
232
+ onShowToast?.("Error saving workflows ⚠️")
233
+ }
234
+ }
235
+
236
+ const handleReloadRules = async () => {
237
+ try {
238
+ if (window.settingsApi) {
239
+ const res = await window.settingsApi.reloadCustomWorkflows()
240
+ if (res && res.success) {
241
+ onShowToast?.("Rules reloaded & active! 🔄")
242
+ } else {
243
+ onShowToast?.("Failed to reload rules ⚠️")
244
+ }
245
+ }
246
+ } catch (e) {
247
+ console.error("Failed to reload rules:", e)
248
+ onShowToast?.("Error reloaded rules ⚠️")
249
+ }
250
+ }
251
+
252
+ // Handle updates to node data
253
+ const updateSelectedWorkflow = useCallback((updater: (w: Workflow) => Workflow) => {
254
+ if (!selectedWorkflowId) return
255
+ setWorkflows(current => current.map(w => {
256
+ if (w.id === selectedWorkflowId) {
257
+ return updater(w)
258
+ }
259
+ return w
260
+ }))
261
+ }, [selectedWorkflowId])
262
+
263
+ // Map selected workflow to node/edge graph
264
+ useEffect(() => {
265
+ if (!selectedWorkflow) {
266
+ setNodes([])
267
+ setEdges([])
268
+ return
269
+ }
270
+
271
+ const tNode: Node = {
272
+ id: 'node-trigger',
273
+ type: 'triggerNode',
274
+ position: { x: 60, y: 140 },
275
+ data: {
276
+ processName: selectedWorkflow.trigger.processName,
277
+ onChangeProcessName: (val: string) => {
278
+ updateSelectedWorkflow(w => ({
279
+ ...w,
280
+ trigger: { ...w.trigger, processName: val }
281
+ }))
282
+ }
283
+ }
284
+ }
285
+
286
+ const aNode: Node = {
287
+ id: 'node-action',
288
+ type: 'actionNode',
289
+ position: { x: 420, y: 100 },
290
+ data: {
291
+ type: selectedWorkflow.action.type,
292
+ message: selectedWorkflow.action.message,
293
+ target: selectedWorkflow.action.target,
294
+ onChangeType: (val: string) => {
295
+ updateSelectedWorkflow(w => ({
296
+ ...w,
297
+ action: { ...w.action, type: val }
298
+ }))
299
+ },
300
+ onChangeMessage: (val: string) => {
301
+ updateSelectedWorkflow(w => ({
302
+ ...w,
303
+ action: { ...w.action, message: val }
304
+ }))
305
+ },
306
+ onChangeTarget: (val: string) => {
307
+ updateSelectedWorkflow(w => ({
308
+ ...w,
309
+ action: { ...w.action, target: val }
310
+ }))
311
+ }
312
+ }
313
+ }
314
+
315
+ const connectionEdge: Edge = {
316
+ id: 'edge-main',
317
+ source: 'node-trigger',
318
+ target: 'node-action',
319
+ sourceHandle: 'trigger-out',
320
+ targetHandle: 'action-in',
321
+ animated: true
322
+ }
323
+
324
+ setNodes([tNode, aNode])
325
+ setEdges([connectionEdge])
326
+ }, [selectedWorkflow, updateSelectedWorkflow])
327
+
328
+ const handleAddNewWorkflow = () => {
329
+ const nextId = `wf-${Date.now()}`
330
+ const newWf: Workflow = {
331
+ id: nextId,
332
+ name: `Launch music when coding (Example)`,
333
+ trigger: {
334
+ type: 'process_running',
335
+ processName: 'code'
336
+ },
337
+ action: {
338
+ type: 'open_app',
339
+ message: 'You started coding. Do you want me to launch Spotify? 🎵',
340
+ target: 'spotify'
341
+ }
342
+ }
343
+
344
+ setWorkflows(current => [...current, newWf])
345
+ setSelectedWorkflowId(nextId)
346
+ onShowToast?.("New workflow created ➕")
347
+ }
348
+
349
+ const handleDeleteWorkflow = (id: string, e: React.MouseEvent) => {
350
+ e.stopPropagation()
351
+ if (confirm("Are you sure you want to delete this workflow?")) {
352
+ setWorkflows(current => current.filter(w => w.id !== id))
353
+ if (selectedWorkflowId === id) {
354
+ const remaining = workflows.filter(w => w.id !== id)
355
+ setSelectedWorkflowId(remaining.length > 0 ? remaining[0].id : null)
356
+ }
357
+ onShowToast?.("Workflow deleted 🗑️")
358
+ }
359
+ }
360
+
361
+ return (
362
+ <section className={`workflows-builder-container ${view === 'workflows' ? 'is-visible' : ''}`} aria-hidden={view !== 'workflows'}>
363
+ {/* Sidebar - Master List */}
364
+ <div className="workflows-sidebar">
365
+ <div className="workflows-sidebar-header">
366
+ <h3>Workflow (Beta)</h3>
367
+ <button className="btn-workflows-add" onClick={handleAddNewWorkflow}>
368
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
369
+ <line x1="12" y1="5" x2="12" y2="19"></line>
370
+ <line x1="5" y1="12" x2="19" y2="12"></line>
371
+ </svg>
372
+ Add Workflow
373
+ </button>
374
+ </div>
375
+ <div className="workflows-list">
376
+ {workflows.map(wf => (
377
+ <div
378
+ key={wf.id}
379
+ className={`workflow-list-item ${selectedWorkflowId === wf.id ? 'active' : ''}`}
380
+ onClick={() => setSelectedWorkflowId(wf.id)}
381
+ >
382
+ <div className="workflow-item-info">
383
+ <span className="workflow-item-name">{wf.name || 'Unnamed Workflow'}</span>
384
+ <span className="workflow-item-desc">
385
+ {wf.trigger.processName ? `Trigger: ${wf.trigger.processName}` : 'No trigger set'}
386
+ </span>
387
+ </div>
388
+ <button
389
+ className="workflow-delete-btn"
390
+ title="Delete workflow"
391
+ onClick={(e) => handleDeleteWorkflow(wf.id, e)}
392
+ >
393
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
394
+ <polyline points="3 6 5 6 21 6"></polyline>
395
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
396
+ </svg>
397
+ </button>
398
+ </div>
399
+ ))}
400
+ </div>
401
+ </div>
402
+
403
+ {/* Editor Canvas */}
404
+ <div className="workflows-canvas-panel">
405
+ {selectedWorkflow ? (
406
+ <>
407
+ <header className="workflows-canvas-header">
408
+ <div className="workflows-canvas-title">
409
+ <input
410
+ type="text"
411
+ className="workflow-name-input"
412
+ value={selectedWorkflow.name || ''}
413
+ onChange={(e) => {
414
+ const name = e.target.value
415
+ updateSelectedWorkflow(w => ({ ...w, name }))
416
+ }}
417
+ placeholder="Workflow Name"
418
+ />
419
+ </div>
420
+ <div className="workflows-canvas-actions">
421
+ <button className="btn-help-toggle" onClick={() => setShowHelp(!showHelp)}>
422
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
423
+ <circle cx="12" cy="12" r="10"></circle>
424
+ <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
425
+ <line x1="12" y1="17" x2="12.01" y2="17"></line>
426
+ </svg>
427
+ {showHelp ? "Close Guide" : "Quick Guide 💡"}
428
+ </button>
429
+ <button className="btn-reload-rules" onClick={handleReloadRules}>
430
+ Reload Rules
431
+ </button>
432
+ <button className="btn-save-workflows" onClick={handleSave}>
433
+ Save Workflows
434
+ </button>
435
+ </div>
436
+ </header>
437
+
438
+ <div className="workflows-canvas-wrapper">
439
+ <div style={{ flex: 1, position: 'relative' }}>
440
+ <ReactFlow
441
+ nodes={nodes}
442
+ edges={edges}
443
+ onNodesChange={onNodesChange}
444
+ onEdgesChange={onEdgesChange}
445
+ nodeTypes={nodeTypes}
446
+ fitView
447
+ fitViewOptions={{ padding: 0.3 }}
448
+ minZoom={0.5}
449
+ maxZoom={1.5}
450
+ nodesDraggable={true}
451
+ elementsSelectable={true}
452
+ >
453
+ <Background color="#334155" gap={16} size={1} />
454
+ <Controls showInteractive={false} />
455
+ <MiniMap
456
+ style={{
457
+ background: 'rgba(15, 23, 42, 0.9)',
458
+ border: '1px solid rgba(148, 163, 184, 0.2)',
459
+ borderRadius: '8px'
460
+ }}
461
+ maskColor="rgba(30, 41, 59, 0.4)"
462
+ nodeColor={(n) => {
463
+ if (n.type === 'triggerNode') return accentColor
464
+ if (n.type === 'actionNode') return '#8b5cf6'
465
+ return '#334155'
466
+ }}
467
+ />
468
+ </ReactFlow>
469
+ </div>
470
+
471
+ {showHelp && (
472
+ <div className="workflow-help-panel">
473
+ <div className="workflow-help-header">
474
+ <h3>
475
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
476
+ <circle cx="12" cy="12" r="10"></circle>
477
+ <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
478
+ <line x1="12" y1="17" x2="12.01" y2="17"></line>
479
+ </svg>
480
+ Workflow Quick Guide
481
+ </h3>
482
+ <button className="workflow-help-close-btn" onClick={() => setShowHelp(false)} title="Close">
483
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
484
+ <line x1="18" y1="6" x2="6" y2="18"></line>
485
+ <line x1="6" y1="6" x2="18" y2="18"></line>
486
+ </svg>
487
+ </button>
488
+ </div>
489
+ <div className="workflow-help-body">
490
+ <div className="help-section">
491
+ <h4>What is a Workflow?</h4>
492
+ <p>
493
+ Workflows let you automate tasks. When a monitored application starts (like Zoom or VS Code), your assistant will pop up on your desktop proposing a proactive action like launching another app, opening a website, or checking system specs.
494
+ </p>
495
+ </div>
496
+ <div className="help-section">
497
+ <h4>How to set up:</h4>
498
+ <ol className="help-steps">
499
+ <li>Click <strong>+ Add Workflow</strong> in the left sidebar.</li>
500
+ <li>In the green Trigger node 🟢:
501
+ <p style={{ margin: '4px 0 0 0', fontSize: '0.75rem', color: '#cbd5e1' }}>
502
+ Enter the executable name, e.g., <code>code</code>, <code>zoom</code>, <code>chrome</code> (do NOT include paths or .exe).
503
+ </p>
504
+ </li>
505
+ <li>In the purple Action node 🟣:
506
+ <p style={{ margin: '4px 0 0 0', fontSize: '0.75rem', color: '#cbd5e1' }}>
507
+ Choose your Action Type ➡️ Write the popup question in Interactive Prompt ➡️ Specify the target app name or URL.
508
+ </p>
509
+ </li>
510
+ <li>Click <strong>Save Workflows</strong> at the top-right to save.</li>
511
+ <li>Click <strong>Reload Rules</strong> to apply the rules immediately!</li>
512
+ </ol>
513
+ </div>
514
+ </div>
515
+ </div>
516
+ )}
517
+ </div>
518
+ </>
519
+ ) : (
520
+ <div className="workflows-empty-canvas">
521
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
522
+ <path d="M4 12h16"></path>
523
+ <path d="M12 4v16"></path>
524
+ <rect x="2" y="9" width="4" height="6" rx="1"></rect>
525
+ <rect x="18" y="9" width="4" height="6" rx="1"></rect>
526
+ <rect x="10" y="2" width="4" height="4" rx="1"></rect>
527
+ <rect x="10" y="18" width="4" height="4" rx="1"></rect>
528
+ </svg>
529
+ <h4>No Workflow Selected</h4>
530
+ <p>Select a workflow from the list or add a new one to begin editing visually.</p>
531
+ </div>
532
+ )}
533
+ </div>
534
+ </section>
535
+ )
536
+ }
@@ -0,0 +1,46 @@
1
+ import React from 'react'
2
+ import WorkflowBuilderPanel from './WorkflowBuilderPanel'
3
+
4
+ export default function WorkflowsWindow() {
5
+ const handleClose = () => {
6
+ window.settingsApi?.closeSettings()
7
+ }
8
+
9
+ const showToast = (msg: string) => {
10
+ console.log("workflows toast:", msg)
11
+ }
12
+
13
+ return (
14
+ <div className="settings-container" style={{ display: 'flex', flexDirection: 'column', height: '100vh', overflow: 'hidden' }}>
15
+ <header className="settings-header drag-region">
16
+ <div className="header-left">
17
+ <span className="settings-icon" style={{ display: 'inline-flex', alignItems: 'center' }}>
18
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
19
+ <path d="M4 12h16"></path>
20
+ <path d="M12 4v16"></path>
21
+ <rect x="2" y="9" width="4" height="6" rx="1"></rect>
22
+ <rect x="18" y="9" width="4" height="6" rx="1"></rect>
23
+ <rect x="10" y="2" width="4" height="4" rx="1"></rect>
24
+ <rect x="10" y="18" width="4" height="4" rx="1"></rect>
25
+ </svg>
26
+ </span>
27
+ <div>
28
+ <h1>Workflow Builder</h1>
29
+ <p>Design visual automated workflows for your Mint assistant.</p>
30
+ </div>
31
+ </div>
32
+ <button className="close-btn" onClick={handleClose} aria-label="Close">
33
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
34
+ stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
35
+ <line x1="18" y1="6" x2="6" y2="18"></line>
36
+ <line x1="6" y1="6" x2="18" y2="18"></line>
37
+ </svg>
38
+ </button>
39
+ </header>
40
+
41
+ <main style={{ flex: 1, position: 'relative', overflow: 'hidden', background: '#090d16' }}>
42
+ <WorkflowBuilderPanel view="workflows" onShowToast={showToast} />
43
+ </main>
44
+ </div>
45
+ )
46
+ }
@@ -4,7 +4,7 @@
4
4
 
5
5
  /* Root panel: overlays the main content area, aligned with sidebar */
6
6
  .img-studio {
7
- display: none;
7
+ display: flex;
8
8
  position: absolute;
9
9
  top: 0;
10
10
  bottom: 0;
@@ -14,7 +14,10 @@
14
14
  overflow: hidden;
15
15
  background: var(--bg-gradient, radial-gradient(circle at top left, #064e3b, #111827 52%, #0b1020));
16
16
  z-index: 10;
17
- transition: left 0.22s ease;
17
+ opacity: 0;
18
+ pointer-events: none;
19
+ transform: translateY(12px) scale(0.995);
20
+ 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
21
  }
19
22
 
20
23
  /* Adjust left position when the sidebar is collapsed */
@@ -23,8 +26,10 @@
23
26
  }
24
27
 
25
28
  /* Only visible when view === 'imagine' */
26
- .img-studio[id="image-studio-panel"]:not([hidden]) {
27
- display: flex;
29
+ .img-studio.is-visible {
30
+ opacity: 1;
31
+ pointer-events: auto;
32
+ transform: translateY(0) scale(1);
28
33
  }
29
34
 
30
35
  /* MintDashboard renders ImageStudioPanel always, use view prop to show/hide via :not class trick */