@pheem49/mint 1.5.5 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex +0 -0
- package/.github/FUNDING.yml +2 -0
- package/.github/workflows/ci.yml +45 -0
- package/.github/workflows/release.yml +79 -0
- package/Cargo.lock +5792 -0
- package/Cargo.toml +32 -0
- package/README.md +387 -353
- package/assets/icon.png +0 -0
- package/bin/mint +0 -0
- package/crates/mint-cli/Cargo.toml +23 -0
- package/crates/mint-cli/src/agent.rs +851 -0
- package/crates/mint-cli/src/gmail.rs +216 -0
- package/crates/mint-cli/src/image.rs +142 -0
- package/crates/mint-cli/src/main.rs +2837 -0
- package/crates/mint-cli/src/mcp.rs +63 -0
- package/crates/mint-cli/src/onboard.rs +1149 -0
- package/crates/mint-cli/src/setup.rs +390 -0
- package/crates/mint-cli/src/skills.rs +8 -0
- package/crates/mint-cli/src/updater.rs +279 -0
- package/crates/mint-core/Cargo.toml +22 -0
- package/crates/mint-core/src/agent_loop.rs +94 -0
- package/crates/mint-core/src/api_server.rs +991 -0
- package/crates/mint-core/src/channels.rs +248 -0
- package/crates/mint-core/src/chat.rs +895 -0
- package/crates/mint-core/src/code_tools.rs +729 -0
- package/crates/mint-core/src/config.rs +368 -0
- package/crates/mint-core/src/files.rs +159 -0
- package/crates/mint-core/src/knowledge.rs +541 -0
- package/crates/mint-core/src/lib.rs +84 -0
- package/crates/mint-core/src/mcp.rs +273 -0
- package/crates/mint-core/src/memory.rs +673 -0
- package/crates/mint-core/src/orchestration.rs +2157 -0
- package/crates/mint-core/src/pictures.rs +314 -0
- package/crates/mint-core/src/plugins.rs +727 -0
- package/crates/mint-core/src/safety.rs +416 -0
- package/crates/mint-core/src/semantic.rs +254 -0
- package/crates/mint-core/src/shell.rs +317 -0
- package/crates/mint-core/src/skills.rs +71 -0
- package/crates/mint-core/src/symbols.rs +157 -0
- package/crates/mint-core/src/tasks.rs +308 -0
- package/crates/mint-core/src/tts.rs +92 -0
- package/crates/mint-core/src/weather.rs +93 -0
- package/crates/mint-core/src/web_search.rs +200 -0
- package/crates/mint-core/src/workflows.rs +81 -0
- package/crates/mint-core/tests/mcp_stdio.rs +45 -0
- package/crates/mint-core/tests/memory_persistence.rs +172 -0
- package/crates/mint-core/tests/pictures_storage.rs +14 -0
- package/crates/mint-core/tests/task_lifecycle.rs +87 -0
- package/package.json +35 -99
- package/src/bin/index.js +16 -0
- package/src/renderer/index-web.html +17 -0
- package/src/renderer/index.html +17 -0
- package/src/renderer/public/Live2DCubismCore.js +9 -0
- package/src/renderer/public/assets/icon.png +0 -0
- package/src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.model3.json +36 -0
- package/src/renderer/src/App.tsx +33 -0
- package/src/renderer/src/calculator.ts +47 -0
- package/src/renderer/src/components/ChatPanel.tsx +1598 -0
- package/src/renderer/src/components/DashboardSidebar.tsx +358 -0
- package/src/renderer/src/components/Live2DStage.tsx +374 -0
- package/src/renderer/src/components/MintDashboard.tsx +950 -0
- package/src/renderer/src/components/ModelPanel.tsx +154 -0
- package/src/renderer/src/components/PicturesLibrary.tsx +46 -0
- package/src/renderer/src/components/ProactiveGlow.tsx +19 -0
- package/src/renderer/src/components/ScreenPicker.tsx +579 -0
- package/src/renderer/src/components/SettingsWindow.tsx +1467 -0
- package/src/renderer/src/components/SpotlightWindow.tsx +280 -0
- package/src/renderer/src/components/WidgetWindow.tsx +36 -0
- package/src/renderer/src/components/WorkspacePanel.tsx +268 -0
- package/src/{UI → renderer/src/css}/settings.css +69 -16
- package/src/renderer/src/css/spotlight.css +113 -0
- package/src/renderer/src/css/styles.css +3722 -0
- package/src/renderer/src/css/widget.css +185 -0
- package/src/renderer/src/env.d.ts +116 -0
- package/src/renderer/src/index.css +379 -0
- package/src/renderer/src/main.tsx +13 -0
- package/src/renderer/src/tauri.ts +996 -0
- package/src/renderer/src-web/App.tsx +25 -0
- package/src/renderer/src-web/calculator.ts +47 -0
- package/src/renderer/src-web/components/ChatPanel.tsx +1662 -0
- package/src/renderer/src-web/components/DashboardSidebar.tsx +242 -0
- package/src/renderer/src-web/components/MintDashboard.tsx +763 -0
- package/src/renderer/src-web/components/PicturesLibrary.tsx +73 -0
- package/src/renderer/src-web/components/SettingsWindow.tsx +1500 -0
- package/src/renderer/src-web/css/settings.css +1100 -0
- package/src/{UI → renderer/src-web/css}/spotlight.css +4 -4
- package/src/{UI → renderer/src-web/css}/styles.css +1055 -159
- package/src/{UI → renderer/src-web/css}/widget.css +2 -2
- package/src/renderer/src-web/env.d.ts +107 -0
- package/src/renderer/src-web/index.css +379 -0
- package/src/renderer/src-web/main.tsx +13 -0
- package/src/renderer/src-web/tauri.ts +983 -0
- package/tsconfig.json +30 -0
- package/vite.config.ts +33 -0
- package/vite.config.web.ts +51 -0
- package/GUIDE_TH.md +0 -125
- package/assets/Agent_Mint.png +0 -0
- package/assets/CLI_Screen.png +0 -0
- package/assets/Settings.png +0 -0
- package/benchmark_ai.js +0 -71
- package/install.ps1 +0 -64
- package/install.sh +0 -54
- package/main.js +0 -139
- package/mint-cli-logic.js +0 -3
- package/mint-cli.js +0 -410
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +0 -47
- package/models/Shiroko_Model/Shiroko//342/232/241/351/253/230/344/272/256/342/232/241/344/275/277/347/224/250/346/225/231/347/250/213/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.txt +0 -23
- package/preload-picker.js +0 -11
- package/preload-settings.js +0 -11
- package/preload.js +0 -41
- package/scripts/install_linux_desktop_entry.js +0 -48
- package/src/AI_Brain/Gemini_API.js +0 -813
- package/src/AI_Brain/agent_orchestrator.js +0 -73
- package/src/AI_Brain/autonomous_brain.js +0 -179
- package/src/AI_Brain/behavior_memory.js +0 -135
- package/src/AI_Brain/headless_agent.js +0 -143
- package/src/AI_Brain/knowledge_base.js +0 -349
- package/src/AI_Brain/memory_store.js +0 -662
- package/src/AI_Brain/proactive_engine.js +0 -172
- package/src/AI_Brain/provider_adapter.js +0 -365
- package/src/Automation_Layer/browser_automation.js +0 -149
- package/src/Automation_Layer/file_operations.js +0 -286
- package/src/Automation_Layer/open_app.js +0 -85
- package/src/Automation_Layer/open_website.js +0 -38
- package/src/CLI/approval_handler.js +0 -47
- package/src/CLI/chat_router.js +0 -247
- package/src/CLI/chat_ui.js +0 -1159
- package/src/CLI/cli_colors.js +0 -115
- package/src/CLI/cli_formatters.js +0 -94
- package/src/CLI/code_agent.js +0 -1667
- package/src/CLI/code_session_memory.js +0 -62
- package/src/CLI/gmail_auth.js +0 -210
- package/src/CLI/image_input.js +0 -90
- package/src/CLI/intent_detectors.js +0 -181
- package/src/CLI/interactive_chat.js +0 -658
- package/src/CLI/list_features.js +0 -64
- package/src/CLI/onboarding.js +0 -416
- package/src/CLI/repo_summarizer.js +0 -282
- package/src/CLI/semantic_code_search.js +0 -312
- package/src/CLI/skill_manager.js +0 -41
- package/src/CLI/slash_command_handler.js +0 -418
- package/src/CLI/symbol_indexer.js +0 -231
- package/src/CLI/updater.js +0 -230
- package/src/CLI/workspace_manager.js +0 -90
- package/src/Channels/brave_search_bridge.js +0 -35
- package/src/Channels/discord_bridge.js +0 -66
- package/src/Channels/google_search_bridge.js +0 -38
- package/src/Channels/line_bridge.js +0 -60
- package/src/Channels/slack_bridge.js +0 -48
- package/src/Channels/telegram_bridge.js +0 -41
- package/src/Channels/whatsapp_bridge.js +0 -57
- package/src/Command_Parser/parser.js +0 -45
- package/src/Plugins/dev_tools.js +0 -41
- package/src/Plugins/discord.js +0 -20
- package/src/Plugins/docker.js +0 -47
- package/src/Plugins/gmail.js +0 -251
- package/src/Plugins/google_calendar.js +0 -252
- package/src/Plugins/mcp_manager.js +0 -95
- package/src/Plugins/notion.js +0 -256
- package/src/Plugins/obsidian.js +0 -54
- package/src/Plugins/plugin_manager.js +0 -81
- package/src/Plugins/spotify.js +0 -173
- package/src/Plugins/system_metrics.js +0 -31
- package/src/Plugins/system_monitor.js +0 -72
- package/src/System/action_executor.js +0 -178
- package/src/System/bridge_manager.js +0 -76
- package/src/System/chat_history_manager.js +0 -83
- package/src/System/config_manager.js +0 -194
- package/src/System/custom_workflows.js +0 -163
- package/src/System/daemon_manager.js +0 -67
- package/src/System/google_tts_urls.js +0 -51
- package/src/System/granular_automation.js +0 -157
- package/src/System/ipc_handlers.js +0 -332
- package/src/System/notifications.js +0 -23
- package/src/System/optional_require.js +0 -23
- package/src/System/picture_store.js +0 -109
- package/src/System/proactive_loop.js +0 -153
- package/src/System/safety_manager.js +0 -273
- package/src/System/sandbox_runner.js +0 -182
- package/src/System/screen_capture.js +0 -175
- package/src/System/smart_context.js +0 -227
- package/src/System/system_automation.js +0 -162
- package/src/System/system_events.js +0 -79
- package/src/System/system_info.js +0 -125
- package/src/System/task_manager.js +0 -222
- package/src/System/tool_registry.js +0 -293
- package/src/System/window_manager.js +0 -220
- package/src/UI/floating.css +0 -80
- package/src/UI/floating.html +0 -17
- package/src/UI/floating.js +0 -67
- package/src/UI/live2d_manager.js +0 -600
- package/src/UI/preload-floating.js +0 -7
- package/src/UI/preload-spotlight.js +0 -11
- package/src/UI/preload-widget.js +0 -5
- package/src/UI/proactive-glow.html +0 -42
- package/src/UI/renderer.js +0 -2127
- package/src/UI/screenPicker.html +0 -214
- package/src/UI/screenPicker.js +0 -262
- package/src/UI/settings.html +0 -577
- package/src/UI/settings.js +0 -770
- package/src/UI/spotlight.html +0 -23
- package/src/UI/spotlight.js +0 -185
- package/src/UI/widget.html +0 -29
- package/src/UI/widget.js +0 -10
- /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/apron.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/catfilter.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/click.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazed.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253/347/234/274/347/217/240/346/221/207/346/231/203.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazedeyes.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/glasses.exp3.json} +0 -0
- /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/pen.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/photo.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_00.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_01.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_02.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_03.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.cdi3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.moc3} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.physics3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.vtube.json} +0 -0
|
@@ -0,0 +1,1467 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import { getProfileValue, setProfileValue } from '../tauri'
|
|
3
|
+
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
theme: 'dark',
|
|
6
|
+
accentColor: '#4f83e6',
|
|
7
|
+
systemTextColor: '#f8fafc',
|
|
8
|
+
customBgStart: '#0f172a',
|
|
9
|
+
customBgEnd: '#1e1b4b',
|
|
10
|
+
customPanelBg: '#1e293b',
|
|
11
|
+
glassBlur: 'blur(16px)',
|
|
12
|
+
fontFamily: "'Outfit', sans-serif",
|
|
13
|
+
fontSize: '15px',
|
|
14
|
+
apiKey: '',
|
|
15
|
+
aiProvider: 'gemini',
|
|
16
|
+
geminiModel: 'gemini-2.5-flash',
|
|
17
|
+
openaiModel: 'gpt-4o',
|
|
18
|
+
openrouterModel: 'openai/gpt-4o-mini',
|
|
19
|
+
deepseekModel: 'deepseek-v4-flash',
|
|
20
|
+
anthropicModel: 'claude-3-5-sonnet-latest',
|
|
21
|
+
ollamaModel: 'llama3:latest',
|
|
22
|
+
language: 'th-TH',
|
|
23
|
+
proactiveInterval: 60,
|
|
24
|
+
proactiveCooldown: 120,
|
|
25
|
+
enableVoiceReply: true,
|
|
26
|
+
enableCustomWorkflows: true,
|
|
27
|
+
enableAgentCollaboration: true,
|
|
28
|
+
ttsProvider: 'google',
|
|
29
|
+
ttsVolume: 1.0,
|
|
30
|
+
ttsSpeed: 1.0,
|
|
31
|
+
ttsPitch: 1.0,
|
|
32
|
+
pluginSpotifyEnabled: true,
|
|
33
|
+
pluginCalendarEnabled: false,
|
|
34
|
+
pluginGmailEnabled: false,
|
|
35
|
+
pluginNotionEnabled: false,
|
|
36
|
+
pluginDiscordEnabled: false,
|
|
37
|
+
showDesktopWidget: true,
|
|
38
|
+
mcpServers: {} as Record<string, any>,
|
|
39
|
+
hfModel: 'meta-llama/Meta-Llama-3-8B-Instruct',
|
|
40
|
+
localApiBaseUrl: '',
|
|
41
|
+
localModelName: 'local-model',
|
|
42
|
+
ollamaHost: '',
|
|
43
|
+
anthropicApiKey: '',
|
|
44
|
+
openaiApiKey: '',
|
|
45
|
+
openrouterApiKey: '',
|
|
46
|
+
deepseekApiKey: '',
|
|
47
|
+
hfApiKey: '',
|
|
48
|
+
automationBrowser: 'chromium',
|
|
49
|
+
browserDebugUrl: 'http://127.0.0.1:9222/json/list',
|
|
50
|
+
browserExtensionContextUrl: 'http://127.0.0.1:3212/context',
|
|
51
|
+
enableHeadlessTaskQueue: false,
|
|
52
|
+
enableAutoUpdate: false,
|
|
53
|
+
updaterEndpoint: '',
|
|
54
|
+
updaterPublicKey: '',
|
|
55
|
+
telegramBotToken: '',
|
|
56
|
+
enableTelegramBridge: false,
|
|
57
|
+
discordBotToken: '',
|
|
58
|
+
discordApplicationId: '',
|
|
59
|
+
enableDiscordBridge: false,
|
|
60
|
+
slackBotToken: '',
|
|
61
|
+
slackAppToken: '',
|
|
62
|
+
enableSlackBridge: false,
|
|
63
|
+
lineChannelAccessToken: '',
|
|
64
|
+
lineChannelSecret: '',
|
|
65
|
+
enableLineBridge: false,
|
|
66
|
+
whatsappCloudAccessToken: '',
|
|
67
|
+
whatsappPhoneNumberId: '',
|
|
68
|
+
whatsappVerifyToken: '',
|
|
69
|
+
whatsappAppSecret: '',
|
|
70
|
+
enableWhatsappBridge: false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type TabType = 'sect-general' | 'sect-audio' | 'sect-automation' | 'sect-theme' | 'sect-plugins' | 'sect-shortcuts' | 'sect-memory'
|
|
74
|
+
|
|
75
|
+
const GEMINI_MODELS = [
|
|
76
|
+
'gemini-2.5-flash',
|
|
77
|
+
'gemini-2.5-pro',
|
|
78
|
+
'gemini-2.0-flash',
|
|
79
|
+
'gemini-1.5-flash',
|
|
80
|
+
'gemini-1.5-pro',
|
|
81
|
+
'gemini-3.1-flash-lite',
|
|
82
|
+
'gemini-3.1-flash-lite-preview'
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
const OPENAI_MODELS = [
|
|
86
|
+
'gpt-4o',
|
|
87
|
+
'gpt-4o-mini',
|
|
88
|
+
'o1',
|
|
89
|
+
'o3-mini',
|
|
90
|
+
'o1-preview',
|
|
91
|
+
'o1-mini',
|
|
92
|
+
'gpt-4-turbo'
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
const OPENROUTER_MODELS = [
|
|
96
|
+
'openai/gpt-4o-mini',
|
|
97
|
+
'openai/gpt-4o',
|
|
98
|
+
'anthropic/claude-3.5-sonnet',
|
|
99
|
+
'anthropic/claude-3.5-haiku',
|
|
100
|
+
'google/gemini-2.5-flash',
|
|
101
|
+
'meta-llama/llama-3.3-70b-instruct',
|
|
102
|
+
'mistralai/mistral-large'
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
const DEEPSEEK_MODELS = [
|
|
106
|
+
'deepseek-v4-flash',
|
|
107
|
+
'deepseek-v4-pro',
|
|
108
|
+
'deepseek-chat',
|
|
109
|
+
'deepseek-reasoner'
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
const ANTHROPIC_MODELS = [
|
|
113
|
+
'claude-3-7-sonnet-latest',
|
|
114
|
+
'claude-3-5-sonnet-latest',
|
|
115
|
+
'claude-3-5-haiku-latest',
|
|
116
|
+
'claude-3-opus-latest'
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
const HF_MODELS = [
|
|
120
|
+
'meta-llama/Llama-3.3-70B-Instruct',
|
|
121
|
+
'meta-llama/Meta-Llama-3-8B-Instruct',
|
|
122
|
+
'meta-llama/Llama-3.2-3B-Instruct',
|
|
123
|
+
'Qwen/Qwen2.5-72B-Instruct',
|
|
124
|
+
'Qwen/Qwen2.5-Coder-32B-Instruct',
|
|
125
|
+
'mistralai/Mistral-7B-Instruct-v0.3',
|
|
126
|
+
'google/gemma-2-9b-it'
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
const LOCAL_MODELS = [
|
|
130
|
+
'local-model',
|
|
131
|
+
'Qwen/Qwen2.5-7B-Instruct-GGUF',
|
|
132
|
+
'meta-llama/Llama-3.2-3B-Instruct-GGUF',
|
|
133
|
+
'lmstudio-community/gemma-2-9b-it-GGUF'
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
const OLLAMA_MODELS = [
|
|
137
|
+
'llama3:latest',
|
|
138
|
+
'llama3.1:latest',
|
|
139
|
+
'llama3.2:latest',
|
|
140
|
+
'gemma2:latest',
|
|
141
|
+
'mistral:latest',
|
|
142
|
+
'phi3:latest',
|
|
143
|
+
'qwen2.5:latest'
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
export default function SettingsWindow() {
|
|
147
|
+
const [activeTab, setActiveTab] = useState<TabType>('sect-general')
|
|
148
|
+
const [config, setConfig] = useState(DEFAULT_CONFIG)
|
|
149
|
+
|
|
150
|
+
// Custom user profile / memory state
|
|
151
|
+
const [userName, setUserName] = useState('')
|
|
152
|
+
const [userPreferences, setUserPreferences] = useState('')
|
|
153
|
+
|
|
154
|
+
// Custom model helpers for all providers
|
|
155
|
+
const [customGemini, setCustomGemini] = useState('')
|
|
156
|
+
const [customOpenAI, setCustomOpenAI] = useState('')
|
|
157
|
+
const [customOpenRouter, setCustomOpenRouter] = useState('')
|
|
158
|
+
const [customDeepSeek, setCustomDeepSeek] = useState('')
|
|
159
|
+
const [customAnthropic, setCustomAnthropic] = useState('')
|
|
160
|
+
const [customHF, setCustomHF] = useState('')
|
|
161
|
+
const [customLocal, setCustomLocal] = useState('')
|
|
162
|
+
const [customOllama, setCustomOllama] = useState('')
|
|
163
|
+
|
|
164
|
+
// New MCP Server Form state
|
|
165
|
+
const [mcpName, setMcpName] = useState('')
|
|
166
|
+
const [mcpCmd, setMcpCmd] = useState('')
|
|
167
|
+
const [mcpArgs, setMcpArgs] = useState('')
|
|
168
|
+
const [mcpEnv, setMcpEnv] = useState('')
|
|
169
|
+
const [updateMessage, setUpdateMessage] = useState('')
|
|
170
|
+
const [updateAvailable, setUpdateAvailable] = useState(false)
|
|
171
|
+
|
|
172
|
+
// Load settings on mount
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
async function loadSettings() {
|
|
175
|
+
try {
|
|
176
|
+
const nameVal = await getProfileValue('name')
|
|
177
|
+
const prefVal = await getProfileValue('preferences')
|
|
178
|
+
setUserName(nameVal || '')
|
|
179
|
+
setUserPreferences(prefVal || '')
|
|
180
|
+
} catch (e) {
|
|
181
|
+
console.error("Failed to load user profile memory:", e)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (window.settingsApi) {
|
|
185
|
+
const loaded = await window.settingsApi.getSettings()
|
|
186
|
+
const merged = { ...DEFAULT_CONFIG, ...loaded }
|
|
187
|
+
setConfig(merged)
|
|
188
|
+
|
|
189
|
+
// Set custom text helpers
|
|
190
|
+
if (!GEMINI_MODELS.includes(merged.geminiModel)) {
|
|
191
|
+
setCustomGemini(merged.geminiModel)
|
|
192
|
+
}
|
|
193
|
+
if (!OPENAI_MODELS.includes(merged.openaiModel)) {
|
|
194
|
+
setCustomOpenAI(merged.openaiModel)
|
|
195
|
+
}
|
|
196
|
+
if (!OPENROUTER_MODELS.includes(merged.openrouterModel)) {
|
|
197
|
+
setCustomOpenRouter(merged.openrouterModel)
|
|
198
|
+
}
|
|
199
|
+
if (!DEEPSEEK_MODELS.includes(merged.deepseekModel)) {
|
|
200
|
+
setCustomDeepSeek(merged.deepseekModel)
|
|
201
|
+
}
|
|
202
|
+
if (!ANTHROPIC_MODELS.includes(merged.anthropicModel)) {
|
|
203
|
+
setCustomAnthropic(merged.anthropicModel)
|
|
204
|
+
}
|
|
205
|
+
if (!HF_MODELS.includes(merged.hfModel)) {
|
|
206
|
+
setCustomHF(merged.hfModel)
|
|
207
|
+
}
|
|
208
|
+
if (!LOCAL_MODELS.includes(merged.localModelName)) {
|
|
209
|
+
setCustomLocal(merged.localModelName)
|
|
210
|
+
}
|
|
211
|
+
if (!OLLAMA_MODELS.includes(merged.ollamaModel)) {
|
|
212
|
+
setCustomOllama(merged.ollamaModel)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
applyThemeStyles(merged)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
loadSettings()
|
|
219
|
+
}, [])
|
|
220
|
+
|
|
221
|
+
const applyThemeStyles = (cfg: typeof DEFAULT_CONFIG) => {
|
|
222
|
+
document.documentElement.setAttribute('data-theme', cfg.theme)
|
|
223
|
+
document.documentElement.style.setProperty('--accent', cfg.accentColor)
|
|
224
|
+
document.documentElement.style.setProperty('--accent-hover', lightenColor(cfg.accentColor, 20))
|
|
225
|
+
document.documentElement.style.setProperty('--text-main', cfg.systemTextColor)
|
|
226
|
+
document.documentElement.style.setProperty('--glass-blur', cfg.glassBlur)
|
|
227
|
+
document.body.style.fontFamily = cfg.fontFamily
|
|
228
|
+
document.documentElement.style.fontSize = cfg.fontSize
|
|
229
|
+
|
|
230
|
+
if (cfg.theme === 'custom') {
|
|
231
|
+
if (cfg.customBgStart && cfg.customBgEnd) {
|
|
232
|
+
const gradient = `linear-gradient(135deg, ${cfg.customBgStart} 0%, ${cfg.customBgEnd} 100%)`
|
|
233
|
+
document.documentElement.style.setProperty('--bg-color', cfg.customBgStart)
|
|
234
|
+
document.documentElement.style.setProperty('--bg-gradient', gradient)
|
|
235
|
+
}
|
|
236
|
+
if (cfg.customPanelBg) {
|
|
237
|
+
const rgb = hexToRgb(cfg.customPanelBg)
|
|
238
|
+
document.documentElement.style.setProperty('--panel-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.75)`)
|
|
239
|
+
document.documentElement.style.setProperty('--panel-raised', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.82)`)
|
|
240
|
+
document.documentElement.style.setProperty('--panel-soft', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.46)`)
|
|
241
|
+
document.documentElement.style.setProperty('--chrome-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.88)`)
|
|
242
|
+
document.documentElement.style.setProperty('--surface-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.62)`)
|
|
243
|
+
document.documentElement.style.setProperty('--surface-strong', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.86)`)
|
|
244
|
+
document.documentElement.style.setProperty('--input-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.72)`)
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
[
|
|
248
|
+
'--bg-color',
|
|
249
|
+
'--bg-gradient',
|
|
250
|
+
'--panel-bg',
|
|
251
|
+
'--panel-raised',
|
|
252
|
+
'--panel-soft',
|
|
253
|
+
'--chrome-bg',
|
|
254
|
+
'--surface-bg',
|
|
255
|
+
'--surface-strong',
|
|
256
|
+
'--input-bg'
|
|
257
|
+
].forEach(name => document.documentElement.style.removeProperty(name))
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const lightenColor = (hex: string, amount: number) => {
|
|
262
|
+
const clean = hex.replace('#', '')
|
|
263
|
+
if (clean.length !== 6) return hex
|
|
264
|
+
const num = parseInt(clean, 16)
|
|
265
|
+
const r = Math.min(255, (num >> 16) + amount)
|
|
266
|
+
const g = Math.min(255, ((num >> 8) & 0x00FF) + amount)
|
|
267
|
+
const b = Math.min(255, (num & 0x0000FF) + amount)
|
|
268
|
+
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const hexToRgb = (hex: string) => {
|
|
272
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
|
273
|
+
return result ? {
|
|
274
|
+
r: parseInt(result[1], 16),
|
|
275
|
+
g: parseInt(result[2], 16),
|
|
276
|
+
b: parseInt(result[3], 16)
|
|
277
|
+
} : { r: 15, g: 23, b: 42 }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const handleSave = async () => {
|
|
281
|
+
const finalConfig = { ...config }
|
|
282
|
+
|
|
283
|
+
// Process custom models
|
|
284
|
+
if (config.geminiModel === 'custom') {
|
|
285
|
+
finalConfig.geminiModel = customGemini || 'gemini-2.5-flash'
|
|
286
|
+
}
|
|
287
|
+
if (config.openaiModel === 'custom') {
|
|
288
|
+
finalConfig.openaiModel = customOpenAI || 'gpt-4o'
|
|
289
|
+
}
|
|
290
|
+
if (config.openrouterModel === 'custom') {
|
|
291
|
+
finalConfig.openrouterModel = customOpenRouter || 'openai/gpt-4o-mini'
|
|
292
|
+
}
|
|
293
|
+
if (config.deepseekModel === 'custom') {
|
|
294
|
+
finalConfig.deepseekModel = customDeepSeek || 'deepseek-v4-flash'
|
|
295
|
+
}
|
|
296
|
+
if (config.anthropicModel === 'custom') {
|
|
297
|
+
finalConfig.anthropicModel = customAnthropic || 'claude-3-5-sonnet-latest'
|
|
298
|
+
}
|
|
299
|
+
if (config.hfModel === 'custom') {
|
|
300
|
+
finalConfig.hfModel = customHF || 'meta-llama/Meta-Llama-3-8B-Instruct'
|
|
301
|
+
}
|
|
302
|
+
if (config.localModelName === 'custom') {
|
|
303
|
+
finalConfig.localModelName = customLocal || 'local-model'
|
|
304
|
+
}
|
|
305
|
+
if (config.ollamaModel === 'custom') {
|
|
306
|
+
finalConfig.ollamaModel = customOllama || 'llama3:latest'
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
await setProfileValue('name', userName)
|
|
311
|
+
await setProfileValue('preferences', userPreferences)
|
|
312
|
+
} catch (e) {
|
|
313
|
+
console.error("Failed to save user profile memory:", e)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (window.settingsApi) {
|
|
317
|
+
await window.settingsApi.saveSettings(finalConfig)
|
|
318
|
+
applyThemeStyles(finalConfig)
|
|
319
|
+
window.settingsApi.closeSettings()
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const handleReset = async () => {
|
|
324
|
+
if (confirm('Reset all settings to default?')) {
|
|
325
|
+
setConfig(DEFAULT_CONFIG)
|
|
326
|
+
setCustomGemini('')
|
|
327
|
+
setCustomOpenAI('')
|
|
328
|
+
setCustomAnthropic('')
|
|
329
|
+
setCustomHF('')
|
|
330
|
+
setCustomLocal('')
|
|
331
|
+
setCustomOllama('')
|
|
332
|
+
setUserName('')
|
|
333
|
+
setUserPreferences('')
|
|
334
|
+
try {
|
|
335
|
+
await setProfileValue('name', '')
|
|
336
|
+
await setProfileValue('preferences', '')
|
|
337
|
+
} catch (e) {
|
|
338
|
+
console.error("Failed to reset profile memory:", e)
|
|
339
|
+
}
|
|
340
|
+
applyThemeStyles(DEFAULT_CONFIG)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const handleClose = () => {
|
|
345
|
+
window.settingsApi?.closeSettings()
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const handleQuit = () => {
|
|
349
|
+
if (confirm('Are you sure you want to exit Mint?')) {
|
|
350
|
+
window.settingsApi?.quitApp()
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const handleOpenWorkflows = () => {
|
|
355
|
+
window.settingsApi?.openCustomWorkflows()
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const handleReloadWorkflows = async () => {
|
|
359
|
+
if (window.settingsApi) {
|
|
360
|
+
const res = await window.settingsApi.reloadCustomWorkflows()
|
|
361
|
+
alert(res?.success ? 'Workflows reloaded successfully!' : 'Workflow reload failed.')
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const handleAddMcpServer = () => {
|
|
366
|
+
if (!mcpName.trim() || !mcpCmd.trim()) {
|
|
367
|
+
alert('Please provide at least a server name and command.')
|
|
368
|
+
return
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
let parsedEnv = {}
|
|
372
|
+
if (mcpEnv.trim()) {
|
|
373
|
+
try {
|
|
374
|
+
parsedEnv = JSON.parse(mcpEnv)
|
|
375
|
+
} catch (e) {
|
|
376
|
+
alert('Invalid JSON in Environment variable field.')
|
|
377
|
+
return
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Split args nicely or pass as string array
|
|
382
|
+
const argList = mcpArgs.split(/\s+/).filter(Boolean)
|
|
383
|
+
|
|
384
|
+
const updatedMcp = {
|
|
385
|
+
...config.mcpServers,
|
|
386
|
+
[mcpName.trim()]: {
|
|
387
|
+
command: mcpCmd.trim(),
|
|
388
|
+
args: argList,
|
|
389
|
+
env: parsedEnv
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
setConfig({
|
|
394
|
+
...config,
|
|
395
|
+
mcpServers: updatedMcp
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
// Reset fields
|
|
399
|
+
setMcpName('')
|
|
400
|
+
setMcpCmd('')
|
|
401
|
+
setMcpArgs('')
|
|
402
|
+
setMcpEnv('')
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const handleRemoveMcpServer = (name: string) => {
|
|
406
|
+
const updated = { ...config.mcpServers }
|
|
407
|
+
delete updated[name]
|
|
408
|
+
setConfig({
|
|
409
|
+
...config,
|
|
410
|
+
mcpServers: updated
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const handleConnectPlugin = async (plugin: string) => {
|
|
415
|
+
if (plugin === 'discord') {
|
|
416
|
+
try {
|
|
417
|
+
await window.api.executeProactiveAction({ type: 'plugin', pluginName: 'discord', target: '' })
|
|
418
|
+
alert('Discord Rich Presence updated.')
|
|
419
|
+
} catch (reason) {
|
|
420
|
+
alert(String(reason))
|
|
421
|
+
}
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
alert(`Configure ${plugin} credentials, then invoke the plugin from Mint chat.`)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const handleCheckUpdates = async () => {
|
|
428
|
+
try {
|
|
429
|
+
const update = await window.settingsApi.checkForUpdates()
|
|
430
|
+
setUpdateAvailable(Boolean(update.available))
|
|
431
|
+
setUpdateMessage(update.available ? `Mint ${update.version} is available.` : 'Mint is up to date.')
|
|
432
|
+
} catch (reason) {
|
|
433
|
+
setUpdateMessage(String(reason))
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const handleInstallUpdate = async () => {
|
|
438
|
+
if (!confirm('Install the signed Mint update now?')) return
|
|
439
|
+
try {
|
|
440
|
+
setUpdateMessage(await window.settingsApi.installAvailableUpdate())
|
|
441
|
+
setUpdateAvailable(false)
|
|
442
|
+
} catch (reason) {
|
|
443
|
+
setUpdateMessage(String(reason))
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const updateField = (field: keyof typeof DEFAULT_CONFIG, value: any) => {
|
|
448
|
+
const updated = { ...config, [field]: value }
|
|
449
|
+
setConfig(updated)
|
|
450
|
+
applyThemeStyles(updated)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return (
|
|
454
|
+
<div className="settings-container">
|
|
455
|
+
<header className="settings-header drag-region">
|
|
456
|
+
<div className="header-left">
|
|
457
|
+
<span className="settings-icon" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
458
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
459
|
+
<circle cx="12" cy="12" r="3"></circle>
|
|
460
|
+
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
|
461
|
+
</svg>
|
|
462
|
+
</span>
|
|
463
|
+
<div>
|
|
464
|
+
<h1>Settings</h1>
|
|
465
|
+
<p>Configure Mint assistant behavior and integrations.</p>
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
<button className="close-btn" onClick={handleClose} aria-label="Close">
|
|
469
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
|
470
|
+
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
471
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
472
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
473
|
+
</svg>
|
|
474
|
+
</button>
|
|
475
|
+
</header>
|
|
476
|
+
|
|
477
|
+
<main className="settings-body">
|
|
478
|
+
<nav className="settings-sidebar" aria-label="Settings sections">
|
|
479
|
+
<button className={`tab-btn ${activeTab === 'sect-general' ? 'active' : ''}`} onClick={() => setActiveTab('sect-general')}>
|
|
480
|
+
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
481
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
482
|
+
<circle cx="12" cy="12" r="3"></circle>
|
|
483
|
+
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
|
484
|
+
</svg>
|
|
485
|
+
</span>
|
|
486
|
+
<strong>General</strong>
|
|
487
|
+
</button>
|
|
488
|
+
<button className={`tab-btn ${activeTab === 'sect-memory' ? 'active' : ''}`} onClick={() => setActiveTab('sect-memory')}>
|
|
489
|
+
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
490
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
491
|
+
<path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"></path>
|
|
492
|
+
<path d="M12 6a3 3 0 1 0 0 6 3 3 0 0 0 0-6z"></path>
|
|
493
|
+
<path d="M6 18a6 6 0 0 1 12 0"></path>
|
|
494
|
+
</svg>
|
|
495
|
+
</span>
|
|
496
|
+
<strong>Memory & Profile</strong>
|
|
497
|
+
</button>
|
|
498
|
+
<button className={`tab-btn ${activeTab === 'sect-audio' ? 'active' : ''}`} onClick={() => setActiveTab('sect-audio')}>
|
|
499
|
+
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
500
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
501
|
+
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
|
|
502
|
+
<path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path>
|
|
503
|
+
</svg>
|
|
504
|
+
</span>
|
|
505
|
+
<strong>Audio & Voice</strong>
|
|
506
|
+
</button>
|
|
507
|
+
<button className={`tab-btn ${activeTab === 'sect-automation' ? 'active' : ''}`} onClick={() => setActiveTab('sect-automation')}>
|
|
508
|
+
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
509
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
510
|
+
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
|
|
511
|
+
</svg>
|
|
512
|
+
</span>
|
|
513
|
+
<strong>Automation</strong>
|
|
514
|
+
</button>
|
|
515
|
+
<button className={`tab-btn ${activeTab === 'sect-theme' ? 'active' : ''}`} onClick={() => setActiveTab('sect-theme')}>
|
|
516
|
+
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
517
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
518
|
+
<circle cx="12" cy="12" r="5"></circle>
|
|
519
|
+
<line x1="12" y1="1" x2="12" y2="3"></line>
|
|
520
|
+
<line x1="12" y1="21" x2="12" y2="23"></line>
|
|
521
|
+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
|
522
|
+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
|
523
|
+
<line x1="1" y1="12" x2="3" y2="12"></line>
|
|
524
|
+
<line x1="21" y1="12" x2="23" y2="12"></line>
|
|
525
|
+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
|
526
|
+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
|
527
|
+
</svg>
|
|
528
|
+
</span>
|
|
529
|
+
<strong>Theme & UI</strong>
|
|
530
|
+
</button>
|
|
531
|
+
<button className={`tab-btn ${activeTab === 'sect-plugins' ? 'active' : ''}`} onClick={() => setActiveTab('sect-plugins')}>
|
|
532
|
+
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
533
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
534
|
+
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path>
|
|
535
|
+
</svg>
|
|
536
|
+
</span>
|
|
537
|
+
<strong>Plugins</strong>
|
|
538
|
+
</button>
|
|
539
|
+
<button className={`tab-btn ${activeTab === 'sect-shortcuts' ? 'active' : ''}`} onClick={() => setActiveTab('sect-shortcuts')}>
|
|
540
|
+
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
541
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
542
|
+
<rect x="2" y="4" width="20" height="16" rx="2" ry="2"></rect>
|
|
543
|
+
<line x1="6" y1="8" x2="6" y2="8"></line>
|
|
544
|
+
<line x1="10" y1="8" x2="10" y2="8"></line>
|
|
545
|
+
<line x1="14" y1="8" x2="14" y2="8"></line>
|
|
546
|
+
<line x1="18" y1="8" x2="18" y2="8"></line>
|
|
547
|
+
<line x1="6" y1="12" x2="6" y2="12"></line>
|
|
548
|
+
<line x1="10" y1="12" x2="10" y2="12"></line>
|
|
549
|
+
<line x1="14" y1="12" x2="14" y2="12"></line>
|
|
550
|
+
<line x1="18" y1="12" x2="18" y2="12"></line>
|
|
551
|
+
<line x1="7" y1="16" x2="17" y2="16"></line>
|
|
552
|
+
</svg>
|
|
553
|
+
</span>
|
|
554
|
+
<strong>Shortcuts</strong>
|
|
555
|
+
</button>
|
|
556
|
+
</nav>
|
|
557
|
+
|
|
558
|
+
<div className="settings-content">
|
|
559
|
+
{/* GENERAL SECTION */}
|
|
560
|
+
{activeTab === 'sect-general' && (
|
|
561
|
+
<div className="tab-pane active">
|
|
562
|
+
<section className="setting-section">
|
|
563
|
+
<div className="section-heading">
|
|
564
|
+
<div>
|
|
565
|
+
<p className="section-kicker">AI routing</p>
|
|
566
|
+
<h2 class="section-title">Provider & Model</h2>
|
|
567
|
+
</div>
|
|
568
|
+
<p className="section-description">Choose which AI backend Mint uses, then set the default model.</p>
|
|
569
|
+
</div>
|
|
570
|
+
|
|
571
|
+
<div className="form-grid compact">
|
|
572
|
+
<div className="setting-row wide">
|
|
573
|
+
<label>Active Provider</label>
|
|
574
|
+
<select value={config.aiProvider} onChange={(e) => updateField('aiProvider', e.target.value)}>
|
|
575
|
+
<option value="gemini">Google Gemini (Cloud)</option>
|
|
576
|
+
<option value="anthropic">Anthropic Claude</option>
|
|
577
|
+
<option value="openai">OpenAI</option>
|
|
578
|
+
<option value="openrouter">OpenRouter</option>
|
|
579
|
+
<option value="deepseek">DeepSeek</option>
|
|
580
|
+
<option value="ollama">Ollama (Local / Private)</option>
|
|
581
|
+
<option value="huggingface">Hugging Face (Inference API)</option>
|
|
582
|
+
<option value="local_openai">Local (LM Studio / OpenAI Compatible)</option>
|
|
583
|
+
</select>
|
|
584
|
+
</div>
|
|
585
|
+
|
|
586
|
+
<div className="setting-row">
|
|
587
|
+
<label>Gemini Model</label>
|
|
588
|
+
<select
|
|
589
|
+
value={GEMINI_MODELS.includes(config.geminiModel) ? config.geminiModel : 'custom'}
|
|
590
|
+
onChange={(e) => updateField('geminiModel', e.target.value)}
|
|
591
|
+
>
|
|
592
|
+
{GEMINI_MODELS.map(model => (
|
|
593
|
+
<option key={model} value={model}>{model}</option>
|
|
594
|
+
))}
|
|
595
|
+
<option value="custom">Custom...</option>
|
|
596
|
+
</select>
|
|
597
|
+
</div>
|
|
598
|
+
{(!GEMINI_MODELS.includes(config.geminiModel) || config.geminiModel === 'custom') && (
|
|
599
|
+
<div className="setting-row">
|
|
600
|
+
<label>Custom Gemini Model</label>
|
|
601
|
+
<input
|
|
602
|
+
type="text"
|
|
603
|
+
value={customGemini}
|
|
604
|
+
onChange={(e) => { setCustomGemini(e.target.value); updateField('geminiModel', 'custom') }}
|
|
605
|
+
placeholder="e.g. gemini-3.1-flash-lite-preview"
|
|
606
|
+
/>
|
|
607
|
+
</div>
|
|
608
|
+
)}
|
|
609
|
+
|
|
610
|
+
<div className="setting-row">
|
|
611
|
+
<label>OpenAI Model</label>
|
|
612
|
+
<select
|
|
613
|
+
value={OPENAI_MODELS.includes(config.openaiModel) ? config.openaiModel : 'custom'}
|
|
614
|
+
onChange={(e) => updateField('openaiModel', e.target.value)}
|
|
615
|
+
>
|
|
616
|
+
{OPENAI_MODELS.map(model => (
|
|
617
|
+
<option key={model} value={model}>{model}</option>
|
|
618
|
+
))}
|
|
619
|
+
<option value="custom">Custom...</option>
|
|
620
|
+
</select>
|
|
621
|
+
</div>
|
|
622
|
+
{(!OPENAI_MODELS.includes(config.openaiModel) || config.openaiModel === 'custom') && (
|
|
623
|
+
<div className="setting-row">
|
|
624
|
+
<label>Custom OpenAI Model</label>
|
|
625
|
+
<input
|
|
626
|
+
type="text"
|
|
627
|
+
value={customOpenAI}
|
|
628
|
+
onChange={(e) => { setCustomOpenAI(e.target.value); updateField('openaiModel', 'custom') }}
|
|
629
|
+
placeholder="e.g. gpt-4o"
|
|
630
|
+
/>
|
|
631
|
+
</div>
|
|
632
|
+
)}
|
|
633
|
+
|
|
634
|
+
<div className="setting-row">
|
|
635
|
+
<label>OpenRouter Model</label>
|
|
636
|
+
<select
|
|
637
|
+
value={OPENROUTER_MODELS.includes(config.openrouterModel) ? config.openrouterModel : 'custom'}
|
|
638
|
+
onChange={(e) => updateField('openrouterModel', e.target.value)}
|
|
639
|
+
>
|
|
640
|
+
{OPENROUTER_MODELS.map(model => (
|
|
641
|
+
<option key={model} value={model}>{model}</option>
|
|
642
|
+
))}
|
|
643
|
+
<option value="custom">Custom...</option>
|
|
644
|
+
</select>
|
|
645
|
+
</div>
|
|
646
|
+
{(!OPENROUTER_MODELS.includes(config.openrouterModel) || config.openrouterModel === 'custom') && (
|
|
647
|
+
<div className="setting-row">
|
|
648
|
+
<label>Custom OpenRouter Model</label>
|
|
649
|
+
<input
|
|
650
|
+
type="text"
|
|
651
|
+
value={customOpenRouter}
|
|
652
|
+
onChange={(e) => { setCustomOpenRouter(e.target.value); updateField('openrouterModel', 'custom') }}
|
|
653
|
+
placeholder="e.g. anthropic/claude-3.5-sonnet"
|
|
654
|
+
/>
|
|
655
|
+
</div>
|
|
656
|
+
)}
|
|
657
|
+
|
|
658
|
+
<div className="setting-row">
|
|
659
|
+
<label>DeepSeek Model</label>
|
|
660
|
+
<select
|
|
661
|
+
value={DEEPSEEK_MODELS.includes(config.deepseekModel) ? config.deepseekModel : 'custom'}
|
|
662
|
+
onChange={(e) => updateField('deepseekModel', e.target.value)}
|
|
663
|
+
>
|
|
664
|
+
{DEEPSEEK_MODELS.map(model => (
|
|
665
|
+
<option key={model} value={model}>{model}</option>
|
|
666
|
+
))}
|
|
667
|
+
<option value="custom">Custom...</option>
|
|
668
|
+
</select>
|
|
669
|
+
</div>
|
|
670
|
+
{(!DEEPSEEK_MODELS.includes(config.deepseekModel) || config.deepseekModel === 'custom') && (
|
|
671
|
+
<div className="setting-row">
|
|
672
|
+
<label>Custom DeepSeek Model</label>
|
|
673
|
+
<input
|
|
674
|
+
type="text"
|
|
675
|
+
value={customDeepSeek}
|
|
676
|
+
onChange={(e) => { setCustomDeepSeek(e.target.value); updateField('deepseekModel', 'custom') }}
|
|
677
|
+
placeholder="e.g. deepseek-v4-pro"
|
|
678
|
+
/>
|
|
679
|
+
</div>
|
|
680
|
+
)}
|
|
681
|
+
|
|
682
|
+
<div className="setting-row">
|
|
683
|
+
<label>Anthropic Model</label>
|
|
684
|
+
<select
|
|
685
|
+
value={ANTHROPIC_MODELS.includes(config.anthropicModel) ? config.anthropicModel : 'custom'}
|
|
686
|
+
onChange={(e) => updateField('anthropicModel', e.target.value)}
|
|
687
|
+
>
|
|
688
|
+
{ANTHROPIC_MODELS.map(model => (
|
|
689
|
+
<option key={model} value={model}>{model}</option>
|
|
690
|
+
))}
|
|
691
|
+
<option value="custom">Custom...</option>
|
|
692
|
+
</select>
|
|
693
|
+
</div>
|
|
694
|
+
{(!ANTHROPIC_MODELS.includes(config.anthropicModel) || config.anthropicModel === 'custom') && (
|
|
695
|
+
<div className="setting-row">
|
|
696
|
+
<label>Custom Anthropic Model</label>
|
|
697
|
+
<input
|
|
698
|
+
type="text"
|
|
699
|
+
value={customAnthropic}
|
|
700
|
+
onChange={(e) => { setCustomAnthropic(e.target.value); updateField('anthropicModel', 'custom') }}
|
|
701
|
+
placeholder="e.g. claude-3-5-sonnet-latest"
|
|
702
|
+
/>
|
|
703
|
+
</div>
|
|
704
|
+
)}
|
|
705
|
+
|
|
706
|
+
<div className="setting-row">
|
|
707
|
+
<label>Hugging Face Model</label>
|
|
708
|
+
<select
|
|
709
|
+
value={HF_MODELS.includes(config.hfModel) ? config.hfModel : 'custom'}
|
|
710
|
+
onChange={(e) => updateField('hfModel', e.target.value)}
|
|
711
|
+
>
|
|
712
|
+
{HF_MODELS.map(model => (
|
|
713
|
+
<option key={model} value={model}>{model}</option>
|
|
714
|
+
))}
|
|
715
|
+
<option value="custom">Custom...</option>
|
|
716
|
+
</select>
|
|
717
|
+
</div>
|
|
718
|
+
{(!HF_MODELS.includes(config.hfModel) || config.hfModel === 'custom') && (
|
|
719
|
+
<div className="setting-row">
|
|
720
|
+
<label>Custom Hugging Face Model</label>
|
|
721
|
+
<input
|
|
722
|
+
type="text"
|
|
723
|
+
value={customHF}
|
|
724
|
+
onChange={(e) => { setCustomHF(e.target.value); updateField('hfModel', 'custom') }}
|
|
725
|
+
placeholder="e.g. meta-llama/Meta-Llama-3-8B-Instruct"
|
|
726
|
+
/>
|
|
727
|
+
</div>
|
|
728
|
+
)}
|
|
729
|
+
|
|
730
|
+
<div className="setting-row">
|
|
731
|
+
<label>LM Studio Model</label>
|
|
732
|
+
<select
|
|
733
|
+
value={LOCAL_MODELS.includes(config.localModelName) ? config.localModelName : 'custom'}
|
|
734
|
+
onChange={(e) => updateField('localModelName', e.target.value)}
|
|
735
|
+
>
|
|
736
|
+
{LOCAL_MODELS.map(model => (
|
|
737
|
+
<option key={model} value={model}>{model}</option>
|
|
738
|
+
))}
|
|
739
|
+
<option value="custom">Custom...</option>
|
|
740
|
+
</select>
|
|
741
|
+
</div>
|
|
742
|
+
{(!LOCAL_MODELS.includes(config.localModelName) || config.localModelName === 'custom') && (
|
|
743
|
+
<div className="setting-row">
|
|
744
|
+
<label>Custom LM Studio Model</label>
|
|
745
|
+
<input
|
|
746
|
+
type="text"
|
|
747
|
+
value={customLocal}
|
|
748
|
+
onChange={(e) => { setCustomLocal(e.target.value); updateField('localModelName', 'custom') }}
|
|
749
|
+
placeholder="e.g. local-model"
|
|
750
|
+
/>
|
|
751
|
+
</div>
|
|
752
|
+
)}
|
|
753
|
+
|
|
754
|
+
<div className="setting-row">
|
|
755
|
+
<label>Ollama Model</label>
|
|
756
|
+
<select
|
|
757
|
+
value={OLLAMA_MODELS.includes(config.ollamaModel) ? config.ollamaModel : 'custom'}
|
|
758
|
+
onChange={(e) => updateField('ollamaModel', e.target.value)}
|
|
759
|
+
>
|
|
760
|
+
{OLLAMA_MODELS.map(model => (
|
|
761
|
+
<option key={model} value={model}>{model}</option>
|
|
762
|
+
))}
|
|
763
|
+
<option value="custom">Custom...</option>
|
|
764
|
+
</select>
|
|
765
|
+
</div>
|
|
766
|
+
{(!OLLAMA_MODELS.includes(config.ollamaModel) || config.ollamaModel === 'custom') && (
|
|
767
|
+
<div className="setting-row">
|
|
768
|
+
<label>Custom Ollama Model</label>
|
|
769
|
+
<input
|
|
770
|
+
type="text"
|
|
771
|
+
value={customOllama}
|
|
772
|
+
onChange={(e) => { setCustomOllama(e.target.value); updateField('ollamaModel', 'custom') }}
|
|
773
|
+
placeholder="e.g. llama3:latest"
|
|
774
|
+
/>
|
|
775
|
+
</div>
|
|
776
|
+
)}
|
|
777
|
+
</div>
|
|
778
|
+
</section>
|
|
779
|
+
|
|
780
|
+
<section className="setting-section">
|
|
781
|
+
<div className="section-heading">
|
|
782
|
+
<div>
|
|
783
|
+
<p className="section-kicker">Desktop updates</p>
|
|
784
|
+
<h2 className="section-title">Signed Tauri Channel</h2>
|
|
785
|
+
</div>
|
|
786
|
+
<p className="section-description">Check and explicitly install signed Tauri releases from your configured update channel.</p>
|
|
787
|
+
</div>
|
|
788
|
+
<div className="toggle-row">
|
|
789
|
+
<div>
|
|
790
|
+
<label>Check signed update channel</label>
|
|
791
|
+
<p className="hint">This flag does not install updates automatically.</p>
|
|
792
|
+
</div>
|
|
793
|
+
<label className="toggle-switch">
|
|
794
|
+
<input
|
|
795
|
+
type="checkbox"
|
|
796
|
+
checked={config.enableAutoUpdate}
|
|
797
|
+
onChange={(e) => updateField('enableAutoUpdate', e.target.checked)}
|
|
798
|
+
/>
|
|
799
|
+
<span className="toggle-slider"></span>
|
|
800
|
+
</label>
|
|
801
|
+
</div>
|
|
802
|
+
<div className="form-grid single">
|
|
803
|
+
<div className="setting-row">
|
|
804
|
+
<label>Updater Endpoint</label>
|
|
805
|
+
<input type="text" value={config.updaterEndpoint} onChange={(e) => updateField('updaterEndpoint', e.target.value)} placeholder="https://updates.example.com/latest.json" />
|
|
806
|
+
</div>
|
|
807
|
+
<div className="setting-row">
|
|
808
|
+
<label>Updater Public Key</label>
|
|
809
|
+
<textarea value={config.updaterPublicKey} onChange={(e) => updateField('updaterPublicKey', e.target.value)} placeholder="Minisign public key" />
|
|
810
|
+
</div>
|
|
811
|
+
</div>
|
|
812
|
+
<div className="setting-actions">
|
|
813
|
+
<button type="button" className="btn-connect" onClick={handleCheckUpdates}>Check for updates</button>
|
|
814
|
+
{updateAvailable && <button type="button" className="btn-primary" onClick={handleInstallUpdate}>Install signed update</button>}
|
|
815
|
+
</div>
|
|
816
|
+
{updateMessage && <p className="hint">{updateMessage}</p>}
|
|
817
|
+
</section>
|
|
818
|
+
|
|
819
|
+
<section className="setting-section">
|
|
820
|
+
<div className="section-heading">
|
|
821
|
+
<div>
|
|
822
|
+
<p className="section-kicker">Credentials</p>
|
|
823
|
+
<h2 class="section-title">API Keys & Hosts</h2>
|
|
824
|
+
</div>
|
|
825
|
+
</div>
|
|
826
|
+
|
|
827
|
+
<div className="form-grid">
|
|
828
|
+
<div className="setting-row">
|
|
829
|
+
<label>Gemini API Key</label>
|
|
830
|
+
<input
|
|
831
|
+
type="password"
|
|
832
|
+
value={config.apiKey}
|
|
833
|
+
onChange={(e) => updateField('apiKey', e.target.value)}
|
|
834
|
+
placeholder="Enter Gemini API Key..."
|
|
835
|
+
/>
|
|
836
|
+
</div>
|
|
837
|
+
<div className="setting-row">
|
|
838
|
+
<label>OpenAI API Key</label>
|
|
839
|
+
<input
|
|
840
|
+
type="password"
|
|
841
|
+
value={config.openaiApiKey}
|
|
842
|
+
onChange={(e) => updateField('openaiApiKey', e.target.value)}
|
|
843
|
+
placeholder="Enter OpenAI API Key..."
|
|
844
|
+
/>
|
|
845
|
+
</div>
|
|
846
|
+
<div className="setting-row">
|
|
847
|
+
<label>OpenRouter API Key</label>
|
|
848
|
+
<input
|
|
849
|
+
type="password"
|
|
850
|
+
value={config.openrouterApiKey}
|
|
851
|
+
onChange={(e) => updateField('openrouterApiKey', e.target.value)}
|
|
852
|
+
placeholder="Enter OpenRouter API Key..."
|
|
853
|
+
/>
|
|
854
|
+
</div>
|
|
855
|
+
<div className="setting-row">
|
|
856
|
+
<label>DeepSeek API Key</label>
|
|
857
|
+
<input
|
|
858
|
+
type="password"
|
|
859
|
+
value={config.deepseekApiKey}
|
|
860
|
+
onChange={(e) => updateField('deepseekApiKey', e.target.value)}
|
|
861
|
+
placeholder="Enter DeepSeek API Key..."
|
|
862
|
+
/>
|
|
863
|
+
</div>
|
|
864
|
+
<div className="setting-row">
|
|
865
|
+
<label>Anthropic API Key</label>
|
|
866
|
+
<input
|
|
867
|
+
type="password"
|
|
868
|
+
value={config.anthropicApiKey}
|
|
869
|
+
onChange={(e) => updateField('anthropicApiKey', e.target.value)}
|
|
870
|
+
placeholder="Enter Anthropic API Key..."
|
|
871
|
+
/>
|
|
872
|
+
</div>
|
|
873
|
+
<div className="setting-row">
|
|
874
|
+
<label>Hugging Face API Key</label>
|
|
875
|
+
<input
|
|
876
|
+
type="password"
|
|
877
|
+
value={config.hfApiKey}
|
|
878
|
+
onChange={(e) => updateField('hfApiKey', e.target.value)}
|
|
879
|
+
placeholder="Enter Hugging Face API Key..."
|
|
880
|
+
/>
|
|
881
|
+
</div>
|
|
882
|
+
<div className="setting-row">
|
|
883
|
+
<label>LM Studio Base URL</label>
|
|
884
|
+
<input
|
|
885
|
+
type="text"
|
|
886
|
+
value={config.localApiBaseUrl}
|
|
887
|
+
onChange={(e) => updateField('localApiBaseUrl', e.target.value)}
|
|
888
|
+
placeholder="e.g. http://localhost:1234/v1"
|
|
889
|
+
/>
|
|
890
|
+
</div>
|
|
891
|
+
<div className="setting-row">
|
|
892
|
+
<label>Ollama Host</label>
|
|
893
|
+
<input
|
|
894
|
+
type="text"
|
|
895
|
+
value={config.ollamaHost}
|
|
896
|
+
onChange={(e) => updateField('ollamaHost', e.target.value)}
|
|
897
|
+
placeholder="e.g. http://localhost:11434"
|
|
898
|
+
/>
|
|
899
|
+
</div>
|
|
900
|
+
</div>
|
|
901
|
+
</section>
|
|
902
|
+
|
|
903
|
+
<section className="setting-section">
|
|
904
|
+
<div className="section-heading">
|
|
905
|
+
<div>
|
|
906
|
+
<p className="section-kicker">Desktop</p>
|
|
907
|
+
<h2 class="section-title">Assistant Presence</h2>
|
|
908
|
+
</div>
|
|
909
|
+
</div>
|
|
910
|
+
<div className="toggle-row">
|
|
911
|
+
<div>
|
|
912
|
+
<label>Show Desktop AI Candidate</label>
|
|
913
|
+
<p className="hint">Show the mini AI character on your desktop.</p>
|
|
914
|
+
</div>
|
|
915
|
+
<label className="toggle-switch">
|
|
916
|
+
<input
|
|
917
|
+
type="checkbox"
|
|
918
|
+
checked={config.showDesktopWidget}
|
|
919
|
+
onChange={(e) => updateField('showDesktopWidget', e.target.checked)}
|
|
920
|
+
/>
|
|
921
|
+
<span className="toggle-slider"></span>
|
|
922
|
+
</label>
|
|
923
|
+
</div>
|
|
924
|
+
</section>
|
|
925
|
+
</div>
|
|
926
|
+
)}
|
|
927
|
+
|
|
928
|
+
{/* MEMORY SECTION */}
|
|
929
|
+
{activeTab === 'sect-memory' && (
|
|
930
|
+
<div className="tab-pane active animate-fade-in">
|
|
931
|
+
<section className="setting-section">
|
|
932
|
+
<div className="section-heading">
|
|
933
|
+
<div>
|
|
934
|
+
<p className="section-kicker">Cross-session Memory</p>
|
|
935
|
+
<h2 className="section-title">User Profile & Preferences</h2>
|
|
936
|
+
</div>
|
|
937
|
+
<p className="section-description">
|
|
938
|
+
Information and preferences stored here will be remembered across all conversations. Mint automatically learns and updates your profile details from your chats in the background, but you can also edit them manually.
|
|
939
|
+
</p>
|
|
940
|
+
</div>
|
|
941
|
+
|
|
942
|
+
<div className="form-grid single">
|
|
943
|
+
<div className="setting-row">
|
|
944
|
+
<label>Your Name / Nickname</label>
|
|
945
|
+
<input
|
|
946
|
+
type="text"
|
|
947
|
+
value={userName}
|
|
948
|
+
onChange={(e) => setUserName(e.target.value)}
|
|
949
|
+
placeholder="e.g. Pheem, Jane"
|
|
950
|
+
/>
|
|
951
|
+
<p className="hint">Used by the assistant to address you in conversation.</p>
|
|
952
|
+
</div>
|
|
953
|
+
|
|
954
|
+
<div className="setting-row">
|
|
955
|
+
<label>Custom Instructions & Preferences</label>
|
|
956
|
+
<textarea
|
|
957
|
+
value={userPreferences}
|
|
958
|
+
onChange={(e) => setUserPreferences(e.target.value)}
|
|
959
|
+
placeholder="e.g. Explain coding concepts step-by-step. Prefer TypeScript. Talk in Thai. Keep explanations concise."
|
|
960
|
+
style={{ minHeight: '150px', resize: 'vertical' }}
|
|
961
|
+
/>
|
|
962
|
+
<p className="hint">Preferences, guidelines, or persona instructions for the assistant.</p>
|
|
963
|
+
</div>
|
|
964
|
+
</div>
|
|
965
|
+
</section>
|
|
966
|
+
</div>
|
|
967
|
+
)}
|
|
968
|
+
|
|
969
|
+
{/* AUDIO SECTION */}
|
|
970
|
+
{activeTab === 'sect-audio' && (
|
|
971
|
+
<div className="tab-pane active">
|
|
972
|
+
<section className="setting-section">
|
|
973
|
+
<div className="section-heading">
|
|
974
|
+
<div>
|
|
975
|
+
<p className="section-kicker">Speech</p>
|
|
976
|
+
<h2 class="section-title">Voice Reply</h2>
|
|
977
|
+
</div>
|
|
978
|
+
<p className="section-description">Control spoken responses and TTS behavior.</p>
|
|
979
|
+
</div>
|
|
980
|
+
|
|
981
|
+
<div className="toggle-row">
|
|
982
|
+
<div>
|
|
983
|
+
<label>Enable Voice Reply</label>
|
|
984
|
+
<p className="hint">Mint will speak responses out loud when this is enabled.</p>
|
|
985
|
+
</div>
|
|
986
|
+
<label className="toggle-switch">
|
|
987
|
+
<input
|
|
988
|
+
type="checkbox"
|
|
989
|
+
checked={config.enableVoiceReply}
|
|
990
|
+
onChange={(e) => updateField('enableVoiceReply', e.target.checked)}
|
|
991
|
+
/>
|
|
992
|
+
<span className="toggle-slider"></span>
|
|
993
|
+
</label>
|
|
994
|
+
</div>
|
|
995
|
+
|
|
996
|
+
<div className="form-grid single">
|
|
997
|
+
<div className="setting-row">
|
|
998
|
+
<label>Voice Engine</label>
|
|
999
|
+
<select value={config.ttsProvider} onChange={(e) => updateField('ttsProvider', e.target.value)}>
|
|
1000
|
+
<option value="google">Google Cloud (Natural, Auto Lang)</option>
|
|
1001
|
+
<option value="native">OS Native (Supports Pitch)</option>
|
|
1002
|
+
</select>
|
|
1003
|
+
</div>
|
|
1004
|
+
</div>
|
|
1005
|
+
|
|
1006
|
+
<div className="slider-stack">
|
|
1007
|
+
<div className="setting-row">
|
|
1008
|
+
<label>Volume</label>
|
|
1009
|
+
<div className="slider-group">
|
|
1010
|
+
<input
|
|
1011
|
+
type="range"
|
|
1012
|
+
min="0"
|
|
1013
|
+
max="1"
|
|
1014
|
+
step="0.1"
|
|
1015
|
+
value={config.ttsVolume}
|
|
1016
|
+
onChange={(e) => updateField('ttsVolume', parseFloat(e.target.value))}
|
|
1017
|
+
className="range-slider"
|
|
1018
|
+
/>
|
|
1019
|
+
<span className="range-value">{Math.round(config.ttsVolume * 100)}%</span>
|
|
1020
|
+
</div>
|
|
1021
|
+
</div>
|
|
1022
|
+
<div className="setting-row">
|
|
1023
|
+
<label>Speed</label>
|
|
1024
|
+
<div className="slider-group">
|
|
1025
|
+
<input
|
|
1026
|
+
type="range"
|
|
1027
|
+
min="0.5"
|
|
1028
|
+
max="2"
|
|
1029
|
+
step="0.1"
|
|
1030
|
+
value={config.ttsSpeed}
|
|
1031
|
+
onChange={(e) => updateField('ttsSpeed', parseFloat(e.target.value))}
|
|
1032
|
+
className="range-slider"
|
|
1033
|
+
/>
|
|
1034
|
+
<span className="range-value">{parseFloat(String(config.ttsSpeed)).toFixed(1)}x</span>
|
|
1035
|
+
</div>
|
|
1036
|
+
</div>
|
|
1037
|
+
<div className="setting-row">
|
|
1038
|
+
<label>Pitch</label>
|
|
1039
|
+
<div className="slider-group">
|
|
1040
|
+
<input
|
|
1041
|
+
type="range"
|
|
1042
|
+
min="0"
|
|
1043
|
+
max="2"
|
|
1044
|
+
step="0.1"
|
|
1045
|
+
value={config.ttsPitch}
|
|
1046
|
+
onChange={(e) => updateField('ttsPitch', parseFloat(e.target.value))}
|
|
1047
|
+
className="range-slider"
|
|
1048
|
+
/>
|
|
1049
|
+
<span className="range-value">{parseFloat(String(config.ttsPitch)).toFixed(1)}</span>
|
|
1050
|
+
</div>
|
|
1051
|
+
<p className="hint">Pitch applies to OS native voice only.</p>
|
|
1052
|
+
</div>
|
|
1053
|
+
</div>
|
|
1054
|
+
</section>
|
|
1055
|
+
</div>
|
|
1056
|
+
)}
|
|
1057
|
+
|
|
1058
|
+
{/* AUTOMATION SECTION */}
|
|
1059
|
+
{activeTab === 'sect-automation' && (
|
|
1060
|
+
<div className="tab-pane active">
|
|
1061
|
+
<section className="setting-section">
|
|
1062
|
+
<div className="section-heading">
|
|
1063
|
+
<div>
|
|
1064
|
+
<p className="section-kicker">Browser</p>
|
|
1065
|
+
<h2 class="section-title">Automation Engine</h2>
|
|
1066
|
+
</div>
|
|
1067
|
+
</div>
|
|
1068
|
+
<div className="form-grid single">
|
|
1069
|
+
<div className="setting-row">
|
|
1070
|
+
<label>Browser Engine</label>
|
|
1071
|
+
<select value={config.automationBrowser} onChange={(e) => updateField('automationBrowser', e.target.value)}>
|
|
1072
|
+
<option value="chromium">Chromium (Bundled)</option>
|
|
1073
|
+
<option value="/usr/bin/firefox">Firefox (System - Linux)</option>
|
|
1074
|
+
</select>
|
|
1075
|
+
</div>
|
|
1076
|
+
<div className="setting-row">
|
|
1077
|
+
<label>Chromium DevTools Endpoint</label>
|
|
1078
|
+
<input type="text" value={config.browserDebugUrl} onChange={(e) => updateField('browserDebugUrl', e.target.value)} />
|
|
1079
|
+
<p className="hint">Required for native tab reading and selector clicks.</p>
|
|
1080
|
+
</div>
|
|
1081
|
+
<div className="setting-row">
|
|
1082
|
+
<label>Browser Extension Context Endpoint</label>
|
|
1083
|
+
<input type="text" value={config.browserExtensionContextUrl} onChange={(e) => updateField('browserExtensionContextUrl', e.target.value)} />
|
|
1084
|
+
<p className="hint">Fallback endpoint used when Chromium remote debugging is unavailable.</p>
|
|
1085
|
+
</div>
|
|
1086
|
+
</div>
|
|
1087
|
+
</section>
|
|
1088
|
+
|
|
1089
|
+
<section className="setting-section">
|
|
1090
|
+
<div className="section-heading">
|
|
1091
|
+
<div>
|
|
1092
|
+
<p className="section-kicker">Background tasks</p>
|
|
1093
|
+
<h2 className="section-title">Native Headless Queue</h2>
|
|
1094
|
+
</div>
|
|
1095
|
+
</div>
|
|
1096
|
+
<div className="toggle-row">
|
|
1097
|
+
<div>
|
|
1098
|
+
<label>Process queued tasks automatically</label>
|
|
1099
|
+
<p className="hint">Allow the bounded Rust worker to process pending tasks every 15 seconds.</p>
|
|
1100
|
+
</div>
|
|
1101
|
+
<label className="toggle-switch">
|
|
1102
|
+
<input
|
|
1103
|
+
type="checkbox"
|
|
1104
|
+
checked={config.enableHeadlessTaskQueue}
|
|
1105
|
+
onChange={(e) => updateField('enableHeadlessTaskQueue', e.target.checked)}
|
|
1106
|
+
/>
|
|
1107
|
+
<span className="toggle-slider"></span>
|
|
1108
|
+
</label>
|
|
1109
|
+
</div>
|
|
1110
|
+
</section>
|
|
1111
|
+
|
|
1112
|
+
<section className="setting-section">
|
|
1113
|
+
<div className="section-heading">
|
|
1114
|
+
<div>
|
|
1115
|
+
<p className="section-kicker">Awareness</p>
|
|
1116
|
+
<h2 class="section-title">Proactive Assistant</h2>
|
|
1117
|
+
</div>
|
|
1118
|
+
<p className="section-description">Tune screen analysis frequency and suggestion timing.</p>
|
|
1119
|
+
</div>
|
|
1120
|
+
<div className="slider-stack">
|
|
1121
|
+
<div className="setting-row">
|
|
1122
|
+
<label>Screen Capture Frequency</label>
|
|
1123
|
+
<div className="slider-group">
|
|
1124
|
+
<input
|
|
1125
|
+
type="range"
|
|
1126
|
+
min="30"
|
|
1127
|
+
max="300"
|
|
1128
|
+
step="30"
|
|
1129
|
+
value={config.proactiveInterval}
|
|
1130
|
+
onChange={(e) => updateField('proactiveInterval', parseInt(e.target.value))}
|
|
1131
|
+
className="range-slider"
|
|
1132
|
+
/>
|
|
1133
|
+
<span className="range-value">{config.proactiveInterval} sec</span>
|
|
1134
|
+
</div>
|
|
1135
|
+
<p className="hint">Lower values respond faster but use more API calls.</p>
|
|
1136
|
+
</div>
|
|
1137
|
+
<div className="setting-row">
|
|
1138
|
+
<label>Suggestion Cooldown</label>
|
|
1139
|
+
<div className="slider-group">
|
|
1140
|
+
<input
|
|
1141
|
+
type="range"
|
|
1142
|
+
min="60"
|
|
1143
|
+
max="600"
|
|
1144
|
+
step="60"
|
|
1145
|
+
value={config.proactiveCooldown}
|
|
1146
|
+
onChange={(e) => updateField('proactiveCooldown', parseInt(e.target.value))}
|
|
1147
|
+
className="range-slider"
|
|
1148
|
+
/>
|
|
1149
|
+
<span className="range-value">{Math.round(config.proactiveCooldown / 60)} min</span>
|
|
1150
|
+
</div>
|
|
1151
|
+
<p className="hint">Minimum time between repeat suggestions.</p>
|
|
1152
|
+
</div>
|
|
1153
|
+
</div>
|
|
1154
|
+
</section>
|
|
1155
|
+
|
|
1156
|
+
<section className="setting-section">
|
|
1157
|
+
<div className="section-heading">
|
|
1158
|
+
<div>
|
|
1159
|
+
<p className="section-kicker">Messaging</p>
|
|
1160
|
+
<h2 className="section-title">Native Channel Bridges</h2>
|
|
1161
|
+
</div>
|
|
1162
|
+
<p className="section-description">Configure credentials used directly by the Rust channel workers.</p>
|
|
1163
|
+
</div>
|
|
1164
|
+
<div className="form-grid">
|
|
1165
|
+
<label className="toggle-row"><span>Telegram Bot API</span><input type="checkbox" checked={config.enableTelegramBridge} onChange={(e) => updateField('enableTelegramBridge', e.target.checked)} /></label>
|
|
1166
|
+
<input type="password" placeholder="Telegram bot token" value={config.telegramBotToken} onChange={(e) => updateField('telegramBotToken', e.target.value)} />
|
|
1167
|
+
<label className="toggle-row"><span>Discord Gateway</span><input type="checkbox" checked={config.enableDiscordBridge} onChange={(e) => updateField('enableDiscordBridge', e.target.checked)} /></label>
|
|
1168
|
+
<input type="password" placeholder="Discord bot token" value={config.discordBotToken} onChange={(e) => updateField('discordBotToken', e.target.value)} />
|
|
1169
|
+
<input type="text" placeholder="Discord application ID for Rich Presence" value={config.discordApplicationId} onChange={(e) => updateField('discordApplicationId', e.target.value)} />
|
|
1170
|
+
<label className="toggle-row"><span>Slack Socket Mode</span><input type="checkbox" checked={config.enableSlackBridge} onChange={(e) => updateField('enableSlackBridge', e.target.checked)} /></label>
|
|
1171
|
+
<input type="password" placeholder="Slack bot token (xoxb-...)" value={config.slackBotToken} onChange={(e) => updateField('slackBotToken', e.target.value)} />
|
|
1172
|
+
<input type="password" placeholder="Slack app token (xapp-...)" value={config.slackAppToken} onChange={(e) => updateField('slackAppToken', e.target.value)} />
|
|
1173
|
+
<label className="toggle-row"><span>LINE Webhook</span><input type="checkbox" checked={config.enableLineBridge} onChange={(e) => updateField('enableLineBridge', e.target.checked)} /></label>
|
|
1174
|
+
<input type="password" placeholder="LINE channel access token" value={config.lineChannelAccessToken} onChange={(e) => updateField('lineChannelAccessToken', e.target.value)} />
|
|
1175
|
+
<input type="password" placeholder="LINE channel secret" value={config.lineChannelSecret} onChange={(e) => updateField('lineChannelSecret', e.target.value)} />
|
|
1176
|
+
<label className="toggle-row"><span>WhatsApp Cloud API</span><input type="checkbox" checked={config.enableWhatsappBridge} onChange={(e) => updateField('enableWhatsappBridge', e.target.checked)} /></label>
|
|
1177
|
+
<input type="password" placeholder="WhatsApp Cloud access token" value={config.whatsappCloudAccessToken} onChange={(e) => updateField('whatsappCloudAccessToken', e.target.value)} />
|
|
1178
|
+
<input type="text" placeholder="WhatsApp phone number ID" value={config.whatsappPhoneNumberId} onChange={(e) => updateField('whatsappPhoneNumberId', e.target.value)} />
|
|
1179
|
+
<input type="password" placeholder="WhatsApp verify token" value={config.whatsappVerifyToken} onChange={(e) => updateField('whatsappVerifyToken', e.target.value)} />
|
|
1180
|
+
<input type="password" placeholder="WhatsApp app secret (optional HMAC validation)" value={config.whatsappAppSecret} onChange={(e) => updateField('whatsappAppSecret', e.target.value)} />
|
|
1181
|
+
</div>
|
|
1182
|
+
</section>
|
|
1183
|
+
|
|
1184
|
+
<section className="setting-section">
|
|
1185
|
+
<div className="section-heading">
|
|
1186
|
+
<div>
|
|
1187
|
+
<p className="section-kicker">Rules</p>
|
|
1188
|
+
<h2 class="section-title">Custom Workflows</h2>
|
|
1189
|
+
</div>
|
|
1190
|
+
</div>
|
|
1191
|
+
<div className="toggle-row">
|
|
1192
|
+
<div>
|
|
1193
|
+
<label>Enable Custom Workflows</label>
|
|
1194
|
+
<p className="hint">Run "If This Then Mint" rules from the workflow JSON file.</p>
|
|
1195
|
+
</div>
|
|
1196
|
+
<label className="toggle-switch">
|
|
1197
|
+
<input
|
|
1198
|
+
type="checkbox"
|
|
1199
|
+
checked={config.enableCustomWorkflows}
|
|
1200
|
+
onChange={(e) => updateField('enableCustomWorkflows', e.target.checked)}
|
|
1201
|
+
/>
|
|
1202
|
+
<span className="toggle-slider"></span>
|
|
1203
|
+
</label>
|
|
1204
|
+
</div>
|
|
1205
|
+
<div className="button-row" style={{ display: 'flex', gap: '10px', marginTop: '10px' }}>
|
|
1206
|
+
<button className="btn btn-secondary" onClick={handleOpenWorkflows}>Open workflows.json</button>
|
|
1207
|
+
<button className="btn btn-primary" onClick={handleReloadWorkflows}>Reload Rules</button>
|
|
1208
|
+
</div>
|
|
1209
|
+
</section>
|
|
1210
|
+
|
|
1211
|
+
<section className="setting-section">
|
|
1212
|
+
<div className="section-heading">
|
|
1213
|
+
<div>
|
|
1214
|
+
<p className="section-kicker">Code mode</p>
|
|
1215
|
+
<h2 class="section-title">Agent Collaboration</h2>
|
|
1216
|
+
</div>
|
|
1217
|
+
</div>
|
|
1218
|
+
<div className="toggle-row">
|
|
1219
|
+
<div>
|
|
1220
|
+
<label>Enable Multi-Agent Review</label>
|
|
1221
|
+
<p className="hint">Allow a secondary model to review code written by the primary model.</p>
|
|
1222
|
+
</div>
|
|
1223
|
+
<label className="toggle-switch">
|
|
1224
|
+
<input
|
|
1225
|
+
type="checkbox"
|
|
1226
|
+
checked={config.enableAgentCollaboration}
|
|
1227
|
+
onChange={(e) => updateField('enableAgentCollaboration', e.target.checked)}
|
|
1228
|
+
/>
|
|
1229
|
+
<span className="toggle-slider"></span>
|
|
1230
|
+
</label>
|
|
1231
|
+
</div>
|
|
1232
|
+
</section>
|
|
1233
|
+
</div>
|
|
1234
|
+
)}
|
|
1235
|
+
|
|
1236
|
+
{/* THEME & UI SECTION */}
|
|
1237
|
+
{activeTab === 'sect-theme' && (
|
|
1238
|
+
<div className="tab-pane active">
|
|
1239
|
+
<section className="setting-section">
|
|
1240
|
+
<div className="section-heading">
|
|
1241
|
+
<div>
|
|
1242
|
+
<p className="section-kicker">Appearance</p>
|
|
1243
|
+
<h2 class="section-title">Theme</h2>
|
|
1244
|
+
</div>
|
|
1245
|
+
</div>
|
|
1246
|
+
<div className="theme-grid" style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '12px' }}>
|
|
1247
|
+
{(['dark', 'light', 'midnight', 'custom'] as const).map(t => (
|
|
1248
|
+
<button
|
|
1249
|
+
key={t}
|
|
1250
|
+
className={`theme-card ${config.theme === t ? 'active' : ''}`}
|
|
1251
|
+
onClick={() => updateField('theme', t)}
|
|
1252
|
+
>
|
|
1253
|
+
<div className={`theme-preview ${t}-preview`}></div>
|
|
1254
|
+
<span style={{ textTransform: 'capitalize' }}>{t}</span>
|
|
1255
|
+
</button>
|
|
1256
|
+
))}
|
|
1257
|
+
</div>
|
|
1258
|
+
|
|
1259
|
+
{config.theme === 'custom' && (
|
|
1260
|
+
<div className="custom-theme-panel" style={{ marginTop: '15px' }}>
|
|
1261
|
+
<div className="setting-row">
|
|
1262
|
+
<label>Background Gradient</label>
|
|
1263
|
+
<div className="color-range" style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
|
1264
|
+
<input type="color" value={config.customBgStart} onChange={(e) => updateField('customBgStart', e.target.value)} />
|
|
1265
|
+
<span style={{ display: 'inline-flex', alignItems: 'center', color: 'var(--text-soft)' }}>
|
|
1266
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
1267
|
+
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
1268
|
+
<polyline points="12 5 19 12 12 19"></polyline>
|
|
1269
|
+
</svg>
|
|
1270
|
+
</span>
|
|
1271
|
+
<input type="color" value={config.customBgEnd} onChange={(e) => updateField('customBgEnd', e.target.value)} />
|
|
1272
|
+
</div>
|
|
1273
|
+
</div>
|
|
1274
|
+
<div className="setting-row">
|
|
1275
|
+
<label>Panel Background</label>
|
|
1276
|
+
<input type="color" value={config.customPanelBg} onChange={(e) => updateField('customPanelBg', e.target.value)} />
|
|
1277
|
+
</div>
|
|
1278
|
+
</div>
|
|
1279
|
+
)}
|
|
1280
|
+
</section>
|
|
1281
|
+
|
|
1282
|
+
<section className="setting-section">
|
|
1283
|
+
<div className="section-heading">
|
|
1284
|
+
<div>
|
|
1285
|
+
<p className="section-kicker">Color</p>
|
|
1286
|
+
<h2 class="section-title">Accent & Text</h2>
|
|
1287
|
+
</div>
|
|
1288
|
+
</div>
|
|
1289
|
+
<div className="color-section">
|
|
1290
|
+
<div>
|
|
1291
|
+
<label>Accent Color</label>
|
|
1292
|
+
<div className="color-presets">
|
|
1293
|
+
{['#8b5cf6', '#06b6d4', '#10b981', '#f59e0b', '#ef4444', '#ec4899'].map(c => (
|
|
1294
|
+
<button
|
|
1295
|
+
key={c}
|
|
1296
|
+
className="color-dot"
|
|
1297
|
+
style={{ backgroundColor: c, border: config.accentColor === c ? '2px solid white' : 'none' }}
|
|
1298
|
+
onClick={() => updateField('accentColor', c)}
|
|
1299
|
+
/>
|
|
1300
|
+
))}
|
|
1301
|
+
</div>
|
|
1302
|
+
</div>
|
|
1303
|
+
<div className="color-inputs" style={{ display: 'flex', gap: '15px', marginTop: '12px' }}>
|
|
1304
|
+
<div>
|
|
1305
|
+
<label>Custom Accent</label>
|
|
1306
|
+
<input type="color" value={config.accentColor} onChange={(e) => updateField('accentColor', e.target.value)} style={{ display: 'block', marginTop: '4px' }} />
|
|
1307
|
+
</div>
|
|
1308
|
+
<div>
|
|
1309
|
+
<label>System Text</label>
|
|
1310
|
+
<input type="color" value={config.systemTextColor} onChange={(e) => updateField('systemTextColor', e.target.value)} style={{ display: 'block', marginTop: '4px' }} />
|
|
1311
|
+
</div>
|
|
1312
|
+
</div>
|
|
1313
|
+
</div>
|
|
1314
|
+
</section>
|
|
1315
|
+
|
|
1316
|
+
<section className="setting-section">
|
|
1317
|
+
<div className="section-heading">
|
|
1318
|
+
<div>
|
|
1319
|
+
<p className="section-kicker">Surface</p>
|
|
1320
|
+
<h2 class="section-title">Interface Style</h2>
|
|
1321
|
+
</div>
|
|
1322
|
+
</div>
|
|
1323
|
+
<div className="form-grid">
|
|
1324
|
+
<div className="setting-row">
|
|
1325
|
+
<label>Glass Blur</label>
|
|
1326
|
+
<select value={config.glassBlur} onChange={(e) => updateField('glassBlur', e.target.value)}>
|
|
1327
|
+
<option value="blur(4px)">Low (4px)</option>
|
|
1328
|
+
<option value="blur(16px)">Medium (16px) - Default</option>
|
|
1329
|
+
<option value="blur(32px)">High (32px)</option>
|
|
1330
|
+
<option value="none">Off (Solid)</option>
|
|
1331
|
+
</select>
|
|
1332
|
+
</div>
|
|
1333
|
+
<div className="setting-row">
|
|
1334
|
+
<label>Font Family</label>
|
|
1335
|
+
<select value={config.fontFamily} onChange={(e) => updateField('fontFamily', e.target.value)}>
|
|
1336
|
+
<option value="'Outfit', sans-serif">Outfit (Default)</option>
|
|
1337
|
+
<option value="'Mali', cursive">Mali (Cute Thai Font)</option>
|
|
1338
|
+
<option value="'Prompt', sans-serif">Prompt (Modern Thai)</option>
|
|
1339
|
+
<option value="'Sarabun', sans-serif">Sarabun (Formal Thai)</option>
|
|
1340
|
+
</select>
|
|
1341
|
+
</div>
|
|
1342
|
+
<div className="setting-row">
|
|
1343
|
+
<label>Font Size</label>
|
|
1344
|
+
<select value={config.fontSize} onChange={(e) => updateField('fontSize', e.target.value)}>
|
|
1345
|
+
<option value="14px">Small</option>
|
|
1346
|
+
<option value="15px">Medium (Default)</option>
|
|
1347
|
+
<option value="16px">Large</option>
|
|
1348
|
+
<option value="17px">Extra Large</option>
|
|
1349
|
+
</select>
|
|
1350
|
+
</div>
|
|
1351
|
+
</div>
|
|
1352
|
+
</section>
|
|
1353
|
+
</div>
|
|
1354
|
+
)}
|
|
1355
|
+
|
|
1356
|
+
{/* PLUGINS SECTION */}
|
|
1357
|
+
{activeTab === 'sect-plugins' && (
|
|
1358
|
+
<div className="tab-pane active">
|
|
1359
|
+
<section className="setting-section">
|
|
1360
|
+
<div className="section-heading">
|
|
1361
|
+
<div>
|
|
1362
|
+
<p className="section-kicker">Integrations</p>
|
|
1363
|
+
<h2 class="section-title">Built-in Plugins</h2>
|
|
1364
|
+
</div>
|
|
1365
|
+
</div>
|
|
1366
|
+
<div className="plugin-list">
|
|
1367
|
+
{[
|
|
1368
|
+
{ key: 'spotify', name: 'Spotify', desc: 'Control playback with AI. Requires playerctl.', icon: '🎵' },
|
|
1369
|
+
{ key: 'calendar', name: 'Google Calendar', desc: 'Read and schedule events.', icon: '📅' },
|
|
1370
|
+
{ key: 'gmail', name: 'Gmail', desc: 'Read email and create drafts safely.', icon: '✉' },
|
|
1371
|
+
{ key: 'notion', name: 'Notion', desc: 'Create notes, pages, and read databases.', icon: '📝' },
|
|
1372
|
+
{ key: 'discord', name: 'Discord RPC', desc: 'Show "Using Mint Assistant" on Discord status.', icon: '💬' },
|
|
1373
|
+
].map(p => (
|
|
1374
|
+
<div className="plugin-card" key={p.key}>
|
|
1375
|
+
<div className="plugin-icon">{p.icon}</div>
|
|
1376
|
+
<div className="plugin-info">
|
|
1377
|
+
<div className="plugin-name">{p.name}</div>
|
|
1378
|
+
<div className="plugin-desc">{p.desc}</div>
|
|
1379
|
+
</div>
|
|
1380
|
+
<div className="plugin-actions">
|
|
1381
|
+
<button className="btn-connect" onClick={() => handleConnectPlugin(p.key)}>Connect</button>
|
|
1382
|
+
</div>
|
|
1383
|
+
</div>
|
|
1384
|
+
))}
|
|
1385
|
+
</div>
|
|
1386
|
+
</section>
|
|
1387
|
+
|
|
1388
|
+
<section className="setting-section">
|
|
1389
|
+
<div className="section-heading">
|
|
1390
|
+
<div>
|
|
1391
|
+
<p className="section-kicker">External tools</p>
|
|
1392
|
+
<h2 class="section-title">MCP Servers</h2>
|
|
1393
|
+
</div>
|
|
1394
|
+
<p className="section-description">Connect Mint to tools like search, GitHub, or filesystem servers.</p>
|
|
1395
|
+
</div>
|
|
1396
|
+
|
|
1397
|
+
<div className="mcp-list">
|
|
1398
|
+
{Object.entries(config.mcpServers || {}).map(([name, srv]: [string, any]) => (
|
|
1399
|
+
<div className="plugin-card" key={name} style={{ marginBottom: '10px' }}>
|
|
1400
|
+
<div className="plugin-icon">⚙</div>
|
|
1401
|
+
<div className="plugin-info">
|
|
1402
|
+
<div className="plugin-name">{name}</div>
|
|
1403
|
+
<div className="plugin-desc" style={{ fontFamily: 'monospace', fontSize: '0.8rem' }}>
|
|
1404
|
+
Command: {srv.command} {srv.args?.join(' ')}
|
|
1405
|
+
</div>
|
|
1406
|
+
</div>
|
|
1407
|
+
<div className="plugin-actions">
|
|
1408
|
+
<button className="btn btn-danger" onClick={() => handleRemoveMcpServer(name)}>Remove</button>
|
|
1409
|
+
</div>
|
|
1410
|
+
</div>
|
|
1411
|
+
))}
|
|
1412
|
+
</div>
|
|
1413
|
+
|
|
1414
|
+
<div className="add-mcp-box">
|
|
1415
|
+
<h3>Add MCP Server</h3>
|
|
1416
|
+
<div className="form-grid">
|
|
1417
|
+
<input type="text" placeholder="Server Name (e.g. google-search)" value={mcpName} onChange={(e) => setMcpName(e.target.value)} />
|
|
1418
|
+
<input type="text" placeholder="Command (e.g. npx)" value={mcpCmd} onChange={(e) => setMcpCmd(e.target.value)} />
|
|
1419
|
+
</div>
|
|
1420
|
+
<input type="text" placeholder="Arguments (e.g. -y @modelcontextprotocol/server-brave-search)" value={mcpArgs} onChange={(e) => setMcpArgs(e.target.value)} style={{ width: '100%', marginTop: '8px' }} />
|
|
1421
|
+
<textarea placeholder='Env JSON, e.g. {"BRAVE_API_KEY": "..."}' value={mcpEnv} onChange={(e) => setMcpEnv(e.target.value)} style={{ width: '100%', marginTop: '8px', height: '60px' }}></textarea>
|
|
1422
|
+
<button className="btn-primary full-width" onClick={handleAddMcpServer} style={{ marginTop: '10px', width: '100%' }}>Add MCP Server</button>
|
|
1423
|
+
</div>
|
|
1424
|
+
</section>
|
|
1425
|
+
</div>
|
|
1426
|
+
)}
|
|
1427
|
+
|
|
1428
|
+
{/* KEYBOARD SHORTCUTS SECTION */}
|
|
1429
|
+
{activeTab === 'sect-shortcuts' && (
|
|
1430
|
+
<div className="tab-pane active">
|
|
1431
|
+
<section className="setting-section">
|
|
1432
|
+
<div className="section-heading">
|
|
1433
|
+
<div>
|
|
1434
|
+
<p className="section-kicker">Keyboard</p>
|
|
1435
|
+
<h2 class="section-title">Shortcuts</h2>
|
|
1436
|
+
</div>
|
|
1437
|
+
</div>
|
|
1438
|
+
<div className="shortcut-list">
|
|
1439
|
+
<div className="shortcut-item">
|
|
1440
|
+
<span>Show / Hide Mint Window</span>
|
|
1441
|
+
<div className="keys"><kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>Space</kbd></div>
|
|
1442
|
+
</div>
|
|
1443
|
+
<div className="shortcut-item">
|
|
1444
|
+
<span>Open Spotlight</span>
|
|
1445
|
+
<div className="keys"><kbd>Alt</kbd><kbd>Space</kbd></div>
|
|
1446
|
+
</div>
|
|
1447
|
+
<div className="shortcut-item">
|
|
1448
|
+
<span>Close / Dismiss</span>
|
|
1449
|
+
<div className="keys"><kbd>Esc</kbd></div>
|
|
1450
|
+
</div>
|
|
1451
|
+
</div>
|
|
1452
|
+
</section>
|
|
1453
|
+
</div>
|
|
1454
|
+
)}
|
|
1455
|
+
</div>
|
|
1456
|
+
</main>
|
|
1457
|
+
|
|
1458
|
+
<footer className="settings-footer">
|
|
1459
|
+
<button className="btn-danger" onClick={handleQuit}>Quit Application</button>
|
|
1460
|
+
<div className="footer-actions">
|
|
1461
|
+
<button className="btn-secondary" onClick={handleReset}>Reset to Default</button>
|
|
1462
|
+
<button className="btn-primary" onClick={handleSave}>Save Settings</button>
|
|
1463
|
+
</div>
|
|
1464
|
+
</footer>
|
|
1465
|
+
</div>
|
|
1466
|
+
)
|
|
1467
|
+
}
|