@pheem49/mint 1.8.2 → 1.9.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.toml +1 -1
- package/Release_Note.md +48 -3
- package/crates/mint-cli/src/agent.rs +95 -84
- package/crates/mint-cli/src/image.rs +8 -8
- package/crates/mint-cli/src/main.rs +806 -403
- package/crates/mint-cli/src/onboard.rs +108 -108
- package/crates/mint-cli/src/setup.rs +143 -143
- package/crates/mint-core/src/api_server.rs +57 -54
- package/crates/mint-core/src/browser.rs +1 -1
- package/crates/mint-core/src/chat.rs +123 -8
- package/crates/mint-core/src/code_tools.rs +18 -20
- package/crates/mint-core/src/config.rs +184 -17
- package/crates/mint-core/src/image_gen.rs +5 -5
- package/crates/mint-core/src/lib.rs +5 -5
- package/crates/mint-core/src/memory.rs +6 -1
- package/crates/mint-core/src/orchestration.rs +104 -85
- package/crates/mint-core/src/pictures.rs +3 -4
- package/crates/mint-core/src/semantic.rs +1 -1
- package/crates/mint-core/src/shell.rs +1 -1
- package/crates/mint-core/src/skills.rs +46 -19
- package/package.json +1 -1
- package/shared-modules/linux-audio/fluidsynth2-static.json +9 -3
- package/shared-modules/linux-audio/fluidsynth2.json +8 -2
- package/shared-modules/linux-audio/lilv.json +26 -19
- package/shared-modules/linux-audio/lv2.json +0 -1
- package/src/renderer/shared/components/AgentActivityTable.tsx +25 -6
- package/src/renderer/shared/utils/fileIcons.ts +135 -0
- package/src/renderer/src/components/ChatPanel.tsx +25 -5
- package/src/renderer/src/components/MintDashboard.tsx +45 -2
- package/src/renderer/src/components/Settings/AgentsTab.tsx +21 -2
- package/src/renderer/src/components/Settings/GeneralTab.tsx +256 -2
- package/src/renderer/src/components/SettingsWindow.tsx +68 -1
- package/src/renderer/src/components/WorkspacePanel.tsx +7 -125
- package/src/renderer/src/css/styles.css +16 -0
- package/src/renderer/src-web/components/ChatPanel.tsx +25 -5
- package/src/renderer/src-web/components/MintDashboard.tsx +19 -1
- package/src/renderer/src-web/components/Settings/AgentsTab.tsx +21 -2
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +254 -3
- package/src/renderer/src-web/components/SettingsWindow.tsx +75 -1
- package/src/renderer/src-web/css/styles.css +16 -0
- package/src-tauri/src/desktop.rs +18 -16
- package/src-tauri/src/discord_rpc.rs +1 -1
- package/src-tauri/src/lib.rs +41 -17
- package/src-tauri/tauri.conf.json +1 -1
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
DEEPSEEK_MODELS,
|
|
8
8
|
ANTHROPIC_MODELS,
|
|
9
9
|
HF_MODELS,
|
|
10
|
-
LOCAL_MODELS
|
|
10
|
+
LOCAL_MODELS,
|
|
11
|
+
CustomProviderConfig,
|
|
12
|
+
CustomProviderModel,
|
|
13
|
+
CustomProviderHeader,
|
|
11
14
|
} from '../SettingsWindow'
|
|
12
15
|
|
|
13
16
|
interface GeneralTabProps {
|
|
@@ -35,6 +38,7 @@ interface GeneralTabProps {
|
|
|
35
38
|
handleCheckUpdates: () => void
|
|
36
39
|
handleInstallUpdate: () => void
|
|
37
40
|
isDesktopApp?: boolean
|
|
41
|
+
onSaveWithoutClosing?: () => Promise<void>
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
export default function GeneralTab({
|
|
@@ -61,8 +65,18 @@ export default function GeneralTab({
|
|
|
61
65
|
updateMessage,
|
|
62
66
|
handleCheckUpdates,
|
|
63
67
|
handleInstallUpdate,
|
|
64
|
-
isDesktopApp = true
|
|
68
|
+
isDesktopApp = true,
|
|
69
|
+
onSaveWithoutClosing
|
|
65
70
|
}: GeneralTabProps) {
|
|
71
|
+
const [savedProviderId, setSavedProviderId] = React.useState<string | null>(null)
|
|
72
|
+
|
|
73
|
+
const handleSaveProvider = async (providerId: string) => {
|
|
74
|
+
if (onSaveWithoutClosing) {
|
|
75
|
+
await onSaveWithoutClosing()
|
|
76
|
+
setSavedProviderId(providerId)
|
|
77
|
+
setTimeout(() => setSavedProviderId(null), 2000)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
66
80
|
return (
|
|
67
81
|
<div className="tab-pane active">
|
|
68
82
|
<section className="setting-section">
|
|
@@ -86,8 +100,38 @@ export default function GeneralTab({
|
|
|
86
100
|
<option value="ollama">Ollama (Local / Private)</option>
|
|
87
101
|
<option value="huggingface">Hugging Face (Inference API)</option>
|
|
88
102
|
<option value="local_openai">Local (LM Studio / OpenAI Compatible)</option>
|
|
103
|
+
{(config.customProviders ?? []).map(cp => (
|
|
104
|
+
<option key={`custom:${cp.id}`} value={`custom:${cp.id}`}>
|
|
105
|
+
{cp.displayName || cp.id} (Custom)
|
|
106
|
+
</option>
|
|
107
|
+
))}
|
|
89
108
|
</select>
|
|
90
109
|
</div>
|
|
110
|
+
{/* Model selector for custom providers */}
|
|
111
|
+
{config.aiProvider.startsWith('custom:') && (() => {
|
|
112
|
+
const activeId = config.aiProvider.replace(/^custom:/, '')
|
|
113
|
+
const cp = (config.customProviders ?? []).find(p => p.id === activeId)
|
|
114
|
+
if (!cp || cp.models.length === 0) return null
|
|
115
|
+
const currentModel = (config.customModelSelections ?? {})[activeId] ?? cp.models[0]?.modelId ?? ''
|
|
116
|
+
return (
|
|
117
|
+
<div className="setting-row wide">
|
|
118
|
+
<label>Active Model</label>
|
|
119
|
+
<select
|
|
120
|
+
value={currentModel}
|
|
121
|
+
onChange={(e) => updateField('customModelSelections', {
|
|
122
|
+
...(config.customModelSelections ?? {}),
|
|
123
|
+
[activeId]: e.target.value
|
|
124
|
+
})}
|
|
125
|
+
>
|
|
126
|
+
{cp.models.map(m => (
|
|
127
|
+
<option key={m.modelId} value={m.modelId}>
|
|
128
|
+
{m.displayName || m.modelId}
|
|
129
|
+
</option>
|
|
130
|
+
))}
|
|
131
|
+
</select>
|
|
132
|
+
</div>
|
|
133
|
+
)
|
|
134
|
+
})()}
|
|
91
135
|
</div>
|
|
92
136
|
|
|
93
137
|
<div className="provider-cards-container">
|
|
@@ -877,6 +921,216 @@ export default function GeneralTab({
|
|
|
877
921
|
</div>
|
|
878
922
|
</section>
|
|
879
923
|
|
|
924
|
+
{/* ─── Custom Providers ─────────────────────────────────────────────────── */}
|
|
925
|
+
<section className="setting-section">
|
|
926
|
+
<div className="section-heading">
|
|
927
|
+
<div>
|
|
928
|
+
<p className="section-kicker">Custom AI Routing</p>
|
|
929
|
+
<h2 className="section-title">Custom Providers</h2>
|
|
930
|
+
</div>
|
|
931
|
+
<p className="section-description">
|
|
932
|
+
Add any OpenAI-compatible endpoint as a custom provider. Configure its models and optional headers.
|
|
933
|
+
</p>
|
|
934
|
+
</div>
|
|
935
|
+
|
|
936
|
+
<div className="provider-cards-container">
|
|
937
|
+
{(config.customProviders ?? []).map((cp, cpIdx) => {
|
|
938
|
+
const isActive = config.aiProvider === `custom:${cp.id}`
|
|
939
|
+
|
|
940
|
+
const updateCp = (patch: Partial<CustomProviderConfig>) => {
|
|
941
|
+
const updated = (config.customProviders ?? []).map((p, i) =>
|
|
942
|
+
i === cpIdx ? { ...p, ...patch } : p
|
|
943
|
+
)
|
|
944
|
+
updateField('customProviders', updated)
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const deleteCp = () => {
|
|
948
|
+
const updated = (config.customProviders ?? []).filter((_, i) => i !== cpIdx)
|
|
949
|
+
updateField('customProviders', updated)
|
|
950
|
+
// If this was the active provider, reset to gemini
|
|
951
|
+
if (config.aiProvider === `custom:${cp.id}`) {
|
|
952
|
+
updateField('aiProvider', 'gemini')
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
const updateModel = (mIdx: number, patch: Partial<CustomProviderModel>) => {
|
|
957
|
+
const updatedModels = cp.models.map((m, i) => i === mIdx ? { ...m, ...patch } : m)
|
|
958
|
+
updateCp({ models: updatedModels })
|
|
959
|
+
}
|
|
960
|
+
const deleteModel = (mIdx: number) => {
|
|
961
|
+
updateCp({ models: cp.models.filter((_, i) => i !== mIdx) })
|
|
962
|
+
}
|
|
963
|
+
const addModel = () => {
|
|
964
|
+
updateCp({ models: [...cp.models, { modelId: '', displayName: '' }] })
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
const updateHeader = (hIdx: number, patch: Partial<CustomProviderHeader>) => {
|
|
968
|
+
const updatedHeaders = cp.headers.map((h, i) => i === hIdx ? { ...h, ...patch } : h)
|
|
969
|
+
updateCp({ headers: updatedHeaders })
|
|
970
|
+
}
|
|
971
|
+
const deleteHeader = (hIdx: number) => {
|
|
972
|
+
updateCp({ headers: cp.headers.filter((_, i) => i !== hIdx) })
|
|
973
|
+
}
|
|
974
|
+
const addHeader = () => {
|
|
975
|
+
updateCp({ headers: [...cp.headers, { name: '', value: '' }] })
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
return (
|
|
979
|
+
<div key={cpIdx} className={`provider-card ${isActive ? 'active-provider' : ''}`}>
|
|
980
|
+
<div className="provider-card-header">
|
|
981
|
+
<div className="provider-card-title">
|
|
982
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
983
|
+
<circle cx="12" cy="12" r="3" /><path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14" />
|
|
984
|
+
</svg>
|
|
985
|
+
{cp.displayName || cp.id || 'Unnamed Provider'}
|
|
986
|
+
</div>
|
|
987
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
988
|
+
{isActive && <span className="provider-active-badge">Active</span>}
|
|
989
|
+
<button
|
|
990
|
+
className="btn btn-primary"
|
|
991
|
+
style={{ padding: '3px 8px', fontSize: '12px', background: savedProviderId === cp.id ? '#10b981' : '' }}
|
|
992
|
+
onClick={() => handleSaveProvider(cp.id)}
|
|
993
|
+
title="Save this provider settings"
|
|
994
|
+
>
|
|
995
|
+
{savedProviderId === cp.id ? 'Saved! ✓' : 'Save'}
|
|
996
|
+
</button>
|
|
997
|
+
<button
|
|
998
|
+
className="btn btn-danger"
|
|
999
|
+
style={{ padding: '3px 8px', fontSize: '12px' }}
|
|
1000
|
+
onClick={deleteCp}
|
|
1001
|
+
title="Delete this provider"
|
|
1002
|
+
>
|
|
1003
|
+
Delete
|
|
1004
|
+
</button>
|
|
1005
|
+
</div>
|
|
1006
|
+
</div>
|
|
1007
|
+
|
|
1008
|
+
<div className="provider-card-body">
|
|
1009
|
+
<div className="setting-row">
|
|
1010
|
+
<label>Name</label>
|
|
1011
|
+
<input
|
|
1012
|
+
type="text"
|
|
1013
|
+
value={cp.displayName}
|
|
1014
|
+
onChange={(e) => {
|
|
1015
|
+
const name = e.target.value
|
|
1016
|
+
const computedId = name
|
|
1017
|
+
.toLowerCase()
|
|
1018
|
+
.replace(/[^a-z0-9_-]/g, '-')
|
|
1019
|
+
.replace(/-+/g, '-')
|
|
1020
|
+
.replace(/^-|-$/g, '') || `provider-${cpIdx + 1}`
|
|
1021
|
+
updateCp({ displayName: name, id: computedId })
|
|
1022
|
+
}}
|
|
1023
|
+
placeholder="e.g. DeepSeek"
|
|
1024
|
+
/>
|
|
1025
|
+
</div>
|
|
1026
|
+
|
|
1027
|
+
{/* Endpoint */}
|
|
1028
|
+
<div className="setting-row">
|
|
1029
|
+
<label>Base URL</label>
|
|
1030
|
+
<input
|
|
1031
|
+
type="text"
|
|
1032
|
+
value={cp.baseUrl}
|
|
1033
|
+
onChange={(e) => updateCp({ baseUrl: e.target.value })}
|
|
1034
|
+
placeholder="https://api.myprovider.com/v1"
|
|
1035
|
+
/>
|
|
1036
|
+
</div>
|
|
1037
|
+
|
|
1038
|
+
<div className="setting-row">
|
|
1039
|
+
<label>API Key</label>
|
|
1040
|
+
<input
|
|
1041
|
+
type="password"
|
|
1042
|
+
value={cp.apiKey}
|
|
1043
|
+
onChange={(e) => updateCp({ apiKey: e.target.value })}
|
|
1044
|
+
placeholder="Optional. Leave empty if you manage auth via headers."
|
|
1045
|
+
/>
|
|
1046
|
+
</div>
|
|
1047
|
+
|
|
1048
|
+
{/* Models */}
|
|
1049
|
+
<div style={{ marginTop: '12px' }}>
|
|
1050
|
+
<label style={{ fontWeight: 600, fontSize: '13px', display: 'block', marginBottom: '8px' }}>Model list</label>
|
|
1051
|
+
{cp.models.map((m, mIdx) => (
|
|
1052
|
+
<div key={mIdx} style={{ display: 'flex', gap: '8px', marginBottom: '6px', alignItems: 'center' }}>
|
|
1053
|
+
<input
|
|
1054
|
+
type="text"
|
|
1055
|
+
value={m.modelId}
|
|
1056
|
+
onChange={(e) => updateModel(mIdx, { modelId: e.target.value, displayName: e.target.value })}
|
|
1057
|
+
placeholder="e.g. llama-3.1-8b"
|
|
1058
|
+
style={{ flex: 1 }}
|
|
1059
|
+
/>
|
|
1060
|
+
<button
|
|
1061
|
+
className="btn btn-danger"
|
|
1062
|
+
style={{ padding: '4px 8px', fontSize: '12px', flexShrink: 0 }}
|
|
1063
|
+
onClick={() => deleteModel(mIdx)}
|
|
1064
|
+
title="Remove model"
|
|
1065
|
+
>🗑</button>
|
|
1066
|
+
</div>
|
|
1067
|
+
))}
|
|
1068
|
+
<button
|
|
1069
|
+
className="btn btn-secondary"
|
|
1070
|
+
style={{ fontSize: '12px', marginTop: '4px' }}
|
|
1071
|
+
onClick={addModel}
|
|
1072
|
+
>
|
|
1073
|
+
+ Add model
|
|
1074
|
+
</button>
|
|
1075
|
+
</div>
|
|
1076
|
+
|
|
1077
|
+
{/* Headers */}
|
|
1078
|
+
<div style={{ marginTop: '12px' }}>
|
|
1079
|
+
<label style={{ fontWeight: 600, fontSize: '13px', display: 'block', marginBottom: '8px' }}>Headers (optional)</label>
|
|
1080
|
+
{cp.headers.map((h, hIdx) => (
|
|
1081
|
+
<div key={hIdx} style={{ display: 'flex', gap: '8px', marginBottom: '6px', alignItems: 'center' }}>
|
|
1082
|
+
<input
|
|
1083
|
+
type="text"
|
|
1084
|
+
value={h.name}
|
|
1085
|
+
onChange={(e) => updateHeader(hIdx, { name: e.target.value })}
|
|
1086
|
+
placeholder="Header-Name"
|
|
1087
|
+
style={{ flex: 1 }}
|
|
1088
|
+
/>
|
|
1089
|
+
<input
|
|
1090
|
+
type="text"
|
|
1091
|
+
value={h.value}
|
|
1092
|
+
onChange={(e) => updateHeader(hIdx, { value: e.target.value })}
|
|
1093
|
+
placeholder="value"
|
|
1094
|
+
style={{ flex: 1 }}
|
|
1095
|
+
/>
|
|
1096
|
+
<button
|
|
1097
|
+
className="btn btn-danger"
|
|
1098
|
+
style={{ padding: '4px 8px', fontSize: '12px', flexShrink: 0 }}
|
|
1099
|
+
onClick={() => deleteHeader(hIdx)}
|
|
1100
|
+
title="Remove header"
|
|
1101
|
+
>🗑</button>
|
|
1102
|
+
</div>
|
|
1103
|
+
))}
|
|
1104
|
+
<button
|
|
1105
|
+
className="btn btn-secondary"
|
|
1106
|
+
style={{ fontSize: '12px', marginTop: '4px' }}
|
|
1107
|
+
onClick={addHeader}
|
|
1108
|
+
>
|
|
1109
|
+
+ Add header
|
|
1110
|
+
</button>
|
|
1111
|
+
</div>
|
|
1112
|
+
</div>
|
|
1113
|
+
</div>
|
|
1114
|
+
)
|
|
1115
|
+
})}
|
|
1116
|
+
</div>
|
|
1117
|
+
|
|
1118
|
+
<div className="setting-actions" style={{ marginTop: '16px' }}>
|
|
1119
|
+
<button
|
|
1120
|
+
className="btn btn-secondary"
|
|
1121
|
+
onClick={() => {
|
|
1122
|
+
const newId = `provider-${(config.customProviders ?? []).length + 1}`
|
|
1123
|
+
updateField('customProviders', [
|
|
1124
|
+
...(config.customProviders ?? []),
|
|
1125
|
+
{ id: newId, displayName: '', baseUrl: '', apiKey: '', models: [], headers: [] }
|
|
1126
|
+
])
|
|
1127
|
+
}}
|
|
1128
|
+
>
|
|
1129
|
+
+ Add custom provider
|
|
1130
|
+
</button>
|
|
1131
|
+
</div>
|
|
1132
|
+
</section>
|
|
1133
|
+
|
|
880
1134
|
|
|
881
1135
|
{isDesktopApp && (
|
|
882
1136
|
<section className="setting-section">
|
|
@@ -8,6 +8,27 @@ import ThemeTab from './Settings/ThemeTab'
|
|
|
8
8
|
import PluginsTab from './Settings/PluginsTab'
|
|
9
9
|
import AgentsTab from './Settings/AgentsTab'
|
|
10
10
|
|
|
11
|
+
// ─── Custom Provider Types ────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export interface CustomProviderModel {
|
|
14
|
+
modelId: string
|
|
15
|
+
displayName: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CustomProviderHeader {
|
|
19
|
+
name: string
|
|
20
|
+
value: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CustomProviderConfig {
|
|
24
|
+
id: string
|
|
25
|
+
displayName: string
|
|
26
|
+
baseUrl: string
|
|
27
|
+
apiKey: string
|
|
28
|
+
models: CustomProviderModel[]
|
|
29
|
+
headers: CustomProviderHeader[]
|
|
30
|
+
}
|
|
31
|
+
|
|
11
32
|
export const DEFAULT_CONFIG = {
|
|
12
33
|
theme: 'dark',
|
|
13
34
|
accentColor: '#10b981',
|
|
@@ -122,7 +143,11 @@ export const DEFAULT_CONFIG = {
|
|
|
122
143
|
systemInstruction: 'You are the Reviewer agent. Your task is to verify the code modifications made by the Coder. Run the automated tests, check lint errors, and ensure the implementation is correct and complete.',
|
|
123
144
|
enabled: true
|
|
124
145
|
}
|
|
125
|
-
]
|
|
146
|
+
],
|
|
147
|
+
// Custom Providers
|
|
148
|
+
customProviders: [] as CustomProviderConfig[],
|
|
149
|
+
/// Per-provider model selection: { [providerId]: selectedModelId }
|
|
150
|
+
customModelSelections: {} as Record<string, string>,
|
|
126
151
|
}
|
|
127
152
|
|
|
128
153
|
type TabType = 'sect-general' | 'sect-audio' | 'sect-automation' | 'sect-theme' | 'sect-plugins' | 'sect-shortcuts' | 'sect-memory' | 'sect-agents'
|
|
@@ -401,6 +426,47 @@ export default function SettingsWindow() {
|
|
|
401
426
|
}
|
|
402
427
|
}
|
|
403
428
|
|
|
429
|
+
const handleSaveWithoutClosing = async () => {
|
|
430
|
+
const finalConfig = { ...config }
|
|
431
|
+
|
|
432
|
+
if (config.geminiModel === 'custom') {
|
|
433
|
+
finalConfig.geminiModel = customGemini || 'gemini-2.5-flash'
|
|
434
|
+
}
|
|
435
|
+
if (config.openaiModel === 'custom') {
|
|
436
|
+
finalConfig.openaiModel = customOpenAI || 'gpt-4o'
|
|
437
|
+
}
|
|
438
|
+
if (config.openrouterModel === 'custom') {
|
|
439
|
+
finalConfig.openrouterModel = customOpenRouter || 'openai/gpt-4o-mini'
|
|
440
|
+
}
|
|
441
|
+
if (config.deepseekModel === 'custom') {
|
|
442
|
+
finalConfig.deepseekModel = customDeepSeek || 'deepseek-v4-flash'
|
|
443
|
+
}
|
|
444
|
+
if (config.anthropicModel === 'custom') {
|
|
445
|
+
finalConfig.anthropicModel = customAnthropic || 'claude-3-5-sonnet-latest'
|
|
446
|
+
}
|
|
447
|
+
if (config.hfModel === 'custom') {
|
|
448
|
+
finalConfig.hfModel = customHF || 'meta-llama/Meta-Llama-3-8B-Instruct'
|
|
449
|
+
}
|
|
450
|
+
if (config.localModelName === 'custom') {
|
|
451
|
+
finalConfig.localModelName = customLocal || 'local-model'
|
|
452
|
+
}
|
|
453
|
+
if (config.ollamaModel === 'custom') {
|
|
454
|
+
finalConfig.ollamaModel = customOllama || 'llama3:latest'
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
await setProfileValue('name', userName)
|
|
459
|
+
await setProfileValue('preferences', userPreferences)
|
|
460
|
+
} catch (e) {
|
|
461
|
+
console.error("Failed to save user profile memory:", e)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (window.settingsApi) {
|
|
465
|
+
await window.settingsApi.saveSettings(finalConfig)
|
|
466
|
+
applyThemeStyles(finalConfig)
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
404
470
|
const handleReset = async () => {
|
|
405
471
|
if (confirm('Reset all settings to default?')) {
|
|
406
472
|
setConfig(DEFAULT_CONFIG)
|
|
@@ -656,6 +722,7 @@ export default function SettingsWindow() {
|
|
|
656
722
|
updateMessage={updateMessage}
|
|
657
723
|
handleCheckUpdates={handleCheckUpdates}
|
|
658
724
|
handleInstallUpdate={handleInstallUpdate}
|
|
725
|
+
onSaveWithoutClosing={handleSaveWithoutClosing}
|
|
659
726
|
/>
|
|
660
727
|
)}
|
|
661
728
|
|
|
@@ -1,60 +1,12 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react'
|
|
2
2
|
import { getWorkspaceTree, type WorkspaceTreeEntry, createWorkspaceFile, createWorkspaceFolder, deleteWorkspaceItem } from '../tauri'
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import folderDistOpenIcon from 'material-icon-theme/icons/folder-dist-open.svg?url'
|
|
11
|
-
import folderDocsIcon from 'material-icon-theme/icons/folder-docs.svg?url'
|
|
12
|
-
import folderDocsOpenIcon from 'material-icon-theme/icons/folder-docs-open.svg?url'
|
|
13
|
-
import folderGitIcon from 'material-icon-theme/icons/folder-git.svg?url'
|
|
14
|
-
import folderGitOpenIcon from 'material-icon-theme/icons/folder-git-open.svg?url'
|
|
15
|
-
import folderGithubIcon from 'material-icon-theme/icons/folder-github.svg?url'
|
|
16
|
-
import folderGithubOpenIcon from 'material-icon-theme/icons/folder-github-open.svg?url'
|
|
17
|
-
import folderImagesIcon from 'material-icon-theme/icons/folder-images.svg?url'
|
|
18
|
-
import folderImagesOpenIcon from 'material-icon-theme/icons/folder-images-open.svg?url'
|
|
19
|
-
import folderLogIcon from 'material-icon-theme/icons/folder-log.svg?url'
|
|
20
|
-
import folderLogOpenIcon from 'material-icon-theme/icons/folder-log-open.svg?url'
|
|
21
|
-
import folderNodeIcon from 'material-icon-theme/icons/folder-node.svg?url'
|
|
22
|
-
import folderNodeOpenIcon from 'material-icon-theme/icons/folder-node-open.svg?url'
|
|
23
|
-
import folderPublicIcon from 'material-icon-theme/icons/folder-public.svg?url'
|
|
24
|
-
import folderPublicOpenIcon from 'material-icon-theme/icons/folder-public-open.svg?url'
|
|
25
|
-
import folderRustIcon from 'material-icon-theme/icons/folder-rust.svg?url'
|
|
26
|
-
import folderRustOpenIcon from 'material-icon-theme/icons/folder-rust-open.svg?url'
|
|
27
|
-
import folderScriptsIcon from 'material-icon-theme/icons/folder-scripts.svg?url'
|
|
28
|
-
import folderScriptsOpenIcon from 'material-icon-theme/icons/folder-scripts-open.svg?url'
|
|
29
|
-
import folderSrcIcon from 'material-icon-theme/icons/folder-src.svg?url'
|
|
30
|
-
import folderSrcOpenIcon from 'material-icon-theme/icons/folder-src-open.svg?url'
|
|
31
|
-
import folderSrcTauriIcon from 'material-icon-theme/icons/folder-src-tauri.svg?url'
|
|
32
|
-
import folderSrcTauriOpenIcon from 'material-icon-theme/icons/folder-src-tauri-open.svg?url'
|
|
33
|
-
import folderTestIcon from 'material-icon-theme/icons/folder-test.svg?url'
|
|
34
|
-
import folderTestOpenIcon from 'material-icon-theme/icons/folder-test-open.svg?url'
|
|
35
|
-
import folderUiIcon from 'material-icon-theme/icons/folder-ui.svg?url'
|
|
36
|
-
import folderUiOpenIcon from 'material-icon-theme/icons/folder-ui-open.svg?url'
|
|
37
|
-
import folderUtilsIcon from 'material-icon-theme/icons/folder-utils.svg?url'
|
|
38
|
-
import folderUtilsOpenIcon from 'material-icon-theme/icons/folder-utils-open.svg?url'
|
|
39
|
-
import cssIcon from 'material-icon-theme/icons/css.svg?url'
|
|
40
|
-
import documentIcon from 'material-icon-theme/icons/document.svg?url'
|
|
41
|
-
import htmlIcon from 'material-icon-theme/icons/html.svg?url'
|
|
42
|
-
import imageIcon from 'material-icon-theme/icons/image.svg?url'
|
|
43
|
-
import javascriptIcon from 'material-icon-theme/icons/javascript.svg?url'
|
|
44
|
-
import jsonIcon from 'material-icon-theme/icons/json.svg?url'
|
|
45
|
-
import lockIcon from 'material-icon-theme/icons/lock.svg?url'
|
|
46
|
-
import logIcon from 'material-icon-theme/icons/log.svg?url'
|
|
47
|
-
import markdownIcon from 'material-icon-theme/icons/markdown.svg?url'
|
|
48
|
-
import npmIcon from 'material-icon-theme/icons/npm.svg?url'
|
|
49
|
-
import reactIcon from 'material-icon-theme/icons/react.svg?url'
|
|
50
|
-
import reactTsIcon from 'material-icon-theme/icons/react_ts.svg?url'
|
|
51
|
-
import rustIcon from 'material-icon-theme/icons/rust.svg?url'
|
|
52
|
-
import settingsIcon from 'material-icon-theme/icons/settings.svg?url'
|
|
53
|
-
import tauriIcon from 'material-icon-theme/icons/tauri.svg?url'
|
|
54
|
-
import tomlIcon from 'material-icon-theme/icons/toml.svg?url'
|
|
55
|
-
import typescriptIcon from 'material-icon-theme/icons/typescript.svg?url'
|
|
56
|
-
import viteIcon from 'material-icon-theme/icons/vite.svg?url'
|
|
57
|
-
import yamlIcon from 'material-icon-theme/icons/yaml.svg?url'
|
|
3
|
+
import {
|
|
4
|
+
materialFolderIcon,
|
|
5
|
+
materialFileIcon,
|
|
6
|
+
getExtension as extension,
|
|
7
|
+
folderOpenIcon
|
|
8
|
+
} from '../../shared/utils/fileIcons'
|
|
9
|
+
|
|
58
10
|
|
|
59
11
|
interface WorkspacePanelProps {
|
|
60
12
|
agentMode: boolean
|
|
@@ -76,76 +28,6 @@ const FILE_LABEL: Record<string, string> = {
|
|
|
76
28
|
tsx: 'TS',
|
|
77
29
|
}
|
|
78
30
|
|
|
79
|
-
const FOLDER_ICONS: Record<string, { closed: string; open: string }> = {
|
|
80
|
-
'.cargo_home': { closed: folderRustIcon, open: folderRustOpenIcon },
|
|
81
|
-
'.github': { closed: folderGithubIcon, open: folderGithubOpenIcon },
|
|
82
|
-
'.git': { closed: folderGitIcon, open: folderGitOpenIcon },
|
|
83
|
-
'.rustup': { closed: folderRustIcon, open: folderRustOpenIcon },
|
|
84
|
-
'.rustup_copy': { closed: folderRustIcon, open: folderRustOpenIcon },
|
|
85
|
-
'.rustup_home': { closed: folderRustIcon, open: folderRustOpenIcon },
|
|
86
|
-
assets: { closed: folderImagesIcon, open: folderImagesOpenIcon },
|
|
87
|
-
components: { closed: folderComponentsIcon, open: folderComponentsOpenIcon },
|
|
88
|
-
crates: { closed: folderRustIcon, open: folderRustOpenIcon },
|
|
89
|
-
css: { closed: folderUiIcon, open: folderUiOpenIcon },
|
|
90
|
-
dist: { closed: folderDistIcon, open: folderDistOpenIcon },
|
|
91
|
-
docs: { closed: folderDocsIcon, open: folderDocsOpenIcon },
|
|
92
|
-
logs: { closed: folderLogIcon, open: folderLogOpenIcon },
|
|
93
|
-
node_modules: { closed: folderNodeIcon, open: folderNodeOpenIcon },
|
|
94
|
-
out: { closed: folderDistIcon, open: folderDistOpenIcon },
|
|
95
|
-
public: { closed: folderPublicIcon, open: folderPublicOpenIcon },
|
|
96
|
-
renderer: { closed: folderUiIcon, open: folderUiOpenIcon },
|
|
97
|
-
scripts: { closed: folderScriptsIcon, open: folderScriptsOpenIcon },
|
|
98
|
-
src: { closed: folderSrcIcon, open: folderSrcOpenIcon },
|
|
99
|
-
'src-tauri': { closed: folderSrcTauriIcon, open: folderSrcTauriOpenIcon },
|
|
100
|
-
tests: { closed: folderTestIcon, open: folderTestOpenIcon },
|
|
101
|
-
utils: { closed: folderUtilsIcon, open: folderUtilsOpenIcon },
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const FILE_ICONS_BY_EXTENSION: Record<string, string> = {
|
|
105
|
-
css: cssIcon,
|
|
106
|
-
html: htmlIcon,
|
|
107
|
-
jpeg: imageIcon,
|
|
108
|
-
jpg: imageIcon,
|
|
109
|
-
js: javascriptIcon,
|
|
110
|
-
json: jsonIcon,
|
|
111
|
-
lock: lockIcon,
|
|
112
|
-
log: logIcon,
|
|
113
|
-
md: markdownIcon,
|
|
114
|
-
png: imageIcon,
|
|
115
|
-
rs: rustIcon,
|
|
116
|
-
svg: imageIcon,
|
|
117
|
-
toml: tomlIcon,
|
|
118
|
-
ts: typescriptIcon,
|
|
119
|
-
tsx: reactTsIcon,
|
|
120
|
-
yaml: yamlIcon,
|
|
121
|
-
yml: yamlIcon,
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const FILE_ICONS_BY_NAME: Record<string, string> = {
|
|
125
|
-
'package-lock.json': npmIcon,
|
|
126
|
-
'package.json': npmIcon,
|
|
127
|
-
'tauri.conf.json': tauriIcon,
|
|
128
|
-
'vite.config.ts': viteIcon,
|
|
129
|
-
'vite.config.web.ts': viteIcon,
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function extension(name: string) {
|
|
133
|
-
const index = name.lastIndexOf('.')
|
|
134
|
-
return index === -1 ? '' : name.slice(index + 1).toLowerCase()
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function materialFolderIcon(name: string, open: boolean) {
|
|
138
|
-
const icon = FOLDER_ICONS[name] || FOLDER_ICONS[name.toLowerCase()]
|
|
139
|
-
if (!icon) return open ? folderOpenIcon : folderIcon
|
|
140
|
-
return open ? icon.open : icon.closed
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function materialFileIcon(name: string, fileExtension: string) {
|
|
144
|
-
if (name.endsWith('.config.ts') || name.endsWith('.config.js')) return settingsIcon
|
|
145
|
-
if (name.endsWith('.tsx')) return reactTsIcon
|
|
146
|
-
if (name.endsWith('.jsx')) return reactIcon
|
|
147
|
-
return FILE_ICONS_BY_NAME[name.toLowerCase()] || FILE_ICONS_BY_EXTENSION[fileExtension] || documentIcon
|
|
148
|
-
}
|
|
149
31
|
|
|
150
32
|
function TreeNode({ entry, level, onRefresh, workspacePath }: { entry: WorkspaceTreeEntry; level: number; onRefresh: () => void; workspacePath: string; key?: string }) {
|
|
151
33
|
const [open, setOpen] = useState(level < 1)
|
|
@@ -1670,12 +1670,28 @@ h1 {
|
|
|
1670
1670
|
width: 15px;
|
|
1671
1671
|
}
|
|
1672
1672
|
|
|
1673
|
+
.agent-activity-icon[data-has-img="true"]::before,
|
|
1674
|
+
.agent-activity-icon[data-has-img="true"]::after {
|
|
1675
|
+
display: none !important;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
.agent-activity-icon img {
|
|
1679
|
+
width: 14px;
|
|
1680
|
+
height: 14px;
|
|
1681
|
+
display: block;
|
|
1682
|
+
object-fit: contain;
|
|
1683
|
+
filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.12));
|
|
1684
|
+
pointer-events: none;
|
|
1685
|
+
user-select: none;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1673
1688
|
.agent-activity-icon::before,
|
|
1674
1689
|
.agent-activity-icon::after {
|
|
1675
1690
|
content: "";
|
|
1676
1691
|
position: absolute;
|
|
1677
1692
|
}
|
|
1678
1693
|
|
|
1694
|
+
|
|
1679
1695
|
.agent-activity-item[data-kind="folder"] .agent-activity-icon::before {
|
|
1680
1696
|
background: var(--text-muted);
|
|
1681
1697
|
border-radius: 2px 2px 1px 1px;
|
|
@@ -172,8 +172,13 @@ export default function ChatPanel({
|
|
|
172
172
|
case 'ollama':
|
|
173
173
|
return dynamicOllamaModels
|
|
174
174
|
default:
|
|
175
|
+
if (provider.startsWith('custom:')) {
|
|
176
|
+
const id = provider.replace(/^custom:/, '')
|
|
177
|
+
const cp = (settingsConfig?.customProviders ?? []).find(p => p.id === id)
|
|
178
|
+
return cp?.models.map(m => m.modelId) ?? []
|
|
179
|
+
}
|
|
175
180
|
return []
|
|
176
|
-
|
|
181
|
+
}
|
|
177
182
|
}
|
|
178
183
|
|
|
179
184
|
const activeProvider = status?.activeProvider ?? ''
|
|
@@ -199,6 +204,11 @@ export default function ChatPanel({
|
|
|
199
204
|
case 'ollama':
|
|
200
205
|
return settingsConfig.ollamaModel
|
|
201
206
|
default:
|
|
207
|
+
if (provider.startsWith('custom:')) {
|
|
208
|
+
const id = provider.replace(/^custom:/, '')
|
|
209
|
+
const cp = (settingsConfig?.customProviders ?? []).find(p => p.id === id)
|
|
210
|
+
return (settingsConfig.customModelSelections ?? {})[id] ?? cp?.models[0]?.modelId ?? ''
|
|
211
|
+
}
|
|
202
212
|
return ''
|
|
203
213
|
}
|
|
204
214
|
}
|
|
@@ -370,14 +380,19 @@ export default function ChatPanel({
|
|
|
370
380
|
}
|
|
371
381
|
}
|
|
372
382
|
if (sending) return
|
|
373
|
-
if (!
|
|
374
|
-
|
|
383
|
+
if (!latest?.aiText || latest.id === lastAutoSpokenIdRef.current) return
|
|
384
|
+
|
|
385
|
+
if (!settingsConfig?.enableVoiceReply) {
|
|
386
|
+
lastAutoSpokenIdRef.current = latest.id
|
|
387
|
+
if (voiceMode) {
|
|
388
|
+
scheduleVoiceListen(350)
|
|
389
|
+
}
|
|
375
390
|
return
|
|
376
391
|
}
|
|
377
|
-
|
|
392
|
+
|
|
378
393
|
lastAutoSpokenIdRef.current = latest.id
|
|
379
394
|
speak(latest.aiText)
|
|
380
|
-
}, [interactions, sending, settingsConfig?.enableVoiceReply])
|
|
395
|
+
}, [interactions, sending, settingsConfig?.enableVoiceReply, voiceMode])
|
|
381
396
|
|
|
382
397
|
// Drag and Drop Zone Overlay
|
|
383
398
|
const [isDragging, setIsDragging] = useState(false)
|
|
@@ -1137,6 +1152,11 @@ export default function ChatPanel({
|
|
|
1137
1152
|
else if (provider === 'huggingface') displayName = 'HF'
|
|
1138
1153
|
else if (provider === 'local_openai') displayName = 'Local'
|
|
1139
1154
|
else if (provider === 'ollama') displayName = 'Ollama'
|
|
1155
|
+
else if (provider.startsWith('custom:')) {
|
|
1156
|
+
const id = provider.replace(/^custom:/, '')
|
|
1157
|
+
const cp = (settingsConfig?.customProviders ?? []).find(p => p.id === id)
|
|
1158
|
+
displayName = cp?.displayName || id
|
|
1159
|
+
}
|
|
1140
1160
|
return <option key={provider} value={provider}>{displayName}</option>
|
|
1141
1161
|
})}
|
|
1142
1162
|
</select>
|
|
@@ -606,6 +606,11 @@ export default function MintDashboard() {
|
|
|
606
606
|
else if (provider === 'huggingface') providerName = 'HF'
|
|
607
607
|
else if (provider === 'local_openai') providerName = 'Local'
|
|
608
608
|
else if (provider === 'ollama') providerName = 'Ollama'
|
|
609
|
+
else if (provider.startsWith('custom:')) {
|
|
610
|
+
const id = provider.replace(/^custom:/, '')
|
|
611
|
+
const cp = (settingsConfig?.customProviders ?? []).find((p: any) => p.id === id)
|
|
612
|
+
providerName = cp?.displayName || id
|
|
613
|
+
}
|
|
609
614
|
|
|
610
615
|
if (providerName && providerName === provider) {
|
|
611
616
|
providerName = providerName.charAt(0).toUpperCase() + providerName.slice(1)
|
|
@@ -623,7 +628,14 @@ export default function MintDashboard() {
|
|
|
623
628
|
case 'huggingface': return config.hfModel || 'meta-llama/Meta-Llama-3-8B-Instruct'
|
|
624
629
|
case 'local_openai': return config.localModelName || 'llama3'
|
|
625
630
|
case 'ollama': return config.ollamaModel || 'llama3:latest'
|
|
626
|
-
default:
|
|
631
|
+
default: {
|
|
632
|
+
if (provider.startsWith('custom:')) {
|
|
633
|
+
const id = provider.replace(/^custom:/, '')
|
|
634
|
+
const cp = (config.customProviders ?? []).find((p: any) => p.id === id)
|
|
635
|
+
return (config.customModelSelections ?? {})[id] ?? cp?.models[0]?.modelId ?? ''
|
|
636
|
+
}
|
|
637
|
+
return ''
|
|
638
|
+
}
|
|
627
639
|
}
|
|
628
640
|
}
|
|
629
641
|
|
|
@@ -665,6 +677,12 @@ export default function MintDashboard() {
|
|
|
665
677
|
config.localModelName = modelName
|
|
666
678
|
} else if (provider === 'ollama') {
|
|
667
679
|
config.ollamaModel = modelName
|
|
680
|
+
} else if (provider.startsWith('custom:')) {
|
|
681
|
+
const id = provider.replace(/^custom:/, '')
|
|
682
|
+
config.customModelSelections = {
|
|
683
|
+
...(config.customModelSelections ?? {}),
|
|
684
|
+
[id]: modelName
|
|
685
|
+
}
|
|
668
686
|
}
|
|
669
687
|
await window.settingsApi.saveSettings(config)
|
|
670
688
|
setSettingsConfig(config)
|