@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
|
@@ -1798,21 +1798,33 @@ export default function ChatPanel({
|
|
|
1798
1798
|
)}
|
|
1799
1799
|
|
|
1800
1800
|
<div className="chat-container">
|
|
1801
|
-
{interactions.map((interaction) =>
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
<
|
|
1801
|
+
{interactions.map((interaction) => {
|
|
1802
|
+
const isSystemEvent = interaction.provider === 'system' && interaction.model === 'provider_change';
|
|
1803
|
+
if (isSystemEvent) {
|
|
1804
|
+
return (
|
|
1805
|
+
<div key={interaction.id} className="system-event-divider">
|
|
1806
|
+
<div className="system-event-line" />
|
|
1807
|
+
<div className="system-event-pill">
|
|
1808
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
1809
|
+
<path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" />
|
|
1810
|
+
</svg>
|
|
1811
|
+
<span>{interaction.userText}</span>
|
|
1812
1812
|
</div>
|
|
1813
|
+
<div className="system-event-line" />
|
|
1813
1814
|
</div>
|
|
1814
|
-
)
|
|
1815
|
-
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
return (
|
|
1818
|
+
<div key={interaction.id} style={{ display: 'flex', flexDirection: 'column', gap: '8px', marginBottom: '16px' }}>
|
|
1819
|
+
{interaction.userText && (
|
|
1820
|
+
<div className="message user-message">
|
|
1821
|
+
<div className="bubble-wrapper">
|
|
1822
|
+
<div className="message-bubble">{renderFormattedMessage(interaction.userText)}</div>
|
|
1823
|
+
<div className="message-time"><span>{new Date(interaction.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span></div>
|
|
1824
|
+
</div>
|
|
1825
|
+
</div>
|
|
1826
|
+
)}
|
|
1827
|
+
<div className="message ai-message">
|
|
1816
1828
|
<div className="bubble-wrapper">
|
|
1817
1829
|
{renderCompletedActivity(interaction)}
|
|
1818
1830
|
{renderFileChanges(interaction)}
|
|
@@ -1858,7 +1870,8 @@ export default function ChatPanel({
|
|
|
1858
1870
|
</div>
|
|
1859
1871
|
</div>
|
|
1860
1872
|
</div>
|
|
1861
|
-
)
|
|
1873
|
+
);
|
|
1874
|
+
})}
|
|
1862
1875
|
|
|
1863
1876
|
{sending && (
|
|
1864
1877
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', marginBottom: '16px' }}>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
|
|
3
|
-
export type DashboardView = 'chat' | 'pictures' | 'model' | 'imagine'
|
|
3
|
+
export type DashboardView = 'chat' | 'pictures' | 'model' | '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 {
|
|
@@ -20,6 +22,8 @@ interface DashboardSidebarProps {
|
|
|
20
22
|
onDeleteConversation: (id: string) => void
|
|
21
23
|
onRenameConversation?: (id: string, newTitle: string) => void
|
|
22
24
|
onSetView: (view: DashboardView) => void
|
|
25
|
+
isSearchOpen: boolean
|
|
26
|
+
onSetSearchOpen: (open: boolean) => void
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
export default function DashboardSidebar({
|
|
@@ -34,6 +38,8 @@ export default function DashboardSidebar({
|
|
|
34
38
|
onDeleteConversation,
|
|
35
39
|
onRenameConversation,
|
|
36
40
|
onSetView,
|
|
41
|
+
isSearchOpen,
|
|
42
|
+
onSetSearchOpen,
|
|
37
43
|
}: DashboardSidebarProps) {
|
|
38
44
|
const [editingSessionId, setEditingSessionId] = useState<string | null>(null)
|
|
39
45
|
const [editTitleValue, setEditTitleValue] = useState('')
|
|
@@ -77,6 +83,16 @@ export default function DashboardSidebar({
|
|
|
77
83
|
<span>New Chat</span>
|
|
78
84
|
</button>
|
|
79
85
|
|
|
86
|
+
<button className="sidebar-top-action sidebar-search-btn" onClick={() => onSetSearchOpen(true)}>
|
|
87
|
+
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
88
|
+
<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">
|
|
89
|
+
<circle cx="11" cy="11" r="8"></circle>
|
|
90
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
91
|
+
</svg>
|
|
92
|
+
</span>
|
|
93
|
+
<span>Search Chats</span>
|
|
94
|
+
</button>
|
|
95
|
+
|
|
80
96
|
<button className={`sidebar-top-action ${view === 'chat' ? 'is-active' : ''}`} onClick={() => onSetView('chat')}>
|
|
81
97
|
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
82
98
|
<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">
|
|
@@ -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,
|
|
@@ -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()
|
|
@@ -664,6 +738,37 @@ export default function MintDashboard() {
|
|
|
664
738
|
}
|
|
665
739
|
}
|
|
666
740
|
|
|
741
|
+
function formatProviderChangeText(provider: string, model: string) {
|
|
742
|
+
let providerName = provider
|
|
743
|
+
if (provider === 'gemini') providerName = 'Gemini'
|
|
744
|
+
else if (provider === 'openai') providerName = 'OpenAI'
|
|
745
|
+
else if (provider === 'openrouter') providerName = 'OpenRouter'
|
|
746
|
+
else if (provider === 'deepseek') providerName = 'DeepSeek'
|
|
747
|
+
else if (provider === 'anthropic') providerName = 'Claude'
|
|
748
|
+
else if (provider === 'huggingface') providerName = 'HF'
|
|
749
|
+
else if (provider === 'local_openai') providerName = 'Local'
|
|
750
|
+
else if (provider === 'ollama') providerName = 'Ollama'
|
|
751
|
+
|
|
752
|
+
if (providerName && providerName === provider) {
|
|
753
|
+
providerName = providerName.charAt(0).toUpperCase() + providerName.slice(1)
|
|
754
|
+
}
|
|
755
|
+
return [providerName, model].filter(Boolean).join(' • ')
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function getActiveModelName(config: any, provider: string) {
|
|
759
|
+
switch (provider) {
|
|
760
|
+
case 'gemini': return config.geminiModel || 'gemini-1.5-flash'
|
|
761
|
+
case 'openai': return config.openaiModel || 'gpt-4o'
|
|
762
|
+
case 'openrouter': return config.openrouterModel || 'anthropic/claude-3.5-sonnet'
|
|
763
|
+
case 'deepseek': return config.deepseekModel || 'deepseek-chat'
|
|
764
|
+
case 'anthropic': return config.anthropicModel || 'claude-3-5-sonnet-20240620'
|
|
765
|
+
case 'huggingface': return config.hfModel || 'meta-llama/Meta-Llama-3-8B-Instruct'
|
|
766
|
+
case 'local_openai': return config.localModelName || 'llama3'
|
|
767
|
+
case 'ollama': return config.ollamaModel || 'llama3:latest'
|
|
768
|
+
default: return ''
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
667
772
|
async function changeProvider(provider: string) {
|
|
668
773
|
try {
|
|
669
774
|
const config = await window.settingsApi.getSettings()
|
|
@@ -671,6 +776,12 @@ export default function MintDashboard() {
|
|
|
671
776
|
await window.settingsApi.saveSettings(config)
|
|
672
777
|
setSettingsConfig(config)
|
|
673
778
|
setStatus(await getRuntimeStatus())
|
|
779
|
+
|
|
780
|
+
// Record system event in chat history
|
|
781
|
+
const activeModel = getActiveModelName(config, provider)
|
|
782
|
+
const displayName = formatProviderChangeText(provider, activeModel)
|
|
783
|
+
await saveSystemInteraction(conversationId, displayName, 'system', 'provider_change')
|
|
784
|
+
await refreshHistory()
|
|
674
785
|
} catch (reason) {
|
|
675
786
|
setError(errorMessage(reason))
|
|
676
787
|
}
|
|
@@ -700,6 +811,11 @@ export default function MintDashboard() {
|
|
|
700
811
|
await window.settingsApi.saveSettings(config)
|
|
701
812
|
setSettingsConfig(config)
|
|
702
813
|
setStatus(await getRuntimeStatus())
|
|
814
|
+
|
|
815
|
+
// Record system event in chat history
|
|
816
|
+
const displayName = formatProviderChangeText(provider, modelName)
|
|
817
|
+
await saveSystemInteraction(conversationId, displayName, 'system', 'provider_change')
|
|
818
|
+
await refreshHistory()
|
|
703
819
|
} catch (reason) {
|
|
704
820
|
setError(errorMessage(reason))
|
|
705
821
|
}
|
|
@@ -738,6 +854,8 @@ export default function MintDashboard() {
|
|
|
738
854
|
onDeleteConversation={deleteConversation}
|
|
739
855
|
onRenameConversation={renameConversation}
|
|
740
856
|
onSetView={changeView}
|
|
857
|
+
isSearchOpen={isSearchOpen}
|
|
858
|
+
onSetSearchOpen={setIsSearchOpen}
|
|
741
859
|
/>
|
|
742
860
|
<main className="assistant-workspace model-hidden">
|
|
743
861
|
<ChatPanel
|
|
@@ -804,6 +922,77 @@ export default function MintDashboard() {
|
|
|
804
922
|
<button onClick={() => setError('')} style={{ marginLeft: '12px', background: 'transparent', border: 0, color: 'white', cursor: 'pointer' }}>✕</button>
|
|
805
923
|
</div>
|
|
806
924
|
)}
|
|
925
|
+
|
|
926
|
+
{isSearchOpen && (
|
|
927
|
+
<div className="sidebar-search-modal-backdrop" onClick={() => setIsSearchOpen(false)}>
|
|
928
|
+
<div className="sidebar-search-modal" onClick={(e) => e.stopPropagation()}>
|
|
929
|
+
<div className="search-modal-header">
|
|
930
|
+
<span className="search-icon-wrapper">
|
|
931
|
+
<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">
|
|
932
|
+
<circle cx="11" cy="11" r="8"></circle>
|
|
933
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
934
|
+
</svg>
|
|
935
|
+
</span>
|
|
936
|
+
<input
|
|
937
|
+
type="text"
|
|
938
|
+
placeholder="Search chats..."
|
|
939
|
+
value={searchQuery}
|
|
940
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
941
|
+
autoFocus
|
|
942
|
+
/>
|
|
943
|
+
<button className="search-modal-close" onClick={() => setIsSearchOpen(false)}>
|
|
944
|
+
<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">
|
|
945
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
946
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
947
|
+
</svg>
|
|
948
|
+
</button>
|
|
949
|
+
</div>
|
|
950
|
+
|
|
951
|
+
<div className="search-modal-body">
|
|
952
|
+
<button
|
|
953
|
+
className="search-new-chat-btn"
|
|
954
|
+
onClick={() => {
|
|
955
|
+
clearHistory('New chat')
|
|
956
|
+
setIsSearchOpen(false)
|
|
957
|
+
}}
|
|
958
|
+
>
|
|
959
|
+
<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">
|
|
960
|
+
<line x1="12" y1="5" x2="12" y2="19"></line>
|
|
961
|
+
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
962
|
+
</svg>
|
|
963
|
+
<span>New Chat</span>
|
|
964
|
+
</button>
|
|
965
|
+
|
|
966
|
+
<div className="search-modal-results">
|
|
967
|
+
{Object.keys(groupedSearchSessions).length > 0 ? (
|
|
968
|
+
Object.entries(groupedSearchSessions).map(([groupName, sessions]) => (
|
|
969
|
+
<div key={groupName} className="search-results-group">
|
|
970
|
+
<div className="search-group-title">{groupName}</div>
|
|
971
|
+
{sessions.map((session) => (
|
|
972
|
+
<button
|
|
973
|
+
key={session.id}
|
|
974
|
+
className={`search-result-item ${session.id === conversationId ? 'active' : ''}`}
|
|
975
|
+
onClick={() => {
|
|
976
|
+
selectConversation(session.id)
|
|
977
|
+
setIsSearchOpen(false)
|
|
978
|
+
}}
|
|
979
|
+
>
|
|
980
|
+
<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">
|
|
981
|
+
<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>
|
|
982
|
+
</svg>
|
|
983
|
+
<span className="search-result-title">{session.title || 'New chat'}</span>
|
|
984
|
+
</button>
|
|
985
|
+
))}
|
|
986
|
+
</div>
|
|
987
|
+
))
|
|
988
|
+
) : (
|
|
989
|
+
<div className="search-no-results">No matching chats found</div>
|
|
990
|
+
)}
|
|
991
|
+
</div>
|
|
992
|
+
</div>
|
|
993
|
+
</div>
|
|
994
|
+
</div>
|
|
995
|
+
)}
|
|
807
996
|
</div>
|
|
808
997
|
)
|
|
809
998
|
}
|
|
@@ -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
|
+
}
|