@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.
- package/Cargo.lock +3 -3
- package/Cargo.toml +1 -1
- package/README.md +9 -0
- package/crates/mint-core/src/api_server.rs +62 -4
- package/crates/mint-core/src/chat.rs +79 -0
- package/crates/mint-core/src/lib.rs +1 -1
- package/crates/mint-core/src/workflows.rs +8 -0
- package/package.json +2 -1
- package/src/renderer/src/App.tsx +1 -1
- package/src/renderer/src/components/ChatPanel.tsx +27 -14
- package/src/renderer/src/components/DashboardSidebar.tsx +71 -12
- package/src/renderer/src/components/ImageStudioPanel.tsx +7 -3
- package/src/renderer/src/components/MintDashboard.tsx +199 -5
- 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 +452 -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 -962
- package/src/renderer/src/components/WorkflowBuilderPanel.tsx +536 -0
- package/src/renderer/src/components/WorkflowsWindow.tsx +46 -0
- package/src/renderer/src/css/image-studio.css +9 -4
- package/src/renderer/src/css/settings.css +75 -6
- package/src/renderer/src/css/styles.css +355 -38
- package/src/renderer/src/css/workflows.css +542 -0
- package/src/renderer/src/env.d.ts +2 -0
- package/src/renderer/src/index.css +1 -0
- package/src/renderer/src/tauri.ts +97 -0
- package/src/renderer/src-web/components/ChatPanel.tsx +27 -14
- package/src/renderer/src-web/components/DashboardSidebar.tsx +17 -1
- package/src/renderer/src-web/components/ImageStudioPanel.tsx +7 -3
- package/src/renderer/src-web/components/MintDashboard.tsx +189 -0
- 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 +452 -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 -968
- package/src/renderer/src-web/css/image-studio.css +9 -4
- package/src/renderer/src-web/css/settings.css +75 -6
- package/src/renderer/src-web/css/styles.css +294 -38
- package/src/renderer/src-web/env.d.ts +2 -0
- package/src/renderer/src-web/tauri.ts +36 -0
- package/src-tauri/src/lib.rs +83 -90
- 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",
|
|
@@ -267,6 +269,79 @@ export default function MintDashboard() {
|
|
|
267
269
|
const chatEnd = useRef<HTMLDivElement | null>(null)
|
|
268
270
|
const startupReady = (dashboardDataReady && modelReady) || startupTimedOut
|
|
269
271
|
|
|
272
|
+
const [isSearchOpen, setIsSearchOpen] = useState(false)
|
|
273
|
+
const [searchQuery, setSearchQuery] = useState('')
|
|
274
|
+
|
|
275
|
+
useEffect(() => {
|
|
276
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
277
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
|
|
278
|
+
event.preventDefault()
|
|
279
|
+
setIsSearchOpen((prev) => !prev)
|
|
280
|
+
} else if (event.key === 'Escape') {
|
|
281
|
+
setIsSearchOpen(false)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
window.addEventListener('keydown', handleKeyDown)
|
|
285
|
+
return () => window.removeEventListener('keydown', handleKeyDown)
|
|
286
|
+
}, [])
|
|
287
|
+
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
if (!isSearchOpen) {
|
|
290
|
+
setSearchQuery('')
|
|
291
|
+
}
|
|
292
|
+
}, [isSearchOpen])
|
|
293
|
+
|
|
294
|
+
const filteredSessions = chatSessions.filter((session) => {
|
|
295
|
+
if (session.kind === 'cli' || session.id === 'conversation-default') return false
|
|
296
|
+
return session.title.toLowerCase().includes(searchQuery.toLowerCase())
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
const groupSessionsByDate = (sessions: ChatSession[]) => {
|
|
300
|
+
const groups: { [key: string]: ChatSession[] } = {}
|
|
301
|
+
|
|
302
|
+
sessions.forEach((session) => {
|
|
303
|
+
const dateStr = session.updatedAt || session.createdAt
|
|
304
|
+
if (!dateStr) {
|
|
305
|
+
const groupName = 'Other'
|
|
306
|
+
if (!groups[groupName]) groups[groupName] = []
|
|
307
|
+
groups[groupName].push(session)
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const date = new Date(dateStr)
|
|
312
|
+
const today = new Date()
|
|
313
|
+
const yesterday = new Date()
|
|
314
|
+
yesterday.setDate(today.getDate() - 1)
|
|
315
|
+
|
|
316
|
+
let groupName = ''
|
|
317
|
+
if (date.toDateString() === today.toDateString()) {
|
|
318
|
+
groupName = 'Today'
|
|
319
|
+
} else if (date.toDateString() === yesterday.toDateString()) {
|
|
320
|
+
groupName = 'Yesterday'
|
|
321
|
+
} else {
|
|
322
|
+
const diffTime = Math.abs(today.getTime() - date.getTime())
|
|
323
|
+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
|
324
|
+
if (diffDays <= 7) {
|
|
325
|
+
groupName = 'Previous 7 days'
|
|
326
|
+
} else if (diffDays <= 30) {
|
|
327
|
+
groupName = 'Previous 30 days'
|
|
328
|
+
} else {
|
|
329
|
+
groupName = date.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (!groups[groupName]) {
|
|
334
|
+
groups[groupName] = []
|
|
335
|
+
}
|
|
336
|
+
groups[groupName].push(session)
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
return groups
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const groupedSearchSessions = groupSessionsByDate(filteredSessions)
|
|
343
|
+
|
|
344
|
+
|
|
270
345
|
async function refreshHistory() {
|
|
271
346
|
const history = await getRecentInteractions(50, conversationId)
|
|
272
347
|
const reversed = history.reverse()
|
|
@@ -361,7 +436,7 @@ export default function MintDashboard() {
|
|
|
361
436
|
}, [view])
|
|
362
437
|
|
|
363
438
|
useEffect(() => {
|
|
364
|
-
if (view === '
|
|
439
|
+
if (view === 'workspace' && !agentMode) updateAgentMode(true)
|
|
365
440
|
}, [view, agentMode])
|
|
366
441
|
|
|
367
442
|
useEffect(() => {
|
|
@@ -674,7 +749,7 @@ export default function MintDashboard() {
|
|
|
674
749
|
const selected = await selectWorkspaceDirectory()
|
|
675
750
|
if (selected) {
|
|
676
751
|
updateWorkspacePath(selected)
|
|
677
|
-
setView('
|
|
752
|
+
setView('workspace')
|
|
678
753
|
}
|
|
679
754
|
} catch (reason) {
|
|
680
755
|
setError(errorMessage(reason))
|
|
@@ -781,6 +856,37 @@ export default function MintDashboard() {
|
|
|
781
856
|
}
|
|
782
857
|
}
|
|
783
858
|
|
|
859
|
+
function formatProviderChangeText(provider: string, model: string) {
|
|
860
|
+
let providerName = provider
|
|
861
|
+
if (provider === 'gemini') providerName = 'Gemini'
|
|
862
|
+
else if (provider === 'openai') providerName = 'OpenAI'
|
|
863
|
+
else if (provider === 'openrouter') providerName = 'OpenRouter'
|
|
864
|
+
else if (provider === 'deepseek') providerName = 'DeepSeek'
|
|
865
|
+
else if (provider === 'anthropic') providerName = 'Claude'
|
|
866
|
+
else if (provider === 'huggingface') providerName = 'HF'
|
|
867
|
+
else if (provider === 'local_openai') providerName = 'Local'
|
|
868
|
+
else if (provider === 'ollama') providerName = 'Ollama'
|
|
869
|
+
|
|
870
|
+
if (providerName && providerName === provider) {
|
|
871
|
+
providerName = providerName.charAt(0).toUpperCase() + providerName.slice(1)
|
|
872
|
+
}
|
|
873
|
+
return [providerName, model].filter(Boolean).join(' • ')
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function getActiveModelName(config: any, provider: string) {
|
|
877
|
+
switch (provider) {
|
|
878
|
+
case 'gemini': return config.geminiModel || 'gemini-1.5-flash'
|
|
879
|
+
case 'openai': return config.openaiModel || 'gpt-4o'
|
|
880
|
+
case 'openrouter': return config.openrouterModel || 'anthropic/claude-3.5-sonnet'
|
|
881
|
+
case 'deepseek': return config.deepseekModel || 'deepseek-chat'
|
|
882
|
+
case 'anthropic': return config.anthropicModel || 'claude-3-5-sonnet-20240620'
|
|
883
|
+
case 'huggingface': return config.hfModel || 'meta-llama/Meta-Llama-3-8B-Instruct'
|
|
884
|
+
case 'local_openai': return config.localModelName || 'llama3'
|
|
885
|
+
case 'ollama': return config.ollamaModel || 'llama3:latest'
|
|
886
|
+
default: return ''
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
784
890
|
async function changeProvider(provider: string) {
|
|
785
891
|
try {
|
|
786
892
|
const config = await window.settingsApi.getSettings()
|
|
@@ -788,6 +894,12 @@ export default function MintDashboard() {
|
|
|
788
894
|
await window.settingsApi.saveSettings(config)
|
|
789
895
|
setSettingsConfig(config)
|
|
790
896
|
setStatus(await getRuntimeStatus())
|
|
897
|
+
|
|
898
|
+
// Record system event in chat history
|
|
899
|
+
const activeModel = getActiveModelName(config, provider)
|
|
900
|
+
const displayName = formatProviderChangeText(provider, activeModel)
|
|
901
|
+
await saveSystemInteraction(conversationId, displayName, 'system', 'provider_change')
|
|
902
|
+
await refreshHistory()
|
|
791
903
|
} catch (reason) {
|
|
792
904
|
setError(errorMessage(reason))
|
|
793
905
|
}
|
|
@@ -817,6 +929,11 @@ export default function MintDashboard() {
|
|
|
817
929
|
await window.settingsApi.saveSettings(config)
|
|
818
930
|
setSettingsConfig(config)
|
|
819
931
|
setStatus(await getRuntimeStatus())
|
|
932
|
+
|
|
933
|
+
// Record system event in chat history
|
|
934
|
+
const displayName = formatProviderChangeText(provider, modelName)
|
|
935
|
+
await saveSystemInteraction(conversationId, displayName, 'system', 'provider_change')
|
|
936
|
+
await refreshHistory()
|
|
820
937
|
} catch (reason) {
|
|
821
938
|
setError(errorMessage(reason))
|
|
822
939
|
}
|
|
@@ -897,9 +1014,11 @@ export default function MintDashboard() {
|
|
|
897
1014
|
onSetInteractionEnabled={updateInteractionEnabled}
|
|
898
1015
|
onSetShowInteractionGuide={updateInteractionGuide}
|
|
899
1016
|
onShowToast={showToast}
|
|
1017
|
+
isSearchOpen={isSearchOpen}
|
|
1018
|
+
onSetSearchOpen={setIsSearchOpen}
|
|
900
1019
|
/>
|
|
901
|
-
<main className={`assistant-workspace ${layoutPreset === 'chat-wide' ? 'layout-chat-wide' : 'layout-model-wide'} ${modelVisible || view === '
|
|
902
|
-
{view === '
|
|
1020
|
+
<main className={`assistant-workspace ${layoutPreset === 'chat-wide' ? 'layout-chat-wide' : 'layout-model-wide'} ${modelVisible || view === 'workspace' ? '' : 'model-hidden'} ${view === 'workspace' ? 'workspace-open' : ''}`}>
|
|
1021
|
+
{view === 'workspace' && (
|
|
903
1022
|
<WorkspacePanel
|
|
904
1023
|
agentMode={agentMode}
|
|
905
1024
|
sending={sending}
|
|
@@ -914,7 +1033,7 @@ export default function MintDashboard() {
|
|
|
914
1033
|
expressionIndex={expressionIndex}
|
|
915
1034
|
accessoryIndex={accessoryIndex}
|
|
916
1035
|
isLocked={isLocked}
|
|
917
|
-
isActive={modelVisible && view !== 'pictures' && view !== '
|
|
1036
|
+
isActive={modelVisible && view !== 'pictures' && view !== 'workspace' && view !== 'workflows'}
|
|
918
1037
|
layoutPreset={layoutPreset}
|
|
919
1038
|
sending={sending}
|
|
920
1039
|
interactionEnabled={interactionEnabled}
|
|
@@ -980,6 +1099,10 @@ export default function MintDashboard() {
|
|
|
980
1099
|
setMessage(imgPrompt)
|
|
981
1100
|
}}
|
|
982
1101
|
/>
|
|
1102
|
+
<WorkflowBuilderPanel
|
|
1103
|
+
view={view}
|
|
1104
|
+
onShowToast={showToast}
|
|
1105
|
+
/>
|
|
983
1106
|
</div>
|
|
984
1107
|
<div className={`startup-loading ${startupReady ? 'is-hidden' : ''}`} aria-live="polite" aria-busy={!startupReady}>
|
|
985
1108
|
<div className="startup-loading-content">
|
|
@@ -998,6 +1121,77 @@ export default function MintDashboard() {
|
|
|
998
1121
|
</button>
|
|
999
1122
|
</div>
|
|
1000
1123
|
)}
|
|
1124
|
+
|
|
1125
|
+
{isSearchOpen && (
|
|
1126
|
+
<div className="sidebar-search-modal-backdrop" onClick={() => setIsSearchOpen(false)}>
|
|
1127
|
+
<div className="sidebar-search-modal" onClick={(e) => e.stopPropagation()}>
|
|
1128
|
+
<div className="search-modal-header">
|
|
1129
|
+
<span className="search-icon-wrapper">
|
|
1130
|
+
<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">
|
|
1131
|
+
<circle cx="11" cy="11" r="8"></circle>
|
|
1132
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
1133
|
+
</svg>
|
|
1134
|
+
</span>
|
|
1135
|
+
<input
|
|
1136
|
+
type="text"
|
|
1137
|
+
placeholder="Search chats..."
|
|
1138
|
+
value={searchQuery}
|
|
1139
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
1140
|
+
autoFocus
|
|
1141
|
+
/>
|
|
1142
|
+
<button className="search-modal-close" onClick={() => setIsSearchOpen(false)}>
|
|
1143
|
+
<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">
|
|
1144
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
1145
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
1146
|
+
</svg>
|
|
1147
|
+
</button>
|
|
1148
|
+
</div>
|
|
1149
|
+
|
|
1150
|
+
<div className="search-modal-body">
|
|
1151
|
+
<button
|
|
1152
|
+
className="search-new-chat-btn"
|
|
1153
|
+
onClick={() => {
|
|
1154
|
+
clearHistory('New chat')
|
|
1155
|
+
setIsSearchOpen(false)
|
|
1156
|
+
}}
|
|
1157
|
+
>
|
|
1158
|
+
<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">
|
|
1159
|
+
<line x1="12" y1="5" x2="12" y2="19"></line>
|
|
1160
|
+
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
1161
|
+
</svg>
|
|
1162
|
+
<span>New Chat</span>
|
|
1163
|
+
</button>
|
|
1164
|
+
|
|
1165
|
+
<div className="search-modal-results">
|
|
1166
|
+
{Object.keys(groupedSearchSessions).length > 0 ? (
|
|
1167
|
+
Object.entries(groupedSearchSessions).map(([groupName, sessions]) => (
|
|
1168
|
+
<div key={groupName} className="search-results-group">
|
|
1169
|
+
<div className="search-group-title">{groupName}</div>
|
|
1170
|
+
{sessions.map((session) => (
|
|
1171
|
+
<button
|
|
1172
|
+
key={session.id}
|
|
1173
|
+
className={`search-result-item ${session.id === conversationId ? 'active' : ''}`}
|
|
1174
|
+
onClick={() => {
|
|
1175
|
+
selectConversation(session.id)
|
|
1176
|
+
setIsSearchOpen(false)
|
|
1177
|
+
}}
|
|
1178
|
+
>
|
|
1179
|
+
<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">
|
|
1180
|
+
<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>
|
|
1181
|
+
</svg>
|
|
1182
|
+
<span className="search-result-title">{session.title || 'New chat'}</span>
|
|
1183
|
+
</button>
|
|
1184
|
+
))}
|
|
1185
|
+
</div>
|
|
1186
|
+
))
|
|
1187
|
+
) : (
|
|
1188
|
+
<div className="search-no-results">No matching chats found</div>
|
|
1189
|
+
)}
|
|
1190
|
+
</div>
|
|
1191
|
+
</div>
|
|
1192
|
+
</div>
|
|
1193
|
+
</div>
|
|
1194
|
+
)}
|
|
1001
1195
|
</div>
|
|
1002
1196
|
)
|
|
1003
1197
|
}
|
|
@@ -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
|
+
}
|