@pheem49/mint 1.8.1 → 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.
Files changed (200) hide show
  1. package/Cargo.lock +3 -3
  2. package/Cargo.toml +1 -1
  3. package/README.md +0 -17
  4. package/Release_Note.md +137 -58
  5. package/bin/mint +0 -0
  6. package/crates/mint-cli/src/agent.rs +150 -45
  7. package/crates/mint-cli/src/image.rs +8 -8
  8. package/crates/mint-cli/src/main.rs +929 -412
  9. package/crates/mint-cli/src/onboard.rs +108 -108
  10. package/crates/mint-cli/src/setup.rs +143 -143
  11. package/crates/mint-core/src/agent_loop.rs +1 -0
  12. package/crates/mint-core/src/api_server.rs +108 -52
  13. package/crates/mint-core/src/browser.rs +56 -1
  14. package/crates/mint-core/src/channels.rs +1 -0
  15. package/crates/mint-core/src/chat.rs +126 -8
  16. package/crates/mint-core/src/code_tools.rs +18 -20
  17. package/crates/mint-core/src/config.rs +245 -17
  18. package/crates/mint-core/src/image_gen.rs +5 -5
  19. package/crates/mint-core/src/lib.rs +16 -2
  20. package/crates/mint-core/src/memory.rs +6 -1
  21. package/crates/mint-core/src/orchestration.rs +260 -72
  22. package/crates/mint-core/src/pictures.rs +3 -4
  23. package/crates/mint-core/src/semantic.rs +1 -1
  24. package/crates/mint-core/src/shell.rs +1 -1
  25. package/crates/mint-core/src/skills.rs +46 -19
  26. package/crates/mint-core/src/tts.rs +3 -1
  27. package/flatpak/com.pheem49.mint.desktop +9 -0
  28. package/flatpak/com.pheem49.mint.metainfo.xml +35 -0
  29. package/flatpak/com.pheem49.mint.yaml +52 -0
  30. package/package.json +1 -1
  31. package/shared-modules/.github/workflows/ci.yml +120 -0
  32. package/shared-modules/.github/workflows/update.yaml +28 -0
  33. package/shared-modules/CODEOWNERS +23 -0
  34. package/shared-modules/README.md +79 -0
  35. package/shared-modules/SDL/README.md +3 -0
  36. package/shared-modules/SDL/SDL-1.2.15.json +51 -0
  37. package/shared-modules/SDL/SDL_Pango-0.1.2-API-adds.patch +118 -0
  38. package/shared-modules/SDL/SDL_image-1.2.12.json +25 -0
  39. package/shared-modules/SDL/SDL_mixer-1.2.12.json +27 -0
  40. package/shared-modules/SDL/SDL_net-1.2.8.json +19 -0
  41. package/shared-modules/SDL/SDL_pango-0.1.2.json +26 -0
  42. package/shared-modules/SDL/SDL_ttf-2.0.11.json +28 -0
  43. package/shared-modules/SDL/libsdl-1.2.15-strict-prototypes.patch +316 -0
  44. package/shared-modules/SDL/sdl-check-for-SDL_VIDEO_X11_BACKINGSTORE.patch +24 -0
  45. package/shared-modules/SDL/sdl-libx11-build.patch +59 -0
  46. package/shared-modules/SDL/sdl-sysloadso-buffer-length.patch +27 -0
  47. package/shared-modules/SDL/sdl12-compat.json +22 -0
  48. package/shared-modules/SDL2/SDL2_image.json +27 -0
  49. package/shared-modules/SDL2/SDL2_ttf.json +29 -0
  50. package/shared-modules/cld2/CMakeLists.txt +157 -0
  51. package/shared-modules/cld2/cld2.json +24 -0
  52. package/shared-modules/clutter/clutter.json +72 -0
  53. package/shared-modules/dbus-glib/dbus-glib.json +29 -0
  54. package/shared-modules/glew/glew.json +36 -0
  55. package/shared-modules/glu/glu-9.json +12 -0
  56. package/shared-modules/gtk2/arc-gtk2-theme-Replace-Inkscape-dependency-with-rsvg-convert.patch +180 -0
  57. package/shared-modules/gtk2/gtk2-common-themes.json +360 -0
  58. package/shared-modules/gtk2/gtk2-fix-crash-in-show-uri.patch +30 -0
  59. package/shared-modules/gtk2/gtk2-gcc14.patch +281 -0
  60. package/shared-modules/gtk2/gtk2-gcc15.patch +250 -0
  61. package/shared-modules/gtk2/gtk2-murrine-c99.patch +34 -0
  62. package/shared-modules/gtk2/gtk2-use-adwaita-theme.patch +80 -0
  63. package/shared-modules/gtk2/gtk2.json +120 -0
  64. package/shared-modules/gtk2/ibus-gcc15.patch +13 -0
  65. package/shared-modules/gtk2/murrine-engine-fix-crash.patch +12 -0
  66. package/shared-modules/gtk2/readme.md +10 -0
  67. package/shared-modules/intltool/intltool-0.51.json +20 -0
  68. package/shared-modules/intltool/intltool-perl5.26-regex-fixes.patch +59 -0
  69. package/shared-modules/lame/0001-Build-xmm_quantize_sub.c-with-msse.patch +34 -0
  70. package/shared-modules/lame/0002-Include-GTK-1-autoconf-directives-in-build-system.patch +222 -0
  71. package/shared-modules/lame/0003-Patch-out-remaining-ansi2knr.patch +70 -0
  72. package/shared-modules/lame/0004-initscr-is-not-used-anywhere-in-lame-sourcetree.patch +41 -0
  73. package/shared-modules/lame/0005-Update-autoconf.patch +81 -0
  74. package/shared-modules/lame/lame-3.99.5.json +32 -0
  75. package/shared-modules/libappindicator/libappindicator-gtk2-12.10.json +140 -0
  76. package/shared-modules/libappindicator/libappindicator-gtk3-12.10.json +140 -0
  77. package/shared-modules/libappindicator/libappindicator-gtk3-introspection-12.10.json +140 -0
  78. package/shared-modules/libappindicator/patches/0001-libappindicator-ftbfs.patch +20 -0
  79. package/shared-modules/libappindicator/patches/0002-libappindicator-no-python.patch +888 -0
  80. package/shared-modules/libappindicator/patches/0003-libappindicator-fix-crash-from-incorrect-signal-emission.patch +74 -0
  81. package/shared-modules/libappindicator/patches/0004-libappindicator-fix-crash-iterating-icon-themes.patch +11 -0
  82. package/shared-modules/libayatana-appindicator/0001-Fix-HAVE_VALGRIND-AM_CONDITIONAL.patch +51 -0
  83. package/shared-modules/libayatana-appindicator/0001-Make-introspection-configurable.patch +44 -0
  84. package/shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json +121 -0
  85. package/shared-modules/libcanberra/0001-gtk-Don-t-assume-all-GdkDisplays-are-GdkX11Displays-.patch +123 -0
  86. package/shared-modules/libcanberra/libcanberra.json +44 -0
  87. package/shared-modules/libmad/libmad.json +19 -0
  88. package/shared-modules/libsecret/libsecret.json +23 -0
  89. package/shared-modules/libsoup/libsoup-2.4.json +14 -0
  90. package/shared-modules/libusb/libusb.json +27 -0
  91. package/shared-modules/linux-audio/dssi.json +31 -0
  92. package/shared-modules/linux-audio/fftw3f-static.json +44 -0
  93. package/shared-modules/linux-audio/fftw3f.json +42 -0
  94. package/shared-modules/linux-audio/fluidsynth2-static.json +36 -0
  95. package/shared-modules/linux-audio/fluidsynth2.json +25 -0
  96. package/shared-modules/linux-audio/ladspa.json +26 -0
  97. package/shared-modules/linux-audio/lash.json +35 -0
  98. package/shared-modules/linux-audio/libinstpatch.json +16 -0
  99. package/shared-modules/linux-audio/liblo-static.json +22 -0
  100. package/shared-modules/linux-audio/liblo.json +17 -0
  101. package/shared-modules/linux-audio/lilv-static.json +94 -0
  102. package/shared-modules/linux-audio/lilv.json +89 -0
  103. package/shared-modules/linux-audio/lrdf.json +57 -0
  104. package/shared-modules/linux-audio/lv2.json +20 -0
  105. package/shared-modules/linux-audio/patches/lash/0001-configure-remove-aclocal-m4.patch +29 -0
  106. package/shared-modules/linux-audio/patches/lash/0002-Fix-build-with-recent-gcc.patch +73 -0
  107. package/shared-modules/linux-audio/patches/lash/0003-Fix-linking.patch +136 -0
  108. package/shared-modules/linux-audio/patches/raptor-libxml.patch +31 -0
  109. package/shared-modules/linux-audio/patches/stk-install.sh +54 -0
  110. package/shared-modules/linux-audio/stk.json +28 -0
  111. package/shared-modules/lua5.1/0004-Fix-stack-overflow-in-vararg-functions.patch +21 -0
  112. package/shared-modules/lua5.1/lua-5.1.5-so.patch +43 -0
  113. package/shared-modules/lua5.1/lua-5.1.5.json +42 -0
  114. package/shared-modules/lua5.3/lua-5.3.5.json +41 -0
  115. package/shared-modules/lua5.3/lua.pc.in +8 -0
  116. package/shared-modules/lua5.3/luaconf.patch +47 -0
  117. package/shared-modules/lua5.3/makefile-add-liblua.so-rule.patch +47 -0
  118. package/shared-modules/lua5.4/lua-5.4.json +41 -0
  119. package/shared-modules/lua5.4/lua.pc.in +8 -0
  120. package/shared-modules/lua5.4/luaconf.patch +34 -0
  121. package/shared-modules/lua5.4/makefile-add-liblua.so-rule.patch +42 -0
  122. package/shared-modules/luajit/luajit.json +30 -0
  123. package/shared-modules/lzo/lzo.json +27 -0
  124. package/shared-modules/mac/mac.json +18 -0
  125. package/shared-modules/physfs/physfs.json +26 -0
  126. package/shared-modules/pygame/audiofile-gcc6.patch +21 -0
  127. package/shared-modules/pygame/cython-fixes.patch +34 -0
  128. package/shared-modules/pygame/fluidsynth-no-rawmidi.patch +69 -0
  129. package/shared-modules/pygame/glib.patch +25 -0
  130. package/shared-modules/pygame/pygame1-python2.json +157 -0
  131. package/shared-modules/pygtk/pygtk-c99.patch +15 -0
  132. package/shared-modules/pygtk/pygtk.json +117 -0
  133. package/shared-modules/pygtk/pygtk.patch +39 -0
  134. package/shared-modules/python2.7/python-2.7.json +48 -0
  135. package/shared-modules/smpeg/smpeg-0.4.5.json +50 -0
  136. package/shared-modules/smpeg/smpeg-am-prog-as.patch +12 -0
  137. package/shared-modules/smpeg/smpeg-export-mpegaudio-class.patch +17 -0
  138. package/shared-modules/smpeg/smpeg-gcc6.patch +40 -0
  139. package/shared-modules/smpeg/smpeg-no-gtk.patch +37 -0
  140. package/shared-modules/squashfs-tools/squashfs-tools.json +25 -0
  141. package/shared-modules/update-checker.sh +50 -0
  142. package/shared-modules/vorbisgain/0001-temp_files.patch +75 -0
  143. package/shared-modules/vorbisgain/0002-errno.patch +33 -0
  144. package/shared-modules/vorbisgain/0003-manpage.patch +248 -0
  145. package/shared-modules/vorbisgain/0004-vorbisgain_mtime.patch +183 -0
  146. package/shared-modules/vorbisgain/0005-double_fclose.patch +21 -0
  147. package/shared-modules/vorbisgain/0006-manpage_hyphens.patch +56 -0
  148. package/shared-modules/vorbisgain/0007-recursively_spelling.patch +14 -0
  149. package/shared-modules/vorbisgain/0008-manpage_recursion_mistake.patch +23 -0
  150. package/shared-modules/vorbisgain/0009-hardening.patch +13 -0
  151. package/shared-modules/vorbisgain/0010-fclose.patch +13 -0
  152. package/shared-modules/vorbisgain/README.md +23 -0
  153. package/shared-modules/vorbisgain/vorbisgain_0.37-2.json +29 -0
  154. package/src/renderer/shared/agentProgress.ts +145 -0
  155. package/src/renderer/shared/calculator.ts +52 -0
  156. package/src/renderer/shared/components/AgentActivityDrawer.tsx +72 -0
  157. package/src/renderer/shared/components/AgentActivityTable.tsx +85 -0
  158. package/src/renderer/shared/components/ApprovalCard.tsx +93 -0
  159. package/src/renderer/shared/components/ChatCodeBlock.tsx +75 -0
  160. package/src/renderer/shared/components/ThinkingBlock.tsx +101 -0
  161. package/src/renderer/shared/constants/models.ts +91 -0
  162. package/src/renderer/shared/platform.ts +82 -0
  163. package/src/renderer/shared/types.ts +140 -0
  164. package/src/renderer/shared/utils/agentActivity.ts +187 -0
  165. package/src/renderer/shared/utils/approval.ts +1 -0
  166. package/src/renderer/shared/utils/approval.tsx +64 -0
  167. package/src/renderer/shared/utils/fileIcons.ts +135 -0
  168. package/src/renderer/shared/utils/markdown.tsx +347 -0
  169. package/src/renderer/shared/utils/providers.ts +36 -0
  170. package/src/renderer/shared/utils/speech.ts +216 -0
  171. package/src/renderer/shared/utils/ui.ts +147 -0
  172. package/src/renderer/src/agentProgress.ts +11 -39
  173. package/src/renderer/src/calculator.ts +3 -46
  174. package/src/renderer/src/components/ChatPanel.tsx +219 -1308
  175. package/src/renderer/src/components/MintDashboard.tsx +67 -170
  176. package/src/renderer/src/components/PicturesLibrary.tsx +32 -8
  177. package/src/renderer/src/components/Settings/AgentsTab.tsx +381 -0
  178. package/src/renderer/src/components/Settings/AutomationTab.tsx +0 -22
  179. package/src/renderer/src/components/Settings/GeneralTab.tsx +256 -2
  180. package/src/renderer/src/components/SettingsWindow.tsx +120 -3
  181. package/src/renderer/src/components/WorkspacePanel.tsx +7 -125
  182. package/src/renderer/src/css/styles.css +100 -0
  183. package/src/renderer/src/tauri.ts +128 -143
  184. package/src/renderer/src-web/agentProgress.ts +11 -39
  185. package/src/renderer/src-web/calculator.ts +3 -46
  186. package/src/renderer/src-web/components/ChatPanel.tsx +219 -1306
  187. package/src/renderer/src-web/components/MintDashboard.tsx +42 -170
  188. package/src/renderer/src-web/components/PicturesLibrary.tsx +23 -29
  189. package/src/renderer/src-web/components/Settings/AgentsTab.tsx +381 -0
  190. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +0 -22
  191. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +254 -3
  192. package/src/renderer/src-web/components/SettingsWindow.tsx +127 -3
  193. package/src/renderer/src-web/css/styles.css +100 -0
  194. package/src/renderer/src-web/tauri.ts +180 -102
  195. package/src-tauri/src/desktop.rs +18 -16
  196. package/src-tauri/src/discord_rpc.rs +1 -1
  197. package/src-tauri/src/lib.rs +168 -40
  198. package/src-tauri/src/webhooks.rs +1 -0
  199. package/src-tauri/src/workflows.rs +1 -0
  200. 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">
@@ -6,6 +6,28 @@ import AudioTab from './Settings/AudioTab'
6
6
  import AutomationTab from './Settings/AutomationTab'
7
7
  import ThemeTab from './Settings/ThemeTab'
8
8
  import PluginsTab from './Settings/PluginsTab'
9
+ import AgentsTab from './Settings/AgentsTab'
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
+ }
9
31
 
10
32
  export const DEFAULT_CONFIG = {
11
33
  theme: 'dark',
@@ -30,7 +52,7 @@ export const DEFAULT_CONFIG = {
30
52
  proactiveCooldown: 120,
31
53
  enableVoiceReply: true,
32
54
  enableCustomWorkflows: true,
33
- enableAgentCollaboration: true,
55
+ enableAgentCollaboration: false,
34
56
  ttsProvider: 'google',
35
57
  ttsVolume: 1.0,
36
58
  ttsSpeed: 1.0,
@@ -91,10 +113,44 @@ export const DEFAULT_CONFIG = {
91
113
  imageGenProvider: 'gemini' as 'gemini' | 'dalle' | 'stability' | 'ideogram' | 'replicate',
92
114
  stabilityApiKey: '',
93
115
  ideogramApiKey: '',
94
- replicateApiKey: ''
116
+ replicateApiKey: '',
117
+ // Multi-Agent Configuration
118
+ agents: [
119
+ {
120
+ id: 'planner',
121
+ name: 'Planner',
122
+ provider: 'gemini',
123
+ model: 'gemini-2.5-flash',
124
+ apiKey: '',
125
+ systemInstruction: 'You are the Planner agent. Your task is to analyze the user request, inspect the workspace structure, and design a step-by-step implementation plan. Create or update the implementation_plan.md file to document your proposal.',
126
+ enabled: true
127
+ },
128
+ {
129
+ id: 'coder',
130
+ name: 'Coder',
131
+ provider: 'gemini',
132
+ model: 'gemini-2.5-flash',
133
+ apiKey: '',
134
+ systemInstruction: 'You are the Coder agent. Your task is to implement the changes specified in the approved implementation plan. Read relevant files, write clean and efficient code, and run terminal commands to build or verify. Stay focused on execution.',
135
+ enabled: true
136
+ },
137
+ {
138
+ id: 'reviewer',
139
+ name: 'Reviewer',
140
+ provider: 'gemini',
141
+ model: 'gemini-2.5-flash',
142
+ apiKey: '',
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.',
144
+ enabled: true
145
+ }
146
+ ],
147
+ // Custom Providers
148
+ customProviders: [] as CustomProviderConfig[],
149
+ /// Per-provider model selection: { [providerId]: selectedModelId }
150
+ customModelSelections: {} as Record<string, string>,
95
151
  }
96
152
 
97
- type TabType = 'sect-general' | 'sect-audio' | 'sect-automation' | 'sect-theme' | 'sect-plugins' | 'sect-shortcuts' | 'sect-memory'
153
+ type TabType = 'sect-general' | 'sect-audio' | 'sect-automation' | 'sect-theme' | 'sect-plugins' | 'sect-shortcuts' | 'sect-memory' | 'sect-agents'
98
154
 
99
155
  export const GEMINI_MODELS = [
100
156
  'gemini-2.5-flash',
@@ -370,6 +426,47 @@ export default function SettingsWindow() {
370
426
  }
371
427
  }
372
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
+
373
470
  const handleReset = async () => {
374
471
  if (confirm('Reset all settings to default?')) {
375
472
  setConfig(DEFAULT_CONFIG)
@@ -586,6 +683,17 @@ export default function SettingsWindow() {
586
683
  </span>
587
684
  <strong>Plugins</strong>
588
685
  </button>
686
+ <button className={`tab-btn ${activeTab === 'sect-agents' ? 'active' : ''}`} onClick={() => setActiveTab('sect-agents')}>
687
+ <span style={{ display: 'inline-flex', alignItems: 'center' }}>
688
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
689
+ <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
690
+ <circle cx="9" cy="7" r="4"></circle>
691
+ <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
692
+ <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
693
+ </svg>
694
+ </span>
695
+ <strong>Multi-Agent (Beta)</strong>
696
+ </button>
589
697
  </nav>
590
698
 
591
699
  <div className="settings-content">
@@ -614,6 +722,7 @@ export default function SettingsWindow() {
614
722
  updateMessage={updateMessage}
615
723
  handleCheckUpdates={handleCheckUpdates}
616
724
  handleInstallUpdate={handleInstallUpdate}
725
+ onSaveWithoutClosing={handleSaveWithoutClosing}
617
726
  />
618
727
  )}
619
728
 
@@ -666,6 +775,14 @@ export default function SettingsWindow() {
666
775
  handleConnectPlugin={handleConnectPlugin}
667
776
  />
668
777
  )}
778
+
779
+ {activeTab === 'sect-agents' && (
780
+ <AgentsTab
781
+ config={config}
782
+ updateField={updateField}
783
+ dynamicOllamaModels={dynamicOllamaModels}
784
+ />
785
+ )}
669
786
  </div>
670
787
  </main>
671
788
 
@@ -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 folderIcon from 'material-icon-theme/icons/folder.svg?url'
4
- import folderOpenIcon from 'material-icon-theme/icons/folder-open.svg?url'
5
- import folderComponentsIcon from 'material-icon-theme/icons/folder-components.svg?url'
6
- import folderComponentsOpenIcon from 'material-icon-theme/icons/folder-components-open.svg?url'
7
- import folderConfigIcon from 'material-icon-theme/icons/folder-config.svg?url'
8
- import folderConfigOpenIcon from 'material-icon-theme/icons/folder-config-open.svg?url'
9
- import folderDistIcon from 'material-icon-theme/icons/folder-dist.svg?url'
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)