@pheem49/mint 1.7.1 → 1.8.1
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.
- package/.github/workflows/release.yml +1 -1
- package/Cargo.lock +34 -3
- package/Cargo.toml +2 -2
- package/README.md +40 -0
- package/Release_Note.md +95 -0
- package/assets/Mint_CLI.png +0 -0
- package/crates/mint-cli/Cargo.toml +1 -0
- package/crates/mint-cli/src/agent.rs +142 -48
- package/crates/mint-cli/src/main.rs +231 -22
- package/crates/mint-core/src/api_server.rs +71 -4
- package/crates/mint-core/src/browser.rs +401 -0
- package/crates/mint-core/src/chat.rs +79 -0
- package/crates/mint-core/src/config.rs +69 -1
- package/crates/mint-core/src/lib.rs +6 -1
- package/crates/mint-core/src/orchestration.rs +137 -1
- package/crates/mint-core/src/skills.rs +113 -4
- package/crates/mint-core/src/workflows.rs +9 -22
- package/package.json +2 -1
- package/src/renderer/src/App.tsx +1 -1
- package/src/renderer/src/components/ChatPanel.tsx +197 -47
- package/src/renderer/src/components/DashboardSidebar.tsx +71 -12
- package/src/renderer/src/components/ImageStudioPanel.tsx +18 -4
- package/src/renderer/src/components/MintDashboard.tsx +239 -7
- package/src/renderer/src/components/PicturesLibrary.tsx +1 -3
- package/src/renderer/src/components/Settings/AudioTab.tsx +97 -0
- package/src/renderer/src/components/Settings/AutomationTab.tsx +195 -0
- package/src/renderer/src/components/Settings/GeneralTab.tsx +950 -0
- package/src/renderer/src/components/Settings/MemoryTab.tsx +60 -0
- package/src/renderer/src/components/Settings/PluginsTab.tsx +632 -0
- package/src/renderer/src/components/Settings/ShortcutsTab.tsx +30 -0
- package/src/renderer/src/components/Settings/ThemeTab.tsx +129 -0
- package/src/renderer/src/components/SettingsWindow.tsx +175 -970
- package/src/renderer/src/components/WorkflowBuilderPanel.tsx +536 -0
- package/src/renderer/src/components/WorkflowsWindow.tsx +46 -0
- package/src/renderer/src/components/WorkspacePanel.tsx +66 -9
- package/src/renderer/src/css/image-studio.css +15 -4
- package/src/renderer/src/css/settings.css +75 -6
- package/src/renderer/src/css/styles.css +419 -49
- package/src/renderer/src/css/workflows.css +542 -0
- package/src/renderer/src/env.d.ts +2 -0
- package/src/renderer/src/index.css +13 -1
- package/src/renderer/src/tauri.ts +140 -2
- package/src/renderer/src-web/components/ChatPanel.tsx +190 -46
- package/src/renderer/src-web/components/DashboardSidebar.tsx +17 -1
- package/src/renderer/src-web/components/ImageStudioPanel.tsx +18 -4
- package/src/renderer/src-web/components/MintDashboard.tsx +197 -3
- package/src/renderer/src-web/components/PicturesLibrary.tsx +1 -3
- package/src/renderer/src-web/components/Settings/AudioTab.tsx +97 -0
- package/src/renderer/src-web/components/Settings/AutomationTab.tsx +196 -0
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +948 -0
- package/src/renderer/src-web/components/Settings/MemoryTab.tsx +60 -0
- package/src/renderer/src-web/components/Settings/PluginsTab.tsx +632 -0
- package/src/renderer/src-web/components/Settings/ShortcutsTab.tsx +30 -0
- package/src/renderer/src-web/components/Settings/ThemeTab.tsx +129 -0
- package/src/renderer/src-web/components/SettingsWindow.tsx +187 -976
- package/src/renderer/src-web/css/image-studio.css +15 -4
- package/src/renderer/src-web/css/settings.css +75 -6
- package/src/renderer/src-web/css/styles.css +394 -50
- package/src/renderer/src-web/env.d.ts +2 -0
- package/src/renderer/src-web/index.css +12 -1
- package/src/renderer/src-web/tauri.ts +60 -2
- package/src-tauri/src/browser.rs +1 -141
- package/src-tauri/src/headless.rs +3 -2
- package/src-tauri/src/lib.rs +195 -111
- package/src-tauri/src/workflows.rs +56 -4
- 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
|
+
}
|