@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,
|
|
@@ -235,6 +236,79 @@ export default function MintDashboard() {
|
|
|
235
236
|
const chatEnd = useRef<HTMLDivElement | null>(null)
|
|
236
237
|
const startupReady = dashboardDataReady || startupTimedOut
|
|
237
238
|
|
|
239
|
+
const [isSearchOpen, setIsSearchOpen] = useState(false)
|
|
240
|
+
const [searchQuery, setSearchQuery] = useState('')
|
|
241
|
+
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
244
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
|
|
245
|
+
event.preventDefault()
|
|
246
|
+
setIsSearchOpen((prev) => !prev)
|
|
247
|
+
} else if (event.key === 'Escape') {
|
|
248
|
+
setIsSearchOpen(false)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
window.addEventListener('keydown', handleKeyDown)
|
|
252
|
+
return () => window.removeEventListener('keydown', handleKeyDown)
|
|
253
|
+
}, [])
|
|
254
|
+
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
if (!isSearchOpen) {
|
|
257
|
+
setSearchQuery('')
|
|
258
|
+
}
|
|
259
|
+
}, [isSearchOpen])
|
|
260
|
+
|
|
261
|
+
const filteredSessions = chatSessions.filter((session) => {
|
|
262
|
+
if (session.kind === 'cli' || session.id === 'conversation-default') return false
|
|
263
|
+
return session.title.toLowerCase().includes(searchQuery.toLowerCase())
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
const groupSessionsByDate = (sessions: ChatSession[]) => {
|
|
267
|
+
const groups: { [key: string]: ChatSession[] } = {}
|
|
268
|
+
|
|
269
|
+
sessions.forEach((session) => {
|
|
270
|
+
const dateStr = session.updatedAt || session.createdAt
|
|
271
|
+
if (!dateStr) {
|
|
272
|
+
const groupName = 'Other'
|
|
273
|
+
if (!groups[groupName]) groups[groupName] = []
|
|
274
|
+
groups[groupName].push(session)
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const date = new Date(dateStr)
|
|
279
|
+
const today = new Date()
|
|
280
|
+
const yesterday = new Date()
|
|
281
|
+
yesterday.setDate(today.getDate() - 1)
|
|
282
|
+
|
|
283
|
+
let groupName = ''
|
|
284
|
+
if (date.toDateString() === today.toDateString()) {
|
|
285
|
+
groupName = 'Today'
|
|
286
|
+
} else if (date.toDateString() === yesterday.toDateString()) {
|
|
287
|
+
groupName = 'Yesterday'
|
|
288
|
+
} else {
|
|
289
|
+
const diffTime = Math.abs(today.getTime() - date.getTime())
|
|
290
|
+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
|
291
|
+
if (diffDays <= 7) {
|
|
292
|
+
groupName = 'Previous 7 days'
|
|
293
|
+
} else if (diffDays <= 30) {
|
|
294
|
+
groupName = 'Previous 30 days'
|
|
295
|
+
} else {
|
|
296
|
+
groupName = date.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!groups[groupName]) {
|
|
301
|
+
groups[groupName] = []
|
|
302
|
+
}
|
|
303
|
+
groups[groupName].push(session)
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
return groups
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const groupedSearchSessions = groupSessionsByDate(filteredSessions)
|
|
310
|
+
|
|
311
|
+
|
|
238
312
|
async function refreshHistory() {
|
|
239
313
|
const history = await getRecentInteractions(50, conversationId)
|
|
240
314
|
const reversed = history.reverse()
|
|
@@ -310,6 +384,10 @@ export default function MintDashboard() {
|
|
|
310
384
|
}
|
|
311
385
|
|
|
312
386
|
const toggleSidebar = () => {
|
|
387
|
+
if (window.innerWidth <= 760) {
|
|
388
|
+
setMobileSidebarOpen(false)
|
|
389
|
+
return
|
|
390
|
+
}
|
|
313
391
|
const next = !sidebarCollapsed
|
|
314
392
|
window.localStorage.setItem('mint:sidebar-collapsed', String(next))
|
|
315
393
|
setSidebarCollapsed(next)
|
|
@@ -326,10 +404,10 @@ export default function MintDashboard() {
|
|
|
326
404
|
setAgentMode(enabled)
|
|
327
405
|
}
|
|
328
406
|
|
|
329
|
-
async function handleApproval(approved: boolean, _autoApproveSession = false) {
|
|
407
|
+
async function handleApproval(approved: boolean, _autoApproveSession = false, answer?: string) {
|
|
330
408
|
if (!pendingApproval) return
|
|
331
409
|
try {
|
|
332
|
-
await submitToolApproval(pendingApproval.token, approved)
|
|
410
|
+
await submitToolApproval(pendingApproval.token, approved, answer)
|
|
333
411
|
} catch (reason) {
|
|
334
412
|
setError(errorMessage(reason))
|
|
335
413
|
} finally {
|
|
@@ -664,6 +742,37 @@ export default function MintDashboard() {
|
|
|
664
742
|
}
|
|
665
743
|
}
|
|
666
744
|
|
|
745
|
+
function formatProviderChangeText(provider: string, model: string) {
|
|
746
|
+
let providerName = provider
|
|
747
|
+
if (provider === 'gemini') providerName = 'Gemini'
|
|
748
|
+
else if (provider === 'openai') providerName = 'OpenAI'
|
|
749
|
+
else if (provider === 'openrouter') providerName = 'OpenRouter'
|
|
750
|
+
else if (provider === 'deepseek') providerName = 'DeepSeek'
|
|
751
|
+
else if (provider === 'anthropic') providerName = 'Claude'
|
|
752
|
+
else if (provider === 'huggingface') providerName = 'HF'
|
|
753
|
+
else if (provider === 'local_openai') providerName = 'Local'
|
|
754
|
+
else if (provider === 'ollama') providerName = 'Ollama'
|
|
755
|
+
|
|
756
|
+
if (providerName && providerName === provider) {
|
|
757
|
+
providerName = providerName.charAt(0).toUpperCase() + providerName.slice(1)
|
|
758
|
+
}
|
|
759
|
+
return [providerName, model].filter(Boolean).join(' • ')
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function getActiveModelName(config: any, provider: string) {
|
|
763
|
+
switch (provider) {
|
|
764
|
+
case 'gemini': return config.geminiModel || 'gemini-1.5-flash'
|
|
765
|
+
case 'openai': return config.openaiModel || 'gpt-4o'
|
|
766
|
+
case 'openrouter': return config.openrouterModel || 'anthropic/claude-3.5-sonnet'
|
|
767
|
+
case 'deepseek': return config.deepseekModel || 'deepseek-chat'
|
|
768
|
+
case 'anthropic': return config.anthropicModel || 'claude-3-5-sonnet-20240620'
|
|
769
|
+
case 'huggingface': return config.hfModel || 'meta-llama/Meta-Llama-3-8B-Instruct'
|
|
770
|
+
case 'local_openai': return config.localModelName || 'llama3'
|
|
771
|
+
case 'ollama': return config.ollamaModel || 'llama3:latest'
|
|
772
|
+
default: return ''
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
667
776
|
async function changeProvider(provider: string) {
|
|
668
777
|
try {
|
|
669
778
|
const config = await window.settingsApi.getSettings()
|
|
@@ -671,6 +780,12 @@ export default function MintDashboard() {
|
|
|
671
780
|
await window.settingsApi.saveSettings(config)
|
|
672
781
|
setSettingsConfig(config)
|
|
673
782
|
setStatus(await getRuntimeStatus())
|
|
783
|
+
|
|
784
|
+
// Record system event in chat history
|
|
785
|
+
const activeModel = getActiveModelName(config, provider)
|
|
786
|
+
const displayName = formatProviderChangeText(provider, activeModel)
|
|
787
|
+
await saveSystemInteraction(conversationId, displayName, 'system', 'provider_change')
|
|
788
|
+
await refreshHistory()
|
|
674
789
|
} catch (reason) {
|
|
675
790
|
setError(errorMessage(reason))
|
|
676
791
|
}
|
|
@@ -700,6 +815,11 @@ export default function MintDashboard() {
|
|
|
700
815
|
await window.settingsApi.saveSettings(config)
|
|
701
816
|
setSettingsConfig(config)
|
|
702
817
|
setStatus(await getRuntimeStatus())
|
|
818
|
+
|
|
819
|
+
// Record system event in chat history
|
|
820
|
+
const displayName = formatProviderChangeText(provider, modelName)
|
|
821
|
+
await saveSystemInteraction(conversationId, displayName, 'system', 'provider_change')
|
|
822
|
+
await refreshHistory()
|
|
703
823
|
} catch (reason) {
|
|
704
824
|
setError(errorMessage(reason))
|
|
705
825
|
}
|
|
@@ -709,7 +829,7 @@ export default function MintDashboard() {
|
|
|
709
829
|
|
|
710
830
|
return (
|
|
711
831
|
<div className={`app-container ${startupReady ? '' : 'is-loading'}`}>
|
|
712
|
-
<div className={`app-body ${sidebarCollapsed ? 'sidebar-collapsed' : ''} ${view === 'pictures' ? 'pictures-open' : ''} ${mobileSidebarOpen ? 'mobile-sidebar-open' : ''}`}>
|
|
832
|
+
<div className={`app-body ${(sidebarCollapsed && window.innerWidth > 760) ? 'sidebar-collapsed' : ''} ${view === 'pictures' ? 'pictures-open' : ''} ${mobileSidebarOpen ? 'mobile-sidebar-open' : ''}`}>
|
|
713
833
|
{mobileSidebarOpen && (
|
|
714
834
|
<div
|
|
715
835
|
className="sidebar-backdrop"
|
|
@@ -738,6 +858,8 @@ export default function MintDashboard() {
|
|
|
738
858
|
onDeleteConversation={deleteConversation}
|
|
739
859
|
onRenameConversation={renameConversation}
|
|
740
860
|
onSetView={changeView}
|
|
861
|
+
isSearchOpen={isSearchOpen}
|
|
862
|
+
onSetSearchOpen={setIsSearchOpen}
|
|
741
863
|
/>
|
|
742
864
|
<main className="assistant-workspace model-hidden">
|
|
743
865
|
<ChatPanel
|
|
@@ -790,6 +912,7 @@ export default function MintDashboard() {
|
|
|
790
912
|
setView('chat')
|
|
791
913
|
setMessage(imgPrompt)
|
|
792
914
|
}}
|
|
915
|
+
onToggleMobileSidebar={() => setMobileSidebarOpen(!mobileSidebarOpen)}
|
|
793
916
|
/>
|
|
794
917
|
</div>
|
|
795
918
|
<div className={`startup-loading ${startupReady ? 'is-hidden' : ''}`} aria-live="polite" aria-busy={!startupReady}>
|
|
@@ -804,6 +927,77 @@ export default function MintDashboard() {
|
|
|
804
927
|
<button onClick={() => setError('')} style={{ marginLeft: '12px', background: 'transparent', border: 0, color: 'white', cursor: 'pointer' }}>✕</button>
|
|
805
928
|
</div>
|
|
806
929
|
)}
|
|
930
|
+
|
|
931
|
+
{isSearchOpen && (
|
|
932
|
+
<div className="sidebar-search-modal-backdrop" onClick={() => setIsSearchOpen(false)}>
|
|
933
|
+
<div className="sidebar-search-modal" onClick={(e) => e.stopPropagation()}>
|
|
934
|
+
<div className="search-modal-header">
|
|
935
|
+
<span className="search-icon-wrapper">
|
|
936
|
+
<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">
|
|
937
|
+
<circle cx="11" cy="11" r="8"></circle>
|
|
938
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
939
|
+
</svg>
|
|
940
|
+
</span>
|
|
941
|
+
<input
|
|
942
|
+
type="text"
|
|
943
|
+
placeholder="Search chats..."
|
|
944
|
+
value={searchQuery}
|
|
945
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
946
|
+
autoFocus
|
|
947
|
+
/>
|
|
948
|
+
<button className="search-modal-close" onClick={() => setIsSearchOpen(false)}>
|
|
949
|
+
<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">
|
|
950
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
951
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
952
|
+
</svg>
|
|
953
|
+
</button>
|
|
954
|
+
</div>
|
|
955
|
+
|
|
956
|
+
<div className="search-modal-body">
|
|
957
|
+
<button
|
|
958
|
+
className="search-new-chat-btn"
|
|
959
|
+
onClick={() => {
|
|
960
|
+
clearHistory('New chat')
|
|
961
|
+
setIsSearchOpen(false)
|
|
962
|
+
}}
|
|
963
|
+
>
|
|
964
|
+
<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">
|
|
965
|
+
<line x1="12" y1="5" x2="12" y2="19"></line>
|
|
966
|
+
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
967
|
+
</svg>
|
|
968
|
+
<span>New Chat</span>
|
|
969
|
+
</button>
|
|
970
|
+
|
|
971
|
+
<div className="search-modal-results">
|
|
972
|
+
{Object.keys(groupedSearchSessions).length > 0 ? (
|
|
973
|
+
Object.entries(groupedSearchSessions).map(([groupName, sessions]) => (
|
|
974
|
+
<div key={groupName} className="search-results-group">
|
|
975
|
+
<div className="search-group-title">{groupName}</div>
|
|
976
|
+
{sessions.map((session) => (
|
|
977
|
+
<button
|
|
978
|
+
key={session.id}
|
|
979
|
+
className={`search-result-item ${session.id === conversationId ? 'active' : ''}`}
|
|
980
|
+
onClick={() => {
|
|
981
|
+
selectConversation(session.id)
|
|
982
|
+
setIsSearchOpen(false)
|
|
983
|
+
}}
|
|
984
|
+
>
|
|
985
|
+
<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">
|
|
986
|
+
<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>
|
|
987
|
+
</svg>
|
|
988
|
+
<span className="search-result-title">{session.title || 'New chat'}</span>
|
|
989
|
+
</button>
|
|
990
|
+
))}
|
|
991
|
+
</div>
|
|
992
|
+
))
|
|
993
|
+
) : (
|
|
994
|
+
<div className="search-no-results">No matching chats found</div>
|
|
995
|
+
)}
|
|
996
|
+
</div>
|
|
997
|
+
</div>
|
|
998
|
+
</div>
|
|
999
|
+
</div>
|
|
1000
|
+
)}
|
|
807
1001
|
</div>
|
|
808
1002
|
)
|
|
809
1003
|
}
|
|
@@ -39,10 +39,8 @@ export default function PicturesLibrary({ view, pictures, onSetView, onRefreshPi
|
|
|
39
39
|
return () => window.clearTimeout(timer)
|
|
40
40
|
}, [view, pictures.length, visibleCount])
|
|
41
41
|
|
|
42
|
-
if (view !== 'pictures') return null
|
|
43
|
-
|
|
44
42
|
return (
|
|
45
|
-
<section className=
|
|
43
|
+
<section className={`pictures-library ${view === 'pictures' ? 'is-visible' : ''}`} aria-hidden={view !== 'pictures'}>
|
|
46
44
|
<header className="pictures-header">
|
|
47
45
|
<div><span className="pictures-kicker">Gallery</span><h2>Saved Pictures</h2></div>
|
|
48
46
|
<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,196 @@
|
|
|
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 = false
|
|
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} disabled={!isDesktopApp}>Open workflows.json</button>
|
|
167
|
+
<button className="btn btn-primary" onClick={handleReloadWorkflows} disabled={!isDesktopApp}>Reload Rules</button>
|
|
168
|
+
</div>
|
|
169
|
+
{!isDesktopApp && <p className="hint">Opening and reloading local workflow files requires the desktop app.</p>}
|
|
170
|
+
</section>
|
|
171
|
+
|
|
172
|
+
<section className="setting-section">
|
|
173
|
+
<div className="section-heading">
|
|
174
|
+
<div>
|
|
175
|
+
<p className="section-kicker">Code mode</p>
|
|
176
|
+
<h2 className="section-title">Agent Collaboration</h2>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
<div className="toggle-row">
|
|
180
|
+
<div>
|
|
181
|
+
<label>Enable Multi-Agent Review</label>
|
|
182
|
+
<p className="hint">Allow a secondary model to review code written by the primary model.</p>
|
|
183
|
+
</div>
|
|
184
|
+
<label className="settings-toggle-switch">
|
|
185
|
+
<input
|
|
186
|
+
type="checkbox"
|
|
187
|
+
checked={config.enableAgentCollaboration}
|
|
188
|
+
onChange={(e) => updateField('enableAgentCollaboration', e.target.checked)}
|
|
189
|
+
/>
|
|
190
|
+
<span className="settings-toggle-slider"></span>
|
|
191
|
+
</label>
|
|
192
|
+
</div>
|
|
193
|
+
</section>
|
|
194
|
+
</div>
|
|
195
|
+
)
|
|
196
|
+
}
|