@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
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
deleteChatSession,
|
|
6
6
|
renameChatSession,
|
|
7
7
|
getRecentInteractions,
|
|
8
|
+
saveSystemInteraction,
|
|
8
9
|
getRuntimeStatus,
|
|
9
10
|
listChatSessions,
|
|
10
11
|
listSavedPictures,
|
|
@@ -28,6 +29,7 @@ import ModelPanel from './ModelPanel'
|
|
|
28
29
|
import type { ModelInteraction } from './ModelPanel'
|
|
29
30
|
import PicturesLibrary from './PicturesLibrary'
|
|
30
31
|
import WorkspacePanel from './WorkspacePanel'
|
|
32
|
+
import WorkflowBuilderPanel from './WorkflowBuilderPanel'
|
|
31
33
|
|
|
32
34
|
const EXPRESSIONS = [
|
|
33
35
|
"Default",
|
|
@@ -266,6 +268,80 @@ export default function MintDashboard() {
|
|
|
266
268
|
const [chatSessions, setChatSessions] = useState<ChatSession[]>([])
|
|
267
269
|
const chatEnd = useRef<HTMLDivElement | null>(null)
|
|
268
270
|
const startupReady = (dashboardDataReady && modelReady) || startupTimedOut
|
|
271
|
+
const [proactiveSuggestion, setProactiveSuggestion] = useState<any>(null)
|
|
272
|
+
|
|
273
|
+
const [isSearchOpen, setIsSearchOpen] = useState(false)
|
|
274
|
+
const [searchQuery, setSearchQuery] = useState('')
|
|
275
|
+
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
278
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
|
|
279
|
+
event.preventDefault()
|
|
280
|
+
setIsSearchOpen((prev) => !prev)
|
|
281
|
+
} else if (event.key === 'Escape') {
|
|
282
|
+
setIsSearchOpen(false)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
window.addEventListener('keydown', handleKeyDown)
|
|
286
|
+
return () => window.removeEventListener('keydown', handleKeyDown)
|
|
287
|
+
}, [])
|
|
288
|
+
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
if (!isSearchOpen) {
|
|
291
|
+
setSearchQuery('')
|
|
292
|
+
}
|
|
293
|
+
}, [isSearchOpen])
|
|
294
|
+
|
|
295
|
+
const filteredSessions = chatSessions.filter((session) => {
|
|
296
|
+
if (session.kind === 'cli' || session.id === 'conversation-default') return false
|
|
297
|
+
return session.title.toLowerCase().includes(searchQuery.toLowerCase())
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
const groupSessionsByDate = (sessions: ChatSession[]) => {
|
|
301
|
+
const groups: { [key: string]: ChatSession[] } = {}
|
|
302
|
+
|
|
303
|
+
sessions.forEach((session) => {
|
|
304
|
+
const dateStr = session.updatedAt || session.createdAt
|
|
305
|
+
if (!dateStr) {
|
|
306
|
+
const groupName = 'Other'
|
|
307
|
+
if (!groups[groupName]) groups[groupName] = []
|
|
308
|
+
groups[groupName].push(session)
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const date = new Date(dateStr)
|
|
313
|
+
const today = new Date()
|
|
314
|
+
const yesterday = new Date()
|
|
315
|
+
yesterday.setDate(today.getDate() - 1)
|
|
316
|
+
|
|
317
|
+
let groupName = ''
|
|
318
|
+
if (date.toDateString() === today.toDateString()) {
|
|
319
|
+
groupName = 'Today'
|
|
320
|
+
} else if (date.toDateString() === yesterday.toDateString()) {
|
|
321
|
+
groupName = 'Yesterday'
|
|
322
|
+
} else {
|
|
323
|
+
const diffTime = Math.abs(today.getTime() - date.getTime())
|
|
324
|
+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
|
325
|
+
if (diffDays <= 7) {
|
|
326
|
+
groupName = 'Previous 7 days'
|
|
327
|
+
} else if (diffDays <= 30) {
|
|
328
|
+
groupName = 'Previous 30 days'
|
|
329
|
+
} else {
|
|
330
|
+
groupName = date.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!groups[groupName]) {
|
|
335
|
+
groups[groupName] = []
|
|
336
|
+
}
|
|
337
|
+
groups[groupName].push(session)
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
return groups
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const groupedSearchSessions = groupSessionsByDate(filteredSessions)
|
|
344
|
+
|
|
269
345
|
|
|
270
346
|
async function refreshHistory() {
|
|
271
347
|
const history = await getRecentInteractions(50, conversationId)
|
|
@@ -344,10 +420,14 @@ export default function MintDashboard() {
|
|
|
344
420
|
setPendingApproval(event.payload)
|
|
345
421
|
}
|
|
346
422
|
})
|
|
423
|
+
const unlistenProactive = window.api.onProactiveSuggestion?.((suggestion: any) => {
|
|
424
|
+
setProactiveSuggestion(suggestion)
|
|
425
|
+
})
|
|
347
426
|
return () => {
|
|
348
427
|
unlistenPromise?.then?.((unlisten) => unlisten?.())
|
|
349
428
|
unlistenSpotlight?.then?.((unlisten) => unlisten?.())
|
|
350
429
|
unlistenVision?.then?.((unlisten) => unlisten?.())
|
|
430
|
+
unlistenProactive?.then?.((unlisten) => unlisten?.())
|
|
351
431
|
}
|
|
352
432
|
}, [])
|
|
353
433
|
|
|
@@ -361,7 +441,7 @@ export default function MintDashboard() {
|
|
|
361
441
|
}, [view])
|
|
362
442
|
|
|
363
443
|
useEffect(() => {
|
|
364
|
-
if (view === '
|
|
444
|
+
if (view === 'workspace' && !agentMode) updateAgentMode(true)
|
|
365
445
|
}, [view, agentMode])
|
|
366
446
|
|
|
367
447
|
useEffect(() => {
|
|
@@ -427,14 +507,14 @@ export default function MintDashboard() {
|
|
|
427
507
|
}
|
|
428
508
|
}, [sending])
|
|
429
509
|
|
|
430
|
-
async function handleApproval(approved: boolean, autoApproveSession = false) {
|
|
510
|
+
async function handleApproval(approved: boolean, autoApproveSession = false, answer?: string) {
|
|
431
511
|
if (!pendingApproval) return
|
|
432
512
|
try {
|
|
433
513
|
if (autoApproveSession) {
|
|
434
514
|
sessionAutoApprovedRef.current = true
|
|
435
515
|
setSessionAutoApproved(true)
|
|
436
516
|
}
|
|
437
|
-
await submitToolApproval(pendingApproval.token, approved)
|
|
517
|
+
await submitToolApproval(pendingApproval.token, approved, answer)
|
|
438
518
|
} catch (reason) {
|
|
439
519
|
setError(errorMessage(reason))
|
|
440
520
|
} finally {
|
|
@@ -533,6 +613,17 @@ export default function MintDashboard() {
|
|
|
533
613
|
}
|
|
534
614
|
}
|
|
535
615
|
|
|
616
|
+
const handleProactiveAction = async (action: any) => {
|
|
617
|
+
setProactiveSuggestion(null)
|
|
618
|
+
if (action && action.type !== 'none') {
|
|
619
|
+
try {
|
|
620
|
+
await window.api.executeProactiveAction(action)
|
|
621
|
+
} catch (err) {
|
|
622
|
+
setError(String(err))
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
536
627
|
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
|
537
628
|
event.preventDefault()
|
|
538
629
|
const trimmed = message.trim()
|
|
@@ -674,7 +765,7 @@ export default function MintDashboard() {
|
|
|
674
765
|
const selected = await selectWorkspaceDirectory()
|
|
675
766
|
if (selected) {
|
|
676
767
|
updateWorkspacePath(selected)
|
|
677
|
-
setView('
|
|
768
|
+
setView('workspace')
|
|
678
769
|
}
|
|
679
770
|
} catch (reason) {
|
|
680
771
|
setError(errorMessage(reason))
|
|
@@ -781,6 +872,37 @@ export default function MintDashboard() {
|
|
|
781
872
|
}
|
|
782
873
|
}
|
|
783
874
|
|
|
875
|
+
function formatProviderChangeText(provider: string, model: string) {
|
|
876
|
+
let providerName = provider
|
|
877
|
+
if (provider === 'gemini') providerName = 'Gemini'
|
|
878
|
+
else if (provider === 'openai') providerName = 'OpenAI'
|
|
879
|
+
else if (provider === 'openrouter') providerName = 'OpenRouter'
|
|
880
|
+
else if (provider === 'deepseek') providerName = 'DeepSeek'
|
|
881
|
+
else if (provider === 'anthropic') providerName = 'Claude'
|
|
882
|
+
else if (provider === 'huggingface') providerName = 'HF'
|
|
883
|
+
else if (provider === 'local_openai') providerName = 'Local'
|
|
884
|
+
else if (provider === 'ollama') providerName = 'Ollama'
|
|
885
|
+
|
|
886
|
+
if (providerName && providerName === provider) {
|
|
887
|
+
providerName = providerName.charAt(0).toUpperCase() + providerName.slice(1)
|
|
888
|
+
}
|
|
889
|
+
return [providerName, model].filter(Boolean).join(' • ')
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function getActiveModelName(config: any, provider: string) {
|
|
893
|
+
switch (provider) {
|
|
894
|
+
case 'gemini': return config.geminiModel || 'gemini-1.5-flash'
|
|
895
|
+
case 'openai': return config.openaiModel || 'gpt-4o'
|
|
896
|
+
case 'openrouter': return config.openrouterModel || 'anthropic/claude-3.5-sonnet'
|
|
897
|
+
case 'deepseek': return config.deepseekModel || 'deepseek-chat'
|
|
898
|
+
case 'anthropic': return config.anthropicModel || 'claude-3-5-sonnet-20240620'
|
|
899
|
+
case 'huggingface': return config.hfModel || 'meta-llama/Meta-Llama-3-8B-Instruct'
|
|
900
|
+
case 'local_openai': return config.localModelName || 'llama3'
|
|
901
|
+
case 'ollama': return config.ollamaModel || 'llama3:latest'
|
|
902
|
+
default: return ''
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
784
906
|
async function changeProvider(provider: string) {
|
|
785
907
|
try {
|
|
786
908
|
const config = await window.settingsApi.getSettings()
|
|
@@ -788,6 +910,12 @@ export default function MintDashboard() {
|
|
|
788
910
|
await window.settingsApi.saveSettings(config)
|
|
789
911
|
setSettingsConfig(config)
|
|
790
912
|
setStatus(await getRuntimeStatus())
|
|
913
|
+
|
|
914
|
+
// Record system event in chat history
|
|
915
|
+
const activeModel = getActiveModelName(config, provider)
|
|
916
|
+
const displayName = formatProviderChangeText(provider, activeModel)
|
|
917
|
+
await saveSystemInteraction(conversationId, displayName, 'system', 'provider_change')
|
|
918
|
+
await refreshHistory()
|
|
791
919
|
} catch (reason) {
|
|
792
920
|
setError(errorMessage(reason))
|
|
793
921
|
}
|
|
@@ -817,6 +945,11 @@ export default function MintDashboard() {
|
|
|
817
945
|
await window.settingsApi.saveSettings(config)
|
|
818
946
|
setSettingsConfig(config)
|
|
819
947
|
setStatus(await getRuntimeStatus())
|
|
948
|
+
|
|
949
|
+
// Record system event in chat history
|
|
950
|
+
const displayName = formatProviderChangeText(provider, modelName)
|
|
951
|
+
await saveSystemInteraction(conversationId, displayName, 'system', 'provider_change')
|
|
952
|
+
await refreshHistory()
|
|
820
953
|
} catch (reason) {
|
|
821
954
|
setError(errorMessage(reason))
|
|
822
955
|
}
|
|
@@ -897,9 +1030,33 @@ export default function MintDashboard() {
|
|
|
897
1030
|
onSetInteractionEnabled={updateInteractionEnabled}
|
|
898
1031
|
onSetShowInteractionGuide={updateInteractionGuide}
|
|
899
1032
|
onShowToast={showToast}
|
|
1033
|
+
isSearchOpen={isSearchOpen}
|
|
1034
|
+
onSetSearchOpen={setIsSearchOpen}
|
|
900
1035
|
/>
|
|
901
|
-
<main className={`assistant-workspace ${layoutPreset === 'chat-wide' ? 'layout-chat-wide' : 'layout-model-wide'} ${modelVisible || view === '
|
|
902
|
-
{
|
|
1036
|
+
<main className={`assistant-workspace ${layoutPreset === 'chat-wide' ? 'layout-chat-wide' : 'layout-model-wide'} ${modelVisible || view === 'workspace' ? '' : 'model-hidden'} ${view === 'workspace' ? 'workspace-open' : ''}`}>
|
|
1037
|
+
{proactiveSuggestion && (
|
|
1038
|
+
<div className="proactive-bar" style={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 100 }}>
|
|
1039
|
+
<div className="proactive-header">
|
|
1040
|
+
<span className="proactive-icon">✨</span>
|
|
1041
|
+
<div className="proactive-message">{proactiveSuggestion.message}</div>
|
|
1042
|
+
<button className="proactive-dismiss-btn" onClick={() => setProactiveSuggestion(null)}>
|
|
1043
|
+
Dismiss
|
|
1044
|
+
</button>
|
|
1045
|
+
</div>
|
|
1046
|
+
<div className="proactive-chips">
|
|
1047
|
+
{proactiveSuggestion.suggestions?.map((sug: any, i: number) => (
|
|
1048
|
+
<button
|
|
1049
|
+
key={i}
|
|
1050
|
+
className="suggestion-chip"
|
|
1051
|
+
onClick={() => handleProactiveAction(sug.action)}
|
|
1052
|
+
>
|
|
1053
|
+
{sug.label}
|
|
1054
|
+
</button>
|
|
1055
|
+
))}
|
|
1056
|
+
</div>
|
|
1057
|
+
</div>
|
|
1058
|
+
)}
|
|
1059
|
+
{view === 'workspace' && (
|
|
903
1060
|
<WorkspacePanel
|
|
904
1061
|
agentMode={agentMode}
|
|
905
1062
|
sending={sending}
|
|
@@ -914,7 +1071,7 @@ export default function MintDashboard() {
|
|
|
914
1071
|
expressionIndex={expressionIndex}
|
|
915
1072
|
accessoryIndex={accessoryIndex}
|
|
916
1073
|
isLocked={isLocked}
|
|
917
|
-
isActive={modelVisible && view !== 'pictures' && view !== '
|
|
1074
|
+
isActive={modelVisible && view !== 'pictures' && view !== 'workspace' && view !== 'workflows'}
|
|
918
1075
|
layoutPreset={layoutPreset}
|
|
919
1076
|
sending={sending}
|
|
920
1077
|
interactionEnabled={interactionEnabled}
|
|
@@ -980,6 +1137,10 @@ export default function MintDashboard() {
|
|
|
980
1137
|
setMessage(imgPrompt)
|
|
981
1138
|
}}
|
|
982
1139
|
/>
|
|
1140
|
+
<WorkflowBuilderPanel
|
|
1141
|
+
view={view}
|
|
1142
|
+
onShowToast={showToast}
|
|
1143
|
+
/>
|
|
983
1144
|
</div>
|
|
984
1145
|
<div className={`startup-loading ${startupReady ? 'is-hidden' : ''}`} aria-live="polite" aria-busy={!startupReady}>
|
|
985
1146
|
<div className="startup-loading-content">
|
|
@@ -998,6 +1159,77 @@ export default function MintDashboard() {
|
|
|
998
1159
|
</button>
|
|
999
1160
|
</div>
|
|
1000
1161
|
)}
|
|
1162
|
+
|
|
1163
|
+
{isSearchOpen && (
|
|
1164
|
+
<div className="sidebar-search-modal-backdrop" onClick={() => setIsSearchOpen(false)}>
|
|
1165
|
+
<div className="sidebar-search-modal" onClick={(e) => e.stopPropagation()}>
|
|
1166
|
+
<div className="search-modal-header">
|
|
1167
|
+
<span className="search-icon-wrapper">
|
|
1168
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
1169
|
+
<circle cx="11" cy="11" r="8"></circle>
|
|
1170
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
1171
|
+
</svg>
|
|
1172
|
+
</span>
|
|
1173
|
+
<input
|
|
1174
|
+
type="text"
|
|
1175
|
+
placeholder="Search chats..."
|
|
1176
|
+
value={searchQuery}
|
|
1177
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
1178
|
+
autoFocus
|
|
1179
|
+
/>
|
|
1180
|
+
<button className="search-modal-close" onClick={() => setIsSearchOpen(false)}>
|
|
1181
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
1182
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
1183
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
1184
|
+
</svg>
|
|
1185
|
+
</button>
|
|
1186
|
+
</div>
|
|
1187
|
+
|
|
1188
|
+
<div className="search-modal-body">
|
|
1189
|
+
<button
|
|
1190
|
+
className="search-new-chat-btn"
|
|
1191
|
+
onClick={() => {
|
|
1192
|
+
clearHistory('New chat')
|
|
1193
|
+
setIsSearchOpen(false)
|
|
1194
|
+
}}
|
|
1195
|
+
>
|
|
1196
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
1197
|
+
<line x1="12" y1="5" x2="12" y2="19"></line>
|
|
1198
|
+
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
1199
|
+
</svg>
|
|
1200
|
+
<span>New Chat</span>
|
|
1201
|
+
</button>
|
|
1202
|
+
|
|
1203
|
+
<div className="search-modal-results">
|
|
1204
|
+
{Object.keys(groupedSearchSessions).length > 0 ? (
|
|
1205
|
+
Object.entries(groupedSearchSessions).map(([groupName, sessions]) => (
|
|
1206
|
+
<div key={groupName} className="search-results-group">
|
|
1207
|
+
<div className="search-group-title">{groupName}</div>
|
|
1208
|
+
{sessions.map((session) => (
|
|
1209
|
+
<button
|
|
1210
|
+
key={session.id}
|
|
1211
|
+
className={`search-result-item ${session.id === conversationId ? 'active' : ''}`}
|
|
1212
|
+
onClick={() => {
|
|
1213
|
+
selectConversation(session.id)
|
|
1214
|
+
setIsSearchOpen(false)
|
|
1215
|
+
}}
|
|
1216
|
+
>
|
|
1217
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
1218
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
|
1219
|
+
</svg>
|
|
1220
|
+
<span className="search-result-title">{session.title || 'New chat'}</span>
|
|
1221
|
+
</button>
|
|
1222
|
+
))}
|
|
1223
|
+
</div>
|
|
1224
|
+
))
|
|
1225
|
+
) : (
|
|
1226
|
+
<div className="search-no-results">No matching chats found</div>
|
|
1227
|
+
)}
|
|
1228
|
+
</div>
|
|
1229
|
+
</div>
|
|
1230
|
+
</div>
|
|
1231
|
+
</div>
|
|
1232
|
+
)}
|
|
1001
1233
|
</div>
|
|
1002
1234
|
)
|
|
1003
1235
|
}
|
|
@@ -9,10 +9,8 @@ interface PicturesLibraryProps {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export default function PicturesLibrary({ view, pictures, onSetView, onRefreshPictures }: PicturesLibraryProps) {
|
|
12
|
-
if (view !== 'pictures') return null
|
|
13
|
-
|
|
14
12
|
return (
|
|
15
|
-
<section className=
|
|
13
|
+
<section className={`pictures-library ${view === 'pictures' ? 'is-visible' : ''}`} aria-hidden={view !== 'pictures'}>
|
|
16
14
|
<header className="pictures-header">
|
|
17
15
|
<div><span className="pictures-kicker">Gallery</span><h2>Saved Pictures</h2></div>
|
|
18
16
|
<div className="pictures-header-actions">
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { DEFAULT_CONFIG } from '../SettingsWindow'
|
|
3
|
+
|
|
4
|
+
interface AudioTabProps {
|
|
5
|
+
config: typeof DEFAULT_CONFIG
|
|
6
|
+
updateField: (field: keyof typeof DEFAULT_CONFIG, value: any) => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function AudioTab({ config, updateField }: AudioTabProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="tab-pane active">
|
|
12
|
+
<section className="setting-section">
|
|
13
|
+
<div className="section-heading">
|
|
14
|
+
<div>
|
|
15
|
+
<p className="section-kicker">Speech</p>
|
|
16
|
+
<h2 className="section-title">Voice Reply</h2>
|
|
17
|
+
</div>
|
|
18
|
+
<p className="section-description">Control spoken responses and TTS behavior.</p>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div className="toggle-row">
|
|
22
|
+
<div>
|
|
23
|
+
<label>Enable Voice Reply</label>
|
|
24
|
+
<p className="hint">Mint will speak responses out loud when this is enabled.</p>
|
|
25
|
+
</div>
|
|
26
|
+
<label className="settings-toggle-switch">
|
|
27
|
+
<input
|
|
28
|
+
type="checkbox"
|
|
29
|
+
checked={config.enableVoiceReply}
|
|
30
|
+
onChange={(e) => updateField('enableVoiceReply', e.target.checked)}
|
|
31
|
+
/>
|
|
32
|
+
<span className="settings-toggle-slider"></span>
|
|
33
|
+
</label>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div className="form-grid single">
|
|
37
|
+
<div className="setting-row">
|
|
38
|
+
<label>Voice Engine</label>
|
|
39
|
+
<select value={config.ttsProvider} onChange={(e) => updateField('ttsProvider', e.target.value)}>
|
|
40
|
+
<option value="google">Google Cloud (Natural, Auto Lang)</option>
|
|
41
|
+
<option value="native">OS Native (Supports Pitch)</option>
|
|
42
|
+
</select>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div className="slider-stack">
|
|
47
|
+
<div className="setting-row">
|
|
48
|
+
<label>Volume</label>
|
|
49
|
+
<div className="slider-group">
|
|
50
|
+
<input
|
|
51
|
+
type="range"
|
|
52
|
+
min="0"
|
|
53
|
+
max="1"
|
|
54
|
+
step="0.1"
|
|
55
|
+
value={config.ttsVolume}
|
|
56
|
+
onChange={(e) => updateField('ttsVolume', parseFloat(e.target.value))}
|
|
57
|
+
className="range-slider"
|
|
58
|
+
/>
|
|
59
|
+
<span className="range-value">{Math.round(config.ttsVolume * 100)}%</span>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div className="setting-row">
|
|
63
|
+
<label>Speed</label>
|
|
64
|
+
<div className="slider-group">
|
|
65
|
+
<input
|
|
66
|
+
type="range"
|
|
67
|
+
min="0.5"
|
|
68
|
+
max="2"
|
|
69
|
+
step="0.1"
|
|
70
|
+
value={config.ttsSpeed}
|
|
71
|
+
onChange={(e) => updateField('ttsSpeed', parseFloat(e.target.value))}
|
|
72
|
+
className="range-slider"
|
|
73
|
+
/>
|
|
74
|
+
<span className="range-value">{parseFloat(String(config.ttsSpeed)).toFixed(1)}x</span>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
<div className="setting-row">
|
|
78
|
+
<label>Pitch</label>
|
|
79
|
+
<div className="slider-group">
|
|
80
|
+
<input
|
|
81
|
+
type="range"
|
|
82
|
+
min="0"
|
|
83
|
+
max="2"
|
|
84
|
+
step="0.1"
|
|
85
|
+
value={config.ttsPitch}
|
|
86
|
+
onChange={(e) => updateField('ttsPitch', parseFloat(e.target.value))}
|
|
87
|
+
className="range-slider"
|
|
88
|
+
/>
|
|
89
|
+
<span className="range-value">{parseFloat(String(config.ttsPitch)).toFixed(1)}</span>
|
|
90
|
+
</div>
|
|
91
|
+
<p className="hint">Pitch applies to OS native voice only.</p>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</section>
|
|
95
|
+
</div>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { DEFAULT_CONFIG } from '../SettingsWindow'
|
|
3
|
+
|
|
4
|
+
interface AutomationTabProps {
|
|
5
|
+
config: typeof DEFAULT_CONFIG
|
|
6
|
+
updateField: (field: keyof typeof DEFAULT_CONFIG, value: any) => void
|
|
7
|
+
handleOpenWorkflows: () => void
|
|
8
|
+
handleReloadWorkflows: () => void
|
|
9
|
+
isDesktopApp?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function AutomationTab({
|
|
13
|
+
config,
|
|
14
|
+
updateField,
|
|
15
|
+
handleOpenWorkflows,
|
|
16
|
+
handleReloadWorkflows,
|
|
17
|
+
isDesktopApp = true
|
|
18
|
+
}: AutomationTabProps) {
|
|
19
|
+
return (
|
|
20
|
+
<div className="tab-pane active">
|
|
21
|
+
<section className="setting-section">
|
|
22
|
+
<div className="section-heading">
|
|
23
|
+
<div>
|
|
24
|
+
<p className="section-kicker">Browser</p>
|
|
25
|
+
<h2 className="section-title">Automation Engine</h2>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="form-grid single">
|
|
29
|
+
<div className="setting-row">
|
|
30
|
+
<label>Browser Engine</label>
|
|
31
|
+
<select value={config.automationBrowser} onChange={(e) => updateField('automationBrowser', e.target.value)}>
|
|
32
|
+
<option value="chromium">Chromium (Bundled)</option>
|
|
33
|
+
<option value="/usr/bin/firefox">Firefox (System - Linux)</option>
|
|
34
|
+
</select>
|
|
35
|
+
</div>
|
|
36
|
+
<div className="setting-row">
|
|
37
|
+
<label>Chromium DevTools Endpoint</label>
|
|
38
|
+
<input type="text" value={config.browserDebugUrl} onChange={(e) => updateField('browserDebugUrl', e.target.value)} />
|
|
39
|
+
<p className="hint">Required for native tab reading and selector clicks.</p>
|
|
40
|
+
</div>
|
|
41
|
+
<div className="setting-row">
|
|
42
|
+
<label>Browser Extension Context Endpoint</label>
|
|
43
|
+
<input type="text" value={config.browserExtensionContextUrl} onChange={(e) => updateField('browserExtensionContextUrl', e.target.value)} />
|
|
44
|
+
<p className="hint">Fallback endpoint used when Chromium remote debugging is unavailable.</p>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</section>
|
|
48
|
+
|
|
49
|
+
<section className="setting-section">
|
|
50
|
+
<div className="section-heading">
|
|
51
|
+
<div>
|
|
52
|
+
<p className="section-kicker">Background tasks</p>
|
|
53
|
+
<h2 className="section-title">Native Headless Queue</h2>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="toggle-row">
|
|
57
|
+
<div>
|
|
58
|
+
<label>Process queued tasks automatically</label>
|
|
59
|
+
<p className="hint">Allow the bounded Rust worker to process pending tasks every 15 seconds.</p>
|
|
60
|
+
</div>
|
|
61
|
+
<label className="settings-toggle-switch">
|
|
62
|
+
<input
|
|
63
|
+
type="checkbox"
|
|
64
|
+
checked={config.enableHeadlessTaskQueue}
|
|
65
|
+
onChange={(e) => updateField('enableHeadlessTaskQueue', e.target.checked)}
|
|
66
|
+
/>
|
|
67
|
+
<span className="settings-toggle-slider"></span>
|
|
68
|
+
</label>
|
|
69
|
+
</div>
|
|
70
|
+
</section>
|
|
71
|
+
|
|
72
|
+
<section className="setting-section">
|
|
73
|
+
<div className="section-heading">
|
|
74
|
+
<div>
|
|
75
|
+
<p className="section-kicker">Awareness</p>
|
|
76
|
+
<h2 className="section-title">Proactive Assistant</h2>
|
|
77
|
+
</div>
|
|
78
|
+
<p className="section-description">Tune screen analysis frequency and suggestion timing.</p>
|
|
79
|
+
</div>
|
|
80
|
+
<div className="slider-stack">
|
|
81
|
+
<div className="setting-row">
|
|
82
|
+
<label>Screen Capture Frequency</label>
|
|
83
|
+
<div className="slider-group">
|
|
84
|
+
<input
|
|
85
|
+
type="range"
|
|
86
|
+
min="30"
|
|
87
|
+
max="300"
|
|
88
|
+
step="30"
|
|
89
|
+
value={config.proactiveInterval}
|
|
90
|
+
onChange={(e) => updateField('proactiveInterval', parseInt(e.target.value))}
|
|
91
|
+
className="range-slider"
|
|
92
|
+
/>
|
|
93
|
+
<span className="range-value">{config.proactiveInterval} sec</span>
|
|
94
|
+
</div>
|
|
95
|
+
<p className="hint">Lower values respond faster but use more API calls.</p>
|
|
96
|
+
</div>
|
|
97
|
+
<div className="setting-row">
|
|
98
|
+
<label>Suggestion Cooldown</label>
|
|
99
|
+
<div className="slider-group">
|
|
100
|
+
<input
|
|
101
|
+
type="range"
|
|
102
|
+
min="60"
|
|
103
|
+
max="600"
|
|
104
|
+
step="60"
|
|
105
|
+
value={config.proactiveCooldown}
|
|
106
|
+
onChange={(e) => updateField('proactiveCooldown', parseInt(e.target.value))}
|
|
107
|
+
className="range-slider"
|
|
108
|
+
/>
|
|
109
|
+
<span className="range-value">{Math.round(config.proactiveCooldown / 60)} min</span>
|
|
110
|
+
</div>
|
|
111
|
+
<p className="hint">Minimum time between repeat suggestions.</p>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</section>
|
|
115
|
+
|
|
116
|
+
<section className="setting-section">
|
|
117
|
+
<div className="section-heading">
|
|
118
|
+
<div>
|
|
119
|
+
<p className="section-kicker">Messaging</p>
|
|
120
|
+
<h2 className="section-title">Native Channel Bridges</h2>
|
|
121
|
+
</div>
|
|
122
|
+
<p className="section-description">Configure credentials used directly by the Rust channel workers.</p>
|
|
123
|
+
</div>
|
|
124
|
+
<div className="form-grid">
|
|
125
|
+
<label className="toggle-row"><span>Telegram Bot API</span><input type="checkbox" checked={config.enableTelegramBridge} onChange={(e) => updateField('enableTelegramBridge', e.target.checked)} /></label>
|
|
126
|
+
<input type="password" placeholder="Telegram bot token" value={config.telegramBotToken} onChange={(e) => updateField('telegramBotToken', e.target.value)} />
|
|
127
|
+
<label className="toggle-row"><span>Discord Gateway</span><input type="checkbox" checked={config.enableDiscordBridge} onChange={(e) => updateField('enableDiscordBridge', e.target.checked)} /></label>
|
|
128
|
+
<input type="password" placeholder="Discord bot token" value={config.discordBotToken} onChange={(e) => updateField('discordBotToken', e.target.value)} />
|
|
129
|
+
<input type="text" placeholder="Discord application ID for Rich Presence" value={config.discordApplicationId} onChange={(e) => updateField('discordApplicationId', e.target.value)} />
|
|
130
|
+
<label className="toggle-row"><span>Slack Socket Mode</span><input type="checkbox" checked={config.enableSlackBridge} onChange={(e) => updateField('enableSlackBridge', e.target.checked)} /></label>
|
|
131
|
+
<input type="password" placeholder="Slack bot token (xoxb-...)" value={config.slackBotToken} onChange={(e) => updateField('slackBotToken', e.target.value)} />
|
|
132
|
+
<input type="password" placeholder="Slack app token (xapp-...)" value={config.slackAppToken} onChange={(e) => updateField('slackAppToken', e.target.value)} />
|
|
133
|
+
<label className="toggle-row"><span>LINE Webhook</span><input type="checkbox" checked={config.enableLineBridge} onChange={(e) => updateField('enableLineBridge', e.target.checked)} /></label>
|
|
134
|
+
<input type="password" placeholder="LINE channel access token" value={config.lineChannelAccessToken} onChange={(e) => updateField('lineChannelAccessToken', e.target.value)} />
|
|
135
|
+
<input type="password" placeholder="LINE channel secret" value={config.lineChannelSecret} onChange={(e) => updateField('lineChannelSecret', e.target.value)} />
|
|
136
|
+
<label className="toggle-row"><span>WhatsApp Cloud API</span><input type="checkbox" checked={config.enableWhatsappBridge} onChange={(e) => updateField('enableWhatsappBridge', e.target.checked)} /></label>
|
|
137
|
+
<input type="password" placeholder="WhatsApp Cloud access token" value={config.whatsappCloudAccessToken} onChange={(e) => updateField('whatsappCloudAccessToken', e.target.value)} />
|
|
138
|
+
<input type="text" placeholder="WhatsApp phone number ID" value={config.whatsappPhoneNumberId} onChange={(e) => updateField('whatsappPhoneNumberId', e.target.value)} />
|
|
139
|
+
<input type="password" placeholder="WhatsApp verify token" value={config.whatsappVerifyToken} onChange={(e) => updateField('whatsappVerifyToken', e.target.value)} />
|
|
140
|
+
<input type="password" placeholder="WhatsApp app secret (optional HMAC validation)" value={config.whatsappAppSecret} onChange={(e) => updateField('whatsappAppSecret', e.target.value)} />
|
|
141
|
+
</div>
|
|
142
|
+
</section>
|
|
143
|
+
|
|
144
|
+
<section className="setting-section">
|
|
145
|
+
<div className="section-heading">
|
|
146
|
+
<div>
|
|
147
|
+
<p className="section-kicker">Rules</p>
|
|
148
|
+
<h2 className="section-title">Custom Workflows</h2>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
<div className="toggle-row">
|
|
152
|
+
<div>
|
|
153
|
+
<label>Enable Custom Workflows</label>
|
|
154
|
+
<p className="hint">Run "If This Then Mint" rules from the workflow JSON file.</p>
|
|
155
|
+
</div>
|
|
156
|
+
<label className="settings-toggle-switch">
|
|
157
|
+
<input
|
|
158
|
+
type="checkbox"
|
|
159
|
+
checked={config.enableCustomWorkflows}
|
|
160
|
+
onChange={(e) => updateField('enableCustomWorkflows', e.target.checked)}
|
|
161
|
+
/>
|
|
162
|
+
<span className="settings-toggle-slider"></span>
|
|
163
|
+
</label>
|
|
164
|
+
</div>
|
|
165
|
+
<div className="button-row" style={{ display: 'flex', gap: '10px', marginTop: '10px' }}>
|
|
166
|
+
<button className="btn btn-secondary" onClick={handleOpenWorkflows}>Open workflows.json</button>
|
|
167
|
+
<button className="btn btn-primary" onClick={handleReloadWorkflows}>Reload Rules</button>
|
|
168
|
+
</div>
|
|
169
|
+
</section>
|
|
170
|
+
|
|
171
|
+
<section className="setting-section">
|
|
172
|
+
<div className="section-heading">
|
|
173
|
+
<div>
|
|
174
|
+
<p className="section-kicker">Code mode</p>
|
|
175
|
+
<h2 className="section-title">Agent Collaboration</h2>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
<div className="toggle-row">
|
|
179
|
+
<div>
|
|
180
|
+
<label>Enable Multi-Agent Review</label>
|
|
181
|
+
<p className="hint">Allow a secondary model to review code written by the primary model.</p>
|
|
182
|
+
</div>
|
|
183
|
+
<label className="settings-toggle-switch">
|
|
184
|
+
<input
|
|
185
|
+
type="checkbox"
|
|
186
|
+
checked={config.enableAgentCollaboration}
|
|
187
|
+
onChange={(e) => updateField('enableAgentCollaboration', e.target.checked)}
|
|
188
|
+
/>
|
|
189
|
+
<span className="settings-toggle-slider"></span>
|
|
190
|
+
</label>
|
|
191
|
+
</div>
|
|
192
|
+
</section>
|
|
193
|
+
</div>
|
|
194
|
+
)
|
|
195
|
+
}
|