@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
|
@@ -0,0 +1,948 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_CONFIG,
|
|
4
|
+
GEMINI_MODELS,
|
|
5
|
+
OPENAI_MODELS,
|
|
6
|
+
OPENROUTER_MODELS,
|
|
7
|
+
DEEPSEEK_MODELS,
|
|
8
|
+
ANTHROPIC_MODELS,
|
|
9
|
+
HF_MODELS,
|
|
10
|
+
LOCAL_MODELS
|
|
11
|
+
} from '../SettingsWindow'
|
|
12
|
+
|
|
13
|
+
interface GeneralTabProps {
|
|
14
|
+
config: typeof DEFAULT_CONFIG
|
|
15
|
+
updateField: (field: keyof typeof DEFAULT_CONFIG, value: any) => void
|
|
16
|
+
customGemini: string
|
|
17
|
+
setCustomGemini: (val: string) => void
|
|
18
|
+
customOpenAI: string
|
|
19
|
+
setCustomOpenAI: (val: string) => void
|
|
20
|
+
customOpenRouter: string
|
|
21
|
+
setCustomOpenRouter: (val: string) => void
|
|
22
|
+
customDeepSeek: string
|
|
23
|
+
setCustomDeepSeek: (val: string) => void
|
|
24
|
+
customAnthropic: string
|
|
25
|
+
setCustomAnthropic: (val: string) => void
|
|
26
|
+
customHF: string
|
|
27
|
+
setCustomHF: (val: string) => void
|
|
28
|
+
customLocal: string
|
|
29
|
+
setCustomLocal: (val: string) => void
|
|
30
|
+
customOllama: string
|
|
31
|
+
setCustomOllama: (val: string) => void
|
|
32
|
+
dynamicOllamaModels: string[]
|
|
33
|
+
updateAvailable: boolean
|
|
34
|
+
updateMessage: string
|
|
35
|
+
handleCheckUpdates: () => void
|
|
36
|
+
handleInstallUpdate: () => void
|
|
37
|
+
isDesktopApp?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default function GeneralTab({
|
|
41
|
+
config,
|
|
42
|
+
updateField,
|
|
43
|
+
customGemini,
|
|
44
|
+
setCustomGemini,
|
|
45
|
+
customOpenAI,
|
|
46
|
+
setCustomOpenAI,
|
|
47
|
+
customOpenRouter,
|
|
48
|
+
setCustomOpenRouter,
|
|
49
|
+
customDeepSeek,
|
|
50
|
+
setCustomDeepSeek,
|
|
51
|
+
customAnthropic,
|
|
52
|
+
setCustomAnthropic,
|
|
53
|
+
customHF,
|
|
54
|
+
setCustomHF,
|
|
55
|
+
customLocal,
|
|
56
|
+
setCustomLocal,
|
|
57
|
+
customOllama,
|
|
58
|
+
setCustomOllama,
|
|
59
|
+
dynamicOllamaModels,
|
|
60
|
+
updateAvailable,
|
|
61
|
+
updateMessage,
|
|
62
|
+
handleCheckUpdates,
|
|
63
|
+
handleInstallUpdate,
|
|
64
|
+
isDesktopApp = false
|
|
65
|
+
}: GeneralTabProps) {
|
|
66
|
+
return (
|
|
67
|
+
<div className="tab-pane active">
|
|
68
|
+
<section className="setting-section">
|
|
69
|
+
<div className="section-heading">
|
|
70
|
+
<div>
|
|
71
|
+
<p className="section-kicker">AI routing</p>
|
|
72
|
+
<h2 className="section-title">Provider & Model</h2>
|
|
73
|
+
</div>
|
|
74
|
+
<p className="section-description">Choose which AI backend Mint uses, then set the default model.</p>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div className="form-grid compact">
|
|
78
|
+
<div className="setting-row wide">
|
|
79
|
+
<label>Active Provider</label>
|
|
80
|
+
<select value={config.aiProvider} onChange={(e) => updateField('aiProvider', e.target.value)}>
|
|
81
|
+
<option value="gemini">Google Gemini (Cloud)</option>
|
|
82
|
+
<option value="anthropic">Anthropic Claude</option>
|
|
83
|
+
<option value="openai">OpenAI</option>
|
|
84
|
+
<option value="openrouter">OpenRouter</option>
|
|
85
|
+
<option value="deepseek">DeepSeek</option>
|
|
86
|
+
<option value="ollama">Ollama (Local / Private)</option>
|
|
87
|
+
<option value="huggingface">Hugging Face (Inference API)</option>
|
|
88
|
+
<option value="local_openai">Local (LM Studio / OpenAI Compatible)</option>
|
|
89
|
+
</select>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div className="provider-cards-container">
|
|
94
|
+
{/* Google Gemini Card */}
|
|
95
|
+
<div className={`provider-card ${config.aiProvider === 'gemini' ? 'active-provider' : ''}`}>
|
|
96
|
+
<div className="provider-card-header">
|
|
97
|
+
<div className="provider-card-title">
|
|
98
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
99
|
+
<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>
|
|
100
|
+
</svg>
|
|
101
|
+
Google Gemini (Cloud)
|
|
102
|
+
</div>
|
|
103
|
+
{config.aiProvider === 'gemini' && <span className="provider-active-badge">Active</span>}
|
|
104
|
+
</div>
|
|
105
|
+
<div className="provider-card-body">
|
|
106
|
+
<div className="setting-row">
|
|
107
|
+
<label>Gemini Model</label>
|
|
108
|
+
<select
|
|
109
|
+
value={GEMINI_MODELS.includes(config.geminiModel) ? config.geminiModel : 'custom'}
|
|
110
|
+
onChange={(e) => updateField('geminiModel', e.target.value)}
|
|
111
|
+
>
|
|
112
|
+
{GEMINI_MODELS.map(model => (
|
|
113
|
+
<option key={model} value={model}>{model}</option>
|
|
114
|
+
))}
|
|
115
|
+
<option value="custom">Custom...</option>
|
|
116
|
+
</select>
|
|
117
|
+
</div>
|
|
118
|
+
{(!GEMINI_MODELS.includes(config.geminiModel) || config.geminiModel === 'custom') && (
|
|
119
|
+
<div className="setting-row">
|
|
120
|
+
<label>Custom Gemini Model</label>
|
|
121
|
+
<input
|
|
122
|
+
type="text"
|
|
123
|
+
value={customGemini}
|
|
124
|
+
onChange={(e) => { setCustomGemini(e.target.value); updateField('geminiModel', 'custom') }}
|
|
125
|
+
placeholder="e.g. gemini-3.1-flash-lite-preview"
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
<div className="setting-row">
|
|
130
|
+
<label>Gemini API Key</label>
|
|
131
|
+
<input
|
|
132
|
+
type="password"
|
|
133
|
+
value={config.apiKey}
|
|
134
|
+
onChange={(e) => updateField('apiKey', e.target.value)}
|
|
135
|
+
placeholder="Enter Gemini API Key..."
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
{/* Anthropic Claude Card */}
|
|
142
|
+
<div className={`provider-card ${config.aiProvider === 'anthropic' ? 'active-provider' : ''}`}>
|
|
143
|
+
<div className="provider-card-header">
|
|
144
|
+
<div className="provider-card-title">
|
|
145
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
146
|
+
<path d="M4.5 16.5c-1.5 1.26-2.5 3.19-2.5 5.5h20c0-2.31-1-4.24-2.5-5.5"></path>
|
|
147
|
+
<path d="M12 2L2 22h20L12 2z"></path>
|
|
148
|
+
</svg>
|
|
149
|
+
Anthropic Claude
|
|
150
|
+
</div>
|
|
151
|
+
{config.aiProvider === 'anthropic' && <span className="provider-active-badge">Active</span>}
|
|
152
|
+
</div>
|
|
153
|
+
<div className="provider-card-body">
|
|
154
|
+
<div className="setting-row">
|
|
155
|
+
<label>Anthropic Model</label>
|
|
156
|
+
<select
|
|
157
|
+
value={ANTHROPIC_MODELS.includes(config.anthropicModel) ? config.anthropicModel : 'custom'}
|
|
158
|
+
onChange={(e) => updateField('anthropicModel', e.target.value)}
|
|
159
|
+
>
|
|
160
|
+
{ANTHROPIC_MODELS.map(model => (
|
|
161
|
+
<option key={model} value={model}>{model}</option>
|
|
162
|
+
))}
|
|
163
|
+
<option value="custom">Custom...</option>
|
|
164
|
+
</select>
|
|
165
|
+
</div>
|
|
166
|
+
{(!ANTHROPIC_MODELS.includes(config.anthropicModel) || config.anthropicModel === 'custom') && (
|
|
167
|
+
<div className="setting-row">
|
|
168
|
+
<label>Custom Anthropic Model</label>
|
|
169
|
+
<input
|
|
170
|
+
type="text"
|
|
171
|
+
value={customAnthropic}
|
|
172
|
+
onChange={(e) => { setCustomAnthropic(e.target.value); updateField('anthropicModel', 'custom') }}
|
|
173
|
+
placeholder="e.g. claude-3-5-sonnet-latest"
|
|
174
|
+
/>
|
|
175
|
+
</div>
|
|
176
|
+
)}
|
|
177
|
+
<div className="setting-row">
|
|
178
|
+
<label>Anthropic API Key</label>
|
|
179
|
+
<input
|
|
180
|
+
type="password"
|
|
181
|
+
value={config.anthropicApiKey}
|
|
182
|
+
onChange={(e) => updateField('anthropicApiKey', e.target.value)}
|
|
183
|
+
placeholder="Enter Anthropic API Key..."
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
{/* OpenAI Card */}
|
|
190
|
+
<div className={`provider-card ${config.aiProvider === 'openai' ? 'active-provider' : ''}`}>
|
|
191
|
+
<div className="provider-card-header">
|
|
192
|
+
<div className="provider-card-title">
|
|
193
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
194
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
195
|
+
<line x1="12" y1="2" x2="12" y2="22"></line>
|
|
196
|
+
<path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
|
|
197
|
+
</svg>
|
|
198
|
+
OpenAI
|
|
199
|
+
</div>
|
|
200
|
+
{config.aiProvider === 'openai' && <span className="provider-active-badge">Active</span>}
|
|
201
|
+
</div>
|
|
202
|
+
<div className="provider-card-body">
|
|
203
|
+
<div className="setting-row">
|
|
204
|
+
<label>OpenAI Model</label>
|
|
205
|
+
<select
|
|
206
|
+
value={OPENAI_MODELS.includes(config.openaiModel) ? config.openaiModel : 'custom'}
|
|
207
|
+
onChange={(e) => updateField('openaiModel', e.target.value)}
|
|
208
|
+
>
|
|
209
|
+
{OPENAI_MODELS.map(model => (
|
|
210
|
+
<option key={model} value={model}>{model}</option>
|
|
211
|
+
))}
|
|
212
|
+
<option value="custom">Custom...</option>
|
|
213
|
+
</select>
|
|
214
|
+
</div>
|
|
215
|
+
{(!OPENAI_MODELS.includes(config.openaiModel) || config.openaiModel === 'custom') && (
|
|
216
|
+
<div className="setting-row">
|
|
217
|
+
<label>Custom OpenAI Model</label>
|
|
218
|
+
<input
|
|
219
|
+
type="text"
|
|
220
|
+
value={customOpenAI}
|
|
221
|
+
onChange={(e) => { setCustomOpenAI(e.target.value); updateField('openaiModel', 'custom') }}
|
|
222
|
+
placeholder="e.g. gpt-4o"
|
|
223
|
+
/>
|
|
224
|
+
</div>
|
|
225
|
+
)}
|
|
226
|
+
<div className="setting-row">
|
|
227
|
+
<label>OpenAI API Key</label>
|
|
228
|
+
<input
|
|
229
|
+
type="password"
|
|
230
|
+
value={config.openaiApiKey}
|
|
231
|
+
onChange={(e) => updateField('openaiApiKey', e.target.value)}
|
|
232
|
+
placeholder="Enter OpenAI API Key..."
|
|
233
|
+
/>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
{/* OpenRouter Card */}
|
|
239
|
+
<div className={`provider-card ${config.aiProvider === 'openrouter' ? 'active-provider' : ''}`}>
|
|
240
|
+
<div className="provider-card-header">
|
|
241
|
+
<div className="provider-card-title">
|
|
242
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
243
|
+
<polyline points="16 3 21 3 21 8"></polyline>
|
|
244
|
+
<line x1="4" y1="20" x2="21" y2="3"></line>
|
|
245
|
+
<polyline points="21 16 21 21 16 21"></polyline>
|
|
246
|
+
<line x1="15" y1="15" x2="21" y2="21"></line>
|
|
247
|
+
<line x1="4" y1="4" x2="9" y2="9"></line>
|
|
248
|
+
</svg>
|
|
249
|
+
OpenRouter
|
|
250
|
+
</div>
|
|
251
|
+
{config.aiProvider === 'openrouter' && <span className="provider-active-badge">Active</span>}
|
|
252
|
+
</div>
|
|
253
|
+
<div className="provider-card-body">
|
|
254
|
+
<div className="setting-row">
|
|
255
|
+
<label>OpenRouter Model</label>
|
|
256
|
+
<select
|
|
257
|
+
value={OPENROUTER_MODELS.includes(config.openrouterModel) ? config.openrouterModel : 'custom'}
|
|
258
|
+
onChange={(e) => updateField('openrouterModel', e.target.value)}
|
|
259
|
+
>
|
|
260
|
+
{OPENROUTER_MODELS.map(model => (
|
|
261
|
+
<option key={model} value={model}>{model}</option>
|
|
262
|
+
))}
|
|
263
|
+
<option value="custom">Custom...</option>
|
|
264
|
+
</select>
|
|
265
|
+
</div>
|
|
266
|
+
{(!OPENROUTER_MODELS.includes(config.openrouterModel) || config.openrouterModel === 'custom') && (
|
|
267
|
+
<div className="setting-row">
|
|
268
|
+
<label>Custom OpenRouter Model</label>
|
|
269
|
+
<input
|
|
270
|
+
type="text"
|
|
271
|
+
value={customOpenRouter}
|
|
272
|
+
onChange={(e) => { setCustomOpenRouter(e.target.value); updateField('openrouterModel', 'custom') }}
|
|
273
|
+
placeholder="e.g. anthropic/claude-3.5-sonnet"
|
|
274
|
+
/>
|
|
275
|
+
</div>
|
|
276
|
+
)}
|
|
277
|
+
<div className="setting-row">
|
|
278
|
+
<label>OpenRouter API Key</label>
|
|
279
|
+
<input
|
|
280
|
+
type="password"
|
|
281
|
+
value={config.openrouterApiKey}
|
|
282
|
+
onChange={(e) => updateField('openrouterApiKey', e.target.value)}
|
|
283
|
+
placeholder="Enter OpenRouter API Key..."
|
|
284
|
+
/>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
{/* DeepSeek Card */}
|
|
290
|
+
<div className={`provider-card ${config.aiProvider === 'deepseek' ? 'active-provider' : ''}`}>
|
|
291
|
+
<div className="provider-card-header">
|
|
292
|
+
<div className="provider-card-title">
|
|
293
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
294
|
+
<circle cx="11" cy="11" r="8"></circle>
|
|
295
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
296
|
+
</svg>
|
|
297
|
+
DeepSeek
|
|
298
|
+
</div>
|
|
299
|
+
{config.aiProvider === 'deepseek' && <span className="provider-active-badge">Active</span>}
|
|
300
|
+
</div>
|
|
301
|
+
<div className="provider-card-body">
|
|
302
|
+
<div className="setting-row">
|
|
303
|
+
<label>DeepSeek Model</label>
|
|
304
|
+
<select
|
|
305
|
+
value={DEEPSEEK_MODELS.includes(config.deepseekModel) ? config.deepseekModel : 'custom'}
|
|
306
|
+
onChange={(e) => updateField('deepseekModel', e.target.value)}
|
|
307
|
+
>
|
|
308
|
+
{DEEPSEEK_MODELS.map(model => (
|
|
309
|
+
<option key={model} value={model}>{model}</option>
|
|
310
|
+
))}
|
|
311
|
+
<option value="custom">Custom...</option>
|
|
312
|
+
</select>
|
|
313
|
+
</div>
|
|
314
|
+
{(!DEEPSEEK_MODELS.includes(config.deepseekModel) || config.deepseekModel === 'custom') && (
|
|
315
|
+
<div className="setting-row">
|
|
316
|
+
<label>Custom DeepSeek Model</label>
|
|
317
|
+
<input
|
|
318
|
+
type="text"
|
|
319
|
+
value={customDeepSeek}
|
|
320
|
+
onChange={(e) => { setCustomDeepSeek(e.target.value); updateField('deepseekModel', 'custom') }}
|
|
321
|
+
placeholder="e.g. deepseek-v4-pro"
|
|
322
|
+
/>
|
|
323
|
+
</div>
|
|
324
|
+
)}
|
|
325
|
+
<div className="setting-row">
|
|
326
|
+
<label>DeepSeek API Key</label>
|
|
327
|
+
<input
|
|
328
|
+
type="password"
|
|
329
|
+
value={config.deepseekApiKey}
|
|
330
|
+
onChange={(e) => updateField('deepseekApiKey', e.target.value)}
|
|
331
|
+
placeholder="Enter DeepSeek API Key..."
|
|
332
|
+
/>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
{/* Hugging Face Card */}
|
|
338
|
+
<div className={`provider-card ${config.aiProvider === 'huggingface' ? 'active-provider' : ''}`}>
|
|
339
|
+
<div className="provider-card-header">
|
|
340
|
+
<div className="provider-card-title">
|
|
341
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
342
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
343
|
+
<path d="M8 14s1.5 2 4 2 4-2 4-2"></path>
|
|
344
|
+
<line x1="9" y1="9" x2="9.01" y2="9"></line>
|
|
345
|
+
<line x1="15" y1="9" x2="15.01" y2="9"></line>
|
|
346
|
+
</svg>
|
|
347
|
+
Hugging Face (Inference API)
|
|
348
|
+
</div>
|
|
349
|
+
{config.aiProvider === 'huggingface' && <span className="provider-active-badge">Active</span>}
|
|
350
|
+
</div>
|
|
351
|
+
<div className="provider-card-body">
|
|
352
|
+
<div className="setting-row">
|
|
353
|
+
<label>Hugging Face Model</label>
|
|
354
|
+
<select
|
|
355
|
+
value={HF_MODELS.includes(config.hfModel) ? config.hfModel : 'custom'}
|
|
356
|
+
onChange={(e) => updateField('hfModel', e.target.value)}
|
|
357
|
+
>
|
|
358
|
+
{HF_MODELS.map(model => (
|
|
359
|
+
<option key={model} value={model}>{model}</option>
|
|
360
|
+
))}
|
|
361
|
+
<option value="custom">Custom...</option>
|
|
362
|
+
</select>
|
|
363
|
+
</div>
|
|
364
|
+
{(!HF_MODELS.includes(config.hfModel) || config.hfModel === 'custom') && (
|
|
365
|
+
<div className="setting-row">
|
|
366
|
+
<label>Custom Hugging Face Model</label>
|
|
367
|
+
<input
|
|
368
|
+
type="text"
|
|
369
|
+
value={customHF}
|
|
370
|
+
onChange={(e) => { setCustomHF(e.target.value); updateField('hfModel', 'custom') }}
|
|
371
|
+
placeholder="e.g. meta-llama/Meta-Llama-3-8B-Instruct"
|
|
372
|
+
/>
|
|
373
|
+
</div>
|
|
374
|
+
)}
|
|
375
|
+
<div className="setting-row">
|
|
376
|
+
<label>Hugging Face API Key</label>
|
|
377
|
+
<input
|
|
378
|
+
type="password"
|
|
379
|
+
value={config.hfApiKey}
|
|
380
|
+
onChange={(e) => updateField('hfApiKey', e.target.value)}
|
|
381
|
+
placeholder="Enter Hugging Face API Key..."
|
|
382
|
+
/>
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
{/* LM Studio Card */}
|
|
388
|
+
<div className={`provider-card ${config.aiProvider === 'local_openai' ? 'active-provider' : ''}`}>
|
|
389
|
+
<div className="provider-card-header">
|
|
390
|
+
<div className="provider-card-title">
|
|
391
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
392
|
+
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
|
|
393
|
+
<line x1="8" y1="21" x2="16" y2="21"></line>
|
|
394
|
+
<line x1="12" y1="17" x2="12" y2="21"></line>
|
|
395
|
+
</svg>
|
|
396
|
+
LM Studio / Local OpenAI
|
|
397
|
+
</div>
|
|
398
|
+
{config.aiProvider === 'local_openai' && <span className="provider-active-badge">Active</span>}
|
|
399
|
+
</div>
|
|
400
|
+
<div className="provider-card-body">
|
|
401
|
+
<div className="setting-row">
|
|
402
|
+
<label>LM Studio Model</label>
|
|
403
|
+
<select
|
|
404
|
+
value={LOCAL_MODELS.includes(config.localModelName) ? config.localModelName : 'custom'}
|
|
405
|
+
onChange={(e) => updateField('localModelName', e.target.value)}
|
|
406
|
+
>
|
|
407
|
+
{LOCAL_MODELS.map(model => (
|
|
408
|
+
<option key={model} value={model}>{model}</option>
|
|
409
|
+
))}
|
|
410
|
+
<option value="custom">Custom...</option>
|
|
411
|
+
</select>
|
|
412
|
+
</div>
|
|
413
|
+
{(!LOCAL_MODELS.includes(config.localModelName) || config.localModelName === 'custom') && (
|
|
414
|
+
<div className="setting-row">
|
|
415
|
+
<label>Custom LM Studio Model</label>
|
|
416
|
+
<input
|
|
417
|
+
type="text"
|
|
418
|
+
value={customLocal}
|
|
419
|
+
onChange={(e) => { setCustomLocal(e.target.value); updateField('localModelName', 'custom') }}
|
|
420
|
+
placeholder="e.g. local-model"
|
|
421
|
+
/>
|
|
422
|
+
</div>
|
|
423
|
+
)}
|
|
424
|
+
<div className="setting-row">
|
|
425
|
+
<label>LM Studio Base URL</label>
|
|
426
|
+
<input
|
|
427
|
+
type="text"
|
|
428
|
+
value={config.localApiBaseUrl}
|
|
429
|
+
onChange={(e) => updateField('localApiBaseUrl', e.target.value)}
|
|
430
|
+
placeholder="e.g. http://localhost:1234/v1"
|
|
431
|
+
/>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
</div>
|
|
435
|
+
|
|
436
|
+
{/* Ollama Card */}
|
|
437
|
+
<div className={`provider-card ${config.aiProvider === 'ollama' ? 'active-provider' : ''}`}>
|
|
438
|
+
<div className="provider-card-header">
|
|
439
|
+
<div className="provider-card-title">
|
|
440
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
441
|
+
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
|
442
|
+
</svg>
|
|
443
|
+
Ollama (Local)
|
|
444
|
+
</div>
|
|
445
|
+
{config.aiProvider === 'ollama' && <span className="provider-active-badge">Active</span>}
|
|
446
|
+
</div>
|
|
447
|
+
<div className="provider-card-body">
|
|
448
|
+
<div className="setting-row">
|
|
449
|
+
<label>Ollama Model</label>
|
|
450
|
+
<select
|
|
451
|
+
value={dynamicOllamaModels.includes(config.ollamaModel) ? config.ollamaModel : 'custom'}
|
|
452
|
+
onChange={(e) => updateField('ollamaModel', e.target.value)}
|
|
453
|
+
>
|
|
454
|
+
{dynamicOllamaModels.map(model => (
|
|
455
|
+
<option key={model} value={model}>{model}</option>
|
|
456
|
+
))}
|
|
457
|
+
{!dynamicOllamaModels.includes(config.ollamaModel) && config.ollamaModel && config.ollamaModel !== 'custom' && (
|
|
458
|
+
<option value={config.ollamaModel}>{config.ollamaModel}</option>
|
|
459
|
+
)}
|
|
460
|
+
<option value="custom">Custom...</option>
|
|
461
|
+
</select>
|
|
462
|
+
</div>
|
|
463
|
+
{(!dynamicOllamaModels.includes(config.ollamaModel) || config.ollamaModel === 'custom') && (
|
|
464
|
+
<div className="setting-row">
|
|
465
|
+
<label>Custom Ollama Model</label>
|
|
466
|
+
<input
|
|
467
|
+
type="text"
|
|
468
|
+
value={customOllama}
|
|
469
|
+
onChange={(e) => { setCustomOllama(e.target.value); updateField('ollamaModel', 'custom') }}
|
|
470
|
+
placeholder="e.g. llama3:latest"
|
|
471
|
+
/>
|
|
472
|
+
</div>
|
|
473
|
+
)}
|
|
474
|
+
<div className="setting-row">
|
|
475
|
+
<label>Ollama Host</label>
|
|
476
|
+
<input
|
|
477
|
+
type="text"
|
|
478
|
+
value={config.ollamaHost}
|
|
479
|
+
onChange={(e) => updateField('ollamaHost', e.target.value)}
|
|
480
|
+
placeholder="e.g. http://localhost:11434"
|
|
481
|
+
/>
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
</div>
|
|
486
|
+
</section>
|
|
487
|
+
|
|
488
|
+
{/* ── Search ── */}
|
|
489
|
+
<section className="setting-section">
|
|
490
|
+
<div className="section-heading">
|
|
491
|
+
<div>
|
|
492
|
+
<p className="section-kicker">Web search</p>
|
|
493
|
+
<h2 className="section-title">Search</h2>
|
|
494
|
+
</div>
|
|
495
|
+
<p className="section-description">Choose which search API Mint uses when performing web lookups.</p>
|
|
496
|
+
</div>
|
|
497
|
+
<div className="provider-cards-container">
|
|
498
|
+
{/* Brave Search */}
|
|
499
|
+
<div className={`provider-card ${config.searchProvider === 'brave' ? 'active-provider' : ''}`}>
|
|
500
|
+
<div className="provider-card-header">
|
|
501
|
+
<div className="provider-card-title">
|
|
502
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
503
|
+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
|
504
|
+
</svg>
|
|
505
|
+
Brave Search API
|
|
506
|
+
</div>
|
|
507
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
508
|
+
{config.searchProvider === 'brave' && <span className="provider-active-badge">Active</span>}
|
|
509
|
+
<label className="settings-toggle-switch" style={{ marginBottom: 0 }}>
|
|
510
|
+
<input type="radio" name="searchProvider" style={{ display: 'none' }} checked={config.searchProvider === 'brave'} onChange={() => updateField('searchProvider', 'brave')} />
|
|
511
|
+
<span className="settings-toggle-slider" style={{ cursor: 'pointer' }} onClick={() => updateField('searchProvider', 'brave')} />
|
|
512
|
+
</label>
|
|
513
|
+
</div>
|
|
514
|
+
</div>
|
|
515
|
+
<div className="provider-card-body">
|
|
516
|
+
<div className="setting-row">
|
|
517
|
+
<label>Brave Search API Key</label>
|
|
518
|
+
<input
|
|
519
|
+
type="password"
|
|
520
|
+
value={config.braveSearchApiKey}
|
|
521
|
+
onChange={(e) => updateField('braveSearchApiKey', e.target.value)}
|
|
522
|
+
placeholder="Enter Brave Search API Key..."
|
|
523
|
+
/>
|
|
524
|
+
</div>
|
|
525
|
+
</div>
|
|
526
|
+
</div>
|
|
527
|
+
|
|
528
|
+
{/* Google Search */}
|
|
529
|
+
<div className={`provider-card ${config.searchProvider === 'google' ? 'active-provider' : ''}`}>
|
|
530
|
+
<div className="provider-card-header">
|
|
531
|
+
<div className="provider-card-title">
|
|
532
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
533
|
+
<circle cx="11" cy="11" r="8"/>
|
|
534
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
|
535
|
+
</svg>
|
|
536
|
+
Google Search API
|
|
537
|
+
</div>
|
|
538
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
539
|
+
{config.searchProvider === 'google' && <span className="provider-active-badge">Active</span>}
|
|
540
|
+
<label className="settings-toggle-switch" style={{ marginBottom: 0 }}>
|
|
541
|
+
<input type="radio" name="searchProvider" style={{ display: 'none' }} checked={config.searchProvider === 'google'} onChange={() => updateField('searchProvider', 'google')} />
|
|
542
|
+
<span className="settings-toggle-slider" style={{ cursor: 'pointer' }} onClick={() => updateField('searchProvider', 'google')} />
|
|
543
|
+
</label>
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
<div className="provider-card-body">
|
|
547
|
+
<div className="setting-row">
|
|
548
|
+
<label>Google Search API Key</label>
|
|
549
|
+
<input
|
|
550
|
+
type="password"
|
|
551
|
+
value={config.googleSearchApiKey}
|
|
552
|
+
onChange={(e) => updateField('googleSearchApiKey', e.target.value)}
|
|
553
|
+
placeholder="Enter Google Search API Key..."
|
|
554
|
+
/>
|
|
555
|
+
</div>
|
|
556
|
+
<div className="setting-row">
|
|
557
|
+
<label>Google Search Engine ID (CX)</label>
|
|
558
|
+
<input
|
|
559
|
+
type="text"
|
|
560
|
+
value={config.googleSearchCx}
|
|
561
|
+
onChange={(e) => updateField('googleSearchCx', e.target.value)}
|
|
562
|
+
placeholder="Enter Google Custom Search Engine ID (CX)..."
|
|
563
|
+
/>
|
|
564
|
+
</div>
|
|
565
|
+
</div>
|
|
566
|
+
</div>
|
|
567
|
+
</div>
|
|
568
|
+
</section>
|
|
569
|
+
|
|
570
|
+
{/* ── Productivity ── */}
|
|
571
|
+
<section className="setting-section">
|
|
572
|
+
<div className="section-heading">
|
|
573
|
+
<div>
|
|
574
|
+
<p className="section-kicker">Productivity integrations</p>
|
|
575
|
+
<h2 className="section-title">Productivity</h2>
|
|
576
|
+
</div>
|
|
577
|
+
<p className="section-description">Enable and configure Gmail, Google Calendar, and Notion plugins directly.</p>
|
|
578
|
+
</div>
|
|
579
|
+
<div className="provider-cards-container">
|
|
580
|
+
{/* Gmail */}
|
|
581
|
+
<div className={`provider-card ${config.pluginGmailEnabled ? 'active-provider' : ''}`}>
|
|
582
|
+
<div className="provider-card-header">
|
|
583
|
+
<div className="provider-card-title">
|
|
584
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
585
|
+
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
|
|
586
|
+
<polyline points="22,6 12,13 2,6"/>
|
|
587
|
+
</svg>
|
|
588
|
+
Gmail Plugin
|
|
589
|
+
</div>
|
|
590
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
591
|
+
{config.pluginGmailEnabled && <span className="provider-active-badge">Enabled</span>}
|
|
592
|
+
<label className="settings-toggle-switch">
|
|
593
|
+
<input type="checkbox" checked={config.pluginGmailEnabled} onChange={(e) => updateField('pluginGmailEnabled', e.target.checked)} />
|
|
594
|
+
<span className="settings-toggle-slider" />
|
|
595
|
+
</label>
|
|
596
|
+
</div>
|
|
597
|
+
</div>
|
|
598
|
+
<div className="provider-card-body">
|
|
599
|
+
<div className="setting-row">
|
|
600
|
+
<label>Gmail Client ID</label>
|
|
601
|
+
<input
|
|
602
|
+
type="text"
|
|
603
|
+
placeholder="Enter Gmail Client ID..."
|
|
604
|
+
value={config.gmailClientId || ''}
|
|
605
|
+
onChange={(e) => updateField('gmailClientId', e.target.value)}
|
|
606
|
+
/>
|
|
607
|
+
</div>
|
|
608
|
+
<div className="setting-row">
|
|
609
|
+
<label>Gmail Client Secret</label>
|
|
610
|
+
<input
|
|
611
|
+
type="password"
|
|
612
|
+
placeholder="Enter Gmail Client Secret..."
|
|
613
|
+
value={config.gmailClientSecret || ''}
|
|
614
|
+
onChange={(e) => updateField('gmailClientSecret', e.target.value)}
|
|
615
|
+
/>
|
|
616
|
+
</div>
|
|
617
|
+
<div className="setting-row">
|
|
618
|
+
<label>Gmail Refresh Token</label>
|
|
619
|
+
<input
|
|
620
|
+
type="password"
|
|
621
|
+
placeholder="Enter Gmail Refresh Token..."
|
|
622
|
+
value={config.gmailRefreshToken || ''}
|
|
623
|
+
onChange={(e) => updateField('gmailRefreshToken', e.target.value)}
|
|
624
|
+
/>
|
|
625
|
+
</div>
|
|
626
|
+
</div>
|
|
627
|
+
</div>
|
|
628
|
+
|
|
629
|
+
{/* Google Calendar */}
|
|
630
|
+
<div className={`provider-card ${config.pluginCalendarEnabled ? 'active-provider' : ''}`}>
|
|
631
|
+
<div className="provider-card-header">
|
|
632
|
+
<div className="provider-card-title">
|
|
633
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
634
|
+
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
|
|
635
|
+
<line x1="16" y1="2" x2="16" y2="6"/>
|
|
636
|
+
<line x1="8" y1="2" x2="8" y2="6"/>
|
|
637
|
+
<line x1="3" y1="10" x2="21" y2="10"/>
|
|
638
|
+
</svg>
|
|
639
|
+
Google Calendar Plugin
|
|
640
|
+
</div>
|
|
641
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
642
|
+
{config.pluginCalendarEnabled && <span className="provider-active-badge">Enabled</span>}
|
|
643
|
+
<label className="settings-toggle-switch">
|
|
644
|
+
<input type="checkbox" checked={config.pluginCalendarEnabled} onChange={(e) => updateField('pluginCalendarEnabled', e.target.checked)} />
|
|
645
|
+
<span className="settings-toggle-slider" />
|
|
646
|
+
</label>
|
|
647
|
+
</div>
|
|
648
|
+
</div>
|
|
649
|
+
<div className="provider-card-body">
|
|
650
|
+
<div className="setting-row">
|
|
651
|
+
<label>Google Client ID</label>
|
|
652
|
+
<input
|
|
653
|
+
type="text"
|
|
654
|
+
placeholder="Enter Google Client ID..."
|
|
655
|
+
value={config.googleCalendarClientId || ''}
|
|
656
|
+
onChange={(e) => updateField('googleCalendarClientId', e.target.value)}
|
|
657
|
+
/>
|
|
658
|
+
</div>
|
|
659
|
+
<div className="setting-row">
|
|
660
|
+
<label>Google Client Secret</label>
|
|
661
|
+
<input
|
|
662
|
+
type="password"
|
|
663
|
+
placeholder="Enter Google Client Secret..."
|
|
664
|
+
value={config.googleCalendarClientSecret || ''}
|
|
665
|
+
onChange={(e) => updateField('googleCalendarClientSecret', e.target.value)}
|
|
666
|
+
/>
|
|
667
|
+
</div>
|
|
668
|
+
<div className="setting-row">
|
|
669
|
+
<label>Google Refresh Token</label>
|
|
670
|
+
<input
|
|
671
|
+
type="password"
|
|
672
|
+
placeholder="Enter Google Refresh Token..."
|
|
673
|
+
value={config.googleCalendarRefreshToken || ''}
|
|
674
|
+
onChange={(e) => updateField('googleCalendarRefreshToken', e.target.value)}
|
|
675
|
+
/>
|
|
676
|
+
</div>
|
|
677
|
+
</div>
|
|
678
|
+
</div>
|
|
679
|
+
|
|
680
|
+
{/* Notion */}
|
|
681
|
+
<div className={`provider-card ${config.pluginNotionEnabled ? 'active-provider' : ''}`}>
|
|
682
|
+
<div className="provider-card-header">
|
|
683
|
+
<div className="provider-card-title">
|
|
684
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
685
|
+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
|
686
|
+
<polyline points="14 2 14 8 20 8"/>
|
|
687
|
+
<line x1="16" y1="13" x2="8" y2="13"/>
|
|
688
|
+
<line x1="16" y1="17" x2="8" y2="17"/>
|
|
689
|
+
<polyline points="10 9 9 9 8 9"/>
|
|
690
|
+
</svg>
|
|
691
|
+
Notion Plugin
|
|
692
|
+
</div>
|
|
693
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
694
|
+
{config.pluginNotionEnabled && <span className="provider-active-badge">Enabled</span>}
|
|
695
|
+
<label className="settings-toggle-switch">
|
|
696
|
+
<input type="checkbox" checked={config.pluginNotionEnabled} onChange={(e) => updateField('pluginNotionEnabled', e.target.checked)} />
|
|
697
|
+
<span className="settings-toggle-slider" />
|
|
698
|
+
</label>
|
|
699
|
+
</div>
|
|
700
|
+
</div>
|
|
701
|
+
<div className="provider-card-body">
|
|
702
|
+
<div className="setting-row">
|
|
703
|
+
<label>Notion API Key</label>
|
|
704
|
+
<input
|
|
705
|
+
type="password"
|
|
706
|
+
placeholder="secret_..."
|
|
707
|
+
value={config.notionApiKey || ''}
|
|
708
|
+
onChange={(e) => updateField('notionApiKey', e.target.value)}
|
|
709
|
+
/>
|
|
710
|
+
</div>
|
|
711
|
+
<div className="setting-row">
|
|
712
|
+
<label>Notion Database ID</label>
|
|
713
|
+
<input
|
|
714
|
+
type="text"
|
|
715
|
+
placeholder="Enter Notion Database ID..."
|
|
716
|
+
value={config.notionDatabaseId || ''}
|
|
717
|
+
onChange={(e) => updateField('notionDatabaseId', e.target.value)}
|
|
718
|
+
/>
|
|
719
|
+
</div>
|
|
720
|
+
</div>
|
|
721
|
+
</div>
|
|
722
|
+
</div>
|
|
723
|
+
</section>
|
|
724
|
+
|
|
725
|
+
{/* ── Image Generation ── */}
|
|
726
|
+
<section className="setting-section">
|
|
727
|
+
<div className="section-heading">
|
|
728
|
+
<div>
|
|
729
|
+
<p className="section-kicker">AI image creation</p>
|
|
730
|
+
<h2 className="section-title">Image Generation</h2>
|
|
731
|
+
</div>
|
|
732
|
+
<p className="section-description">Choose which image generation provider Mint uses.</p>
|
|
733
|
+
</div>
|
|
734
|
+
<div className="provider-cards-container">
|
|
735
|
+
{/* Gemini Images */}
|
|
736
|
+
<div className={`provider-card ${config.imageGenProvider === 'gemini' ? 'active-provider' : ''}`}>
|
|
737
|
+
<div className="provider-card-header">
|
|
738
|
+
<div className="provider-card-title">
|
|
739
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
740
|
+
<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"/>
|
|
741
|
+
</svg>
|
|
742
|
+
Google NanoBanana (Gemini Images)
|
|
743
|
+
</div>
|
|
744
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
745
|
+
{config.imageGenProvider === 'gemini' && <span className="provider-active-badge">Active</span>}
|
|
746
|
+
<label className="settings-toggle-switch" style={{ marginBottom: 0 }}>
|
|
747
|
+
<input type="radio" name="imageGenProvider" style={{ display: 'none' }} checked={config.imageGenProvider === 'gemini'} onChange={() => updateField('imageGenProvider', 'gemini')} />
|
|
748
|
+
<span className="settings-toggle-slider" style={{ cursor: 'pointer' }} onClick={() => updateField('imageGenProvider', 'gemini')} />
|
|
749
|
+
</label>
|
|
750
|
+
</div>
|
|
751
|
+
</div>
|
|
752
|
+
<div className="provider-card-body">
|
|
753
|
+
<p className="hint" style={{ margin: 0 }}>Uses your Gemini API key — no extra key needed.</p>
|
|
754
|
+
</div>
|
|
755
|
+
</div>
|
|
756
|
+
|
|
757
|
+
{/* DALL·E */}
|
|
758
|
+
<div className={`provider-card ${config.imageGenProvider === 'dalle' ? 'active-provider' : ''}`}>
|
|
759
|
+
<div className="provider-card-header">
|
|
760
|
+
<div className="provider-card-title">
|
|
761
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
762
|
+
<circle cx="12" cy="12" r="10"/>
|
|
763
|
+
<line x1="12" y1="2" x2="12" y2="22"/>
|
|
764
|
+
<path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>
|
|
765
|
+
</svg>
|
|
766
|
+
OpenAI DALL·E
|
|
767
|
+
</div>
|
|
768
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
769
|
+
{config.imageGenProvider === 'dalle' && <span className="provider-active-badge">Active</span>}
|
|
770
|
+
<label className="settings-toggle-switch" style={{ marginBottom: 0 }}>
|
|
771
|
+
<input type="radio" name="imageGenProvider" style={{ display: 'none' }} checked={config.imageGenProvider === 'dalle'} onChange={() => updateField('imageGenProvider', 'dalle')} />
|
|
772
|
+
<span className="settings-toggle-slider" style={{ cursor: 'pointer' }} onClick={() => updateField('imageGenProvider', 'dalle')} />
|
|
773
|
+
</label>
|
|
774
|
+
</div>
|
|
775
|
+
</div>
|
|
776
|
+
<div className="provider-card-body">
|
|
777
|
+
<p className="hint" style={{ margin: 0 }}>Uses your OpenAI API key — no extra key needed.</p>
|
|
778
|
+
</div>
|
|
779
|
+
</div>
|
|
780
|
+
|
|
781
|
+
{/* Stability AI */}
|
|
782
|
+
<div className={`provider-card ${config.imageGenProvider === 'stability' ? 'active-provider' : ''}`}>
|
|
783
|
+
<div className="provider-card-header">
|
|
784
|
+
<div className="provider-card-title">
|
|
785
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
786
|
+
<path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10z"/>
|
|
787
|
+
<path d="M8 12l2 2 4-4"/>
|
|
788
|
+
</svg>
|
|
789
|
+
Stability AI (Stable Diffusion)
|
|
790
|
+
</div>
|
|
791
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
792
|
+
{config.imageGenProvider === 'stability' && <span className="provider-active-badge">Active</span>}
|
|
793
|
+
<label className="settings-toggle-switch" style={{ marginBottom: 0 }}>
|
|
794
|
+
<input type="radio" name="imageGenProvider" style={{ display: 'none' }} checked={config.imageGenProvider === 'stability'} onChange={() => updateField('imageGenProvider', 'stability')} />
|
|
795
|
+
<span className="settings-toggle-slider" style={{ cursor: 'pointer' }} onClick={() => updateField('imageGenProvider', 'stability')} />
|
|
796
|
+
</label>
|
|
797
|
+
</div>
|
|
798
|
+
</div>
|
|
799
|
+
<div className="provider-card-body">
|
|
800
|
+
<div className="setting-row">
|
|
801
|
+
<label>Stability AI API Key</label>
|
|
802
|
+
<input
|
|
803
|
+
type="password"
|
|
804
|
+
value={config.stabilityApiKey}
|
|
805
|
+
onChange={(e) => updateField('stabilityApiKey', e.target.value)}
|
|
806
|
+
placeholder="Enter Stability AI API Key..."
|
|
807
|
+
/>
|
|
808
|
+
</div>
|
|
809
|
+
</div>
|
|
810
|
+
</div>
|
|
811
|
+
|
|
812
|
+
{/* Ideogram */}
|
|
813
|
+
<div className={`provider-card ${config.imageGenProvider === 'ideogram' ? 'active-provider' : ''}`}>
|
|
814
|
+
<div className="provider-card-header">
|
|
815
|
+
<div className="provider-card-title">
|
|
816
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
817
|
+
<rect x="3" y="3" width="18" height="18" rx="2"/>
|
|
818
|
+
<circle cx="8.5" cy="8.5" r="1.5"/>
|
|
819
|
+
<polyline points="21 15 16 10 5 21"/>
|
|
820
|
+
</svg>
|
|
821
|
+
Ideogram v3
|
|
822
|
+
</div>
|
|
823
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
824
|
+
{config.imageGenProvider === 'ideogram' && <span className="provider-active-badge">Active</span>}
|
|
825
|
+
<label className="settings-toggle-switch" style={{ marginBottom: 0 }}>
|
|
826
|
+
<input type="radio" name="imageGenProvider" style={{ display: 'none' }} checked={config.imageGenProvider === 'ideogram'} onChange={() => updateField('imageGenProvider', 'ideogram')} />
|
|
827
|
+
<span className="settings-toggle-slider" style={{ cursor: 'pointer' }} onClick={() => updateField('imageGenProvider', 'ideogram')} />
|
|
828
|
+
</label>
|
|
829
|
+
</div>
|
|
830
|
+
</div>
|
|
831
|
+
<div className="provider-card-body">
|
|
832
|
+
<div className="setting-row">
|
|
833
|
+
<label>Ideogram API Key</label>
|
|
834
|
+
<input
|
|
835
|
+
type="password"
|
|
836
|
+
value={config.ideogramApiKey}
|
|
837
|
+
onChange={(e) => updateField('ideogramApiKey', e.target.value)}
|
|
838
|
+
placeholder="Enter Ideogram API Key..."
|
|
839
|
+
/>
|
|
840
|
+
</div>
|
|
841
|
+
</div>
|
|
842
|
+
</div>
|
|
843
|
+
|
|
844
|
+
{/* Replicate */}
|
|
845
|
+
<div className={`provider-card ${config.imageGenProvider === 'replicate' ? 'active-provider' : ''}`}>
|
|
846
|
+
<div className="provider-card-header">
|
|
847
|
+
<div className="provider-card-title">
|
|
848
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
849
|
+
<polyline points="16 3 21 3 21 8"/>
|
|
850
|
+
<line x1="4" y1="20" x2="21" y2="3"/>
|
|
851
|
+
<polyline points="21 16 21 21 16 21"/>
|
|
852
|
+
<line x1="15" y1="15" x2="21" y2="21"/>
|
|
853
|
+
<line x1="4" y1="4" x2="9" y2="9"/>
|
|
854
|
+
</svg>
|
|
855
|
+
Replicate (FLUX / SDXL / custom)
|
|
856
|
+
</div>
|
|
857
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
858
|
+
{config.imageGenProvider === 'replicate' && <span className="provider-active-badge">Active</span>}
|
|
859
|
+
<label className="settings-toggle-switch" style={{ marginBottom: 0 }}>
|
|
860
|
+
<input type="radio" name="imageGenProvider" style={{ display: 'none' }} checked={config.imageGenProvider === 'replicate'} onChange={() => updateField('imageGenProvider', 'replicate')} />
|
|
861
|
+
<span className="settings-toggle-slider" style={{ cursor: 'pointer' }} onClick={() => updateField('imageGenProvider', 'replicate')} />
|
|
862
|
+
</label>
|
|
863
|
+
</div>
|
|
864
|
+
</div>
|
|
865
|
+
<div className="provider-card-body">
|
|
866
|
+
<div className="setting-row">
|
|
867
|
+
<label>Replicate API Key</label>
|
|
868
|
+
<input
|
|
869
|
+
type="password"
|
|
870
|
+
value={config.replicateApiKey}
|
|
871
|
+
onChange={(e) => updateField('replicateApiKey', e.target.value)}
|
|
872
|
+
placeholder="Enter Replicate API Key..."
|
|
873
|
+
/>
|
|
874
|
+
</div>
|
|
875
|
+
</div>
|
|
876
|
+
</div>
|
|
877
|
+
</div>
|
|
878
|
+
</section>
|
|
879
|
+
|
|
880
|
+
{isDesktopApp && (
|
|
881
|
+
<section className="setting-section">
|
|
882
|
+
<div className="section-heading">
|
|
883
|
+
<div>
|
|
884
|
+
<p className="section-kicker">Desktop updates</p>
|
|
885
|
+
|
|
886
|
+
<h2 className="section-title">Signed Tauri Channel</h2>
|
|
887
|
+
</div>
|
|
888
|
+
<p className="section-description">Check and explicitly install signed Tauri releases from your configured update channel.</p>
|
|
889
|
+
</div>
|
|
890
|
+
<div className="toggle-row">
|
|
891
|
+
<div>
|
|
892
|
+
<label>Check signed update channel</label>
|
|
893
|
+
<p className="hint">This flag does not install updates automatically.</p>
|
|
894
|
+
</div>
|
|
895
|
+
<label className="settings-toggle-switch">
|
|
896
|
+
<input
|
|
897
|
+
type="checkbox"
|
|
898
|
+
checked={config.enableAutoUpdate}
|
|
899
|
+
onChange={(e) => updateField('enableAutoUpdate', e.target.checked)}
|
|
900
|
+
/>
|
|
901
|
+
<span className="settings-toggle-slider"></span>
|
|
902
|
+
</label>
|
|
903
|
+
</div>
|
|
904
|
+
<div className="form-grid single">
|
|
905
|
+
<div className="setting-row">
|
|
906
|
+
<label>Updater Endpoint</label>
|
|
907
|
+
<input type="text" value={config.updaterEndpoint} onChange={(e) => updateField('updaterEndpoint', e.target.value)} placeholder="https://updates.example.com/latest.json" />
|
|
908
|
+
</div>
|
|
909
|
+
<div className="setting-row">
|
|
910
|
+
<label>Updater Public Key</label>
|
|
911
|
+
<textarea value={config.updaterPublicKey} onChange={(e) => updateField('updaterPublicKey', e.target.value)} placeholder="Minisign public key" />
|
|
912
|
+
</div>
|
|
913
|
+
</div>
|
|
914
|
+
<div className="setting-actions">
|
|
915
|
+
<button type="button" className="btn-connect" onClick={handleCheckUpdates}>Check for updates</button>
|
|
916
|
+
{updateAvailable && <button type="button" className="btn-primary" onClick={handleInstallUpdate}>Install signed update</button>}
|
|
917
|
+
</div>
|
|
918
|
+
{updateMessage && <p className="hint">{updateMessage}</p>}
|
|
919
|
+
</section>
|
|
920
|
+
)}
|
|
921
|
+
|
|
922
|
+
{isDesktopApp && (
|
|
923
|
+
<section className="setting-section">
|
|
924
|
+
<div className="section-heading">
|
|
925
|
+
<div>
|
|
926
|
+
<p className="section-kicker">Desktop</p>
|
|
927
|
+
<h2 className="section-title">Assistant Presence</h2>
|
|
928
|
+
</div>
|
|
929
|
+
</div>
|
|
930
|
+
<div className="toggle-row">
|
|
931
|
+
<div>
|
|
932
|
+
<label>Show Desktop AI Candidate</label>
|
|
933
|
+
<p className="hint">Show the mini AI character on your desktop.</p>
|
|
934
|
+
</div>
|
|
935
|
+
<label className="settings-toggle-switch">
|
|
936
|
+
<input
|
|
937
|
+
type="checkbox"
|
|
938
|
+
checked={config.showDesktopWidget}
|
|
939
|
+
onChange={(e) => updateField('showDesktopWidget', e.target.checked)}
|
|
940
|
+
/>
|
|
941
|
+
<span className="settings-toggle-slider"></span>
|
|
942
|
+
</label>
|
|
943
|
+
</div>
|
|
944
|
+
</section>
|
|
945
|
+
)}
|
|
946
|
+
</div>
|
|
947
|
+
)
|
|
948
|
+
}
|