@pheem49/mint 1.7.0 → 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-cli/src/agent.rs +1 -1
- package/crates/mint-cli/src/main.rs +47 -24
- package/crates/mint-cli/src/setup.rs +1 -1
- package/crates/mint-core/src/api_server.rs +64 -10
- package/crates/mint-core/src/chat.rs +79 -0
- package/crates/mint-core/src/lib.rs +1 -1
- package/crates/mint-core/src/plugins.rs +4 -1
- package/crates/mint-core/src/workflows.rs +8 -0
- package/package.json +13 -2
- 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 +84 -92
- package/src-tauri/tauri.conf.json +1 -1
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
1
|
+
import { useState, useEffect, useRef } from 'react'
|
|
2
2
|
|
|
3
|
-
export type DashboardView = 'chat' | 'pictures' | 'model' | '
|
|
3
|
+
export type DashboardView = 'chat' | 'pictures' | 'model' | 'workspace' | 'imagine' | 'workflows'
|
|
4
4
|
|
|
5
5
|
interface ChatSessionItem {
|
|
6
6
|
id: string
|
|
7
7
|
title: string
|
|
8
8
|
kind: string
|
|
9
|
+
createdAt?: string
|
|
10
|
+
updatedAt?: string
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
interface DashboardSidebarProps {
|
|
@@ -33,6 +35,8 @@ interface DashboardSidebarProps {
|
|
|
33
35
|
onSetInteractionEnabled: (enabled: boolean) => void
|
|
34
36
|
onSetShowInteractionGuide: (visible: boolean) => void
|
|
35
37
|
onShowToast: (message: string) => void
|
|
38
|
+
isSearchOpen: boolean
|
|
39
|
+
onSetSearchOpen: (open: boolean) => void
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
export default function DashboardSidebar({
|
|
@@ -60,9 +64,23 @@ export default function DashboardSidebar({
|
|
|
60
64
|
onSetInteractionEnabled,
|
|
61
65
|
onSetShowInteractionGuide,
|
|
62
66
|
onShowToast,
|
|
67
|
+
isSearchOpen,
|
|
68
|
+
onSetSearchOpen,
|
|
63
69
|
}: DashboardSidebarProps) {
|
|
64
70
|
const [editingSessionId, setEditingSessionId] = useState<string | null>(null)
|
|
65
71
|
const [editTitleValue, setEditTitleValue] = useState('')
|
|
72
|
+
const [isMoreOpen, setIsMoreOpen] = useState(false)
|
|
73
|
+
const moreContainerRef = useRef<HTMLDivElement>(null)
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
function handleClickOutside(event: MouseEvent) {
|
|
77
|
+
if (moreContainerRef.current && !moreContainerRef.current.contains(event.target as Node)) {
|
|
78
|
+
setIsMoreOpen(false)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
document.addEventListener('mousedown', handleClickOutside)
|
|
82
|
+
return () => document.removeEventListener('mousedown', handleClickOutside)
|
|
83
|
+
}, [])
|
|
66
84
|
|
|
67
85
|
const handleSaveRename = (id: string) => {
|
|
68
86
|
if (editTitleValue.trim() && editTitleValue.trim() !== chatSessions.find(s => s.id === id)?.title) {
|
|
@@ -115,6 +133,16 @@ export default function DashboardSidebar({
|
|
|
115
133
|
<span>New Chat</span>
|
|
116
134
|
</button>
|
|
117
135
|
|
|
136
|
+
<button className="sidebar-top-action sidebar-search-btn" onClick={() => onSetSearchOpen(true)}>
|
|
137
|
+
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
138
|
+
<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">
|
|
139
|
+
<circle cx="11" cy="11" r="8"></circle>
|
|
140
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
141
|
+
</svg>
|
|
142
|
+
</span>
|
|
143
|
+
<span>Search Chats</span>
|
|
144
|
+
</button>
|
|
145
|
+
|
|
118
146
|
<button className={`sidebar-top-action ${view === 'chat' ? 'is-active' : ''}`} onClick={() => onSetView('chat')}>
|
|
119
147
|
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
120
148
|
<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">
|
|
@@ -133,22 +161,14 @@ export default function DashboardSidebar({
|
|
|
133
161
|
</span>
|
|
134
162
|
<span>Pictures</span>
|
|
135
163
|
</button>
|
|
136
|
-
<button className={`sidebar-top-action ${view === '
|
|
137
|
-
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
138
|
-
<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">
|
|
139
|
-
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
|
|
140
|
-
</svg>
|
|
141
|
-
</span>
|
|
142
|
-
<span>Image Studio</span>
|
|
143
|
-
</button>
|
|
144
|
-
<button className={`sidebar-top-action ${view === 'workspeac' ? 'is-active' : ''}`} onClick={() => onSetView('workspeac')}>
|
|
164
|
+
<button className={`sidebar-top-action ${view === 'workspace' ? 'is-active' : ''}`} onClick={() => onSetView('workspace')}>
|
|
145
165
|
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
146
166
|
<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">
|
|
147
167
|
<path d="M3 6h7l2 2h9v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"></path>
|
|
148
168
|
<path d="M3 6v12"></path>
|
|
149
169
|
</svg>
|
|
150
170
|
</span>
|
|
151
|
-
<span>
|
|
171
|
+
<span>Workspace</span>
|
|
152
172
|
</button>
|
|
153
173
|
<button className={`sidebar-top-action ${modelVisible ? 'is-active' : ''}`} onClick={onToggleModel}>
|
|
154
174
|
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
@@ -160,6 +180,45 @@ export default function DashboardSidebar({
|
|
|
160
180
|
<span>Live2D Model</span>
|
|
161
181
|
</button>
|
|
162
182
|
|
|
183
|
+
<div className="sidebar-more-container" ref={moreContainerRef}>
|
|
184
|
+
<button className={`sidebar-top-action ${isMoreOpen || view === 'imagine' || view === 'workflows' ? 'is-active' : ''}`} onClick={() => setIsMoreOpen(!isMoreOpen)}>
|
|
185
|
+
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
186
|
+
<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">
|
|
187
|
+
<circle cx="12" cy="12" r="1.5"></circle>
|
|
188
|
+
<circle cx="19" cy="12" r="1.5"></circle>
|
|
189
|
+
<circle cx="5" cy="12" r="1.5"></circle>
|
|
190
|
+
</svg>
|
|
191
|
+
</span>
|
|
192
|
+
<span>More</span>
|
|
193
|
+
</button>
|
|
194
|
+
{isMoreOpen && (
|
|
195
|
+
<div className="sidebar-more-popover">
|
|
196
|
+
<button className={`popover-item ${view === 'imagine' ? 'active' : ''}`} onClick={() => { onSetView('imagine'); setIsMoreOpen(false); }}>
|
|
197
|
+
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
198
|
+
<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">
|
|
199
|
+
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
|
|
200
|
+
</svg>
|
|
201
|
+
</span>
|
|
202
|
+
<span>Image Studio</span>
|
|
203
|
+
</button>
|
|
204
|
+
<button className={`popover-item ${view === 'workflows' ? 'active' : ''}`} onClick={() => { onSetView('workflows'); setIsMoreOpen(false); }}>
|
|
205
|
+
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
206
|
+
<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">
|
|
207
|
+
<path d="M4 12h16"></path>
|
|
208
|
+
<path d="M12 4v16"></path>
|
|
209
|
+
<rect x="2" y="9" width="4" height="6" rx="1"></rect>
|
|
210
|
+
<rect x="18" y="9" width="4" height="6" rx="1"></rect>
|
|
211
|
+
<rect x="10" y="2" width="4" height="4" rx="1"></rect>
|
|
212
|
+
<rect x="10" y="18" width="4" height="4" rx="1"></rect>
|
|
213
|
+
</svg>
|
|
214
|
+
</span>
|
|
215
|
+
<span>Workflow (Beta)</span>
|
|
216
|
+
</button>
|
|
217
|
+
</div>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
|
|
163
222
|
{modelVisible && (
|
|
164
223
|
<div className="sidebar-model-controls">
|
|
165
224
|
<button
|
|
@@ -193,12 +193,16 @@ export default function ImageStudioPanel({ view, onRefreshPictures, onSendToChat
|
|
|
193
193
|
document.body.removeChild(a)
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
if (view !== 'imagine') return null
|
|
197
|
-
|
|
198
196
|
const modelOptions = PROVIDER_MODELS[selectedProvider] ?? []
|
|
199
197
|
|
|
200
198
|
return (
|
|
201
|
-
<div
|
|
199
|
+
<div
|
|
200
|
+
className={`img-studio ${view === 'imagine' ? 'is-visible' : ''}`}
|
|
201
|
+
id="image-studio-panel"
|
|
202
|
+
role="main"
|
|
203
|
+
aria-label="Image Studio"
|
|
204
|
+
aria-hidden={view !== 'imagine'}
|
|
205
|
+
>
|
|
202
206
|
{/* Header */}
|
|
203
207
|
<header className="img-studio-header">
|
|
204
208
|
<div className="img-studio-header-title">
|
|
@@ -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
|
+
}
|