@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 = false
68
+ isDesktopApp = false,
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,12 +921,219 @@ 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 (config.aiProvider === `custom:${cp.id}`) {
951
+ updateField('aiProvider', 'gemini')
952
+ }
953
+ }
954
+
955
+ const updateModel = (mIdx: number, patch: Partial<CustomProviderModel>) => {
956
+ const updatedModels = cp.models.map((m, i) => i === mIdx ? { ...m, ...patch } : m)
957
+ updateCp({ models: updatedModels })
958
+ }
959
+ const deleteModel = (mIdx: number) => {
960
+ updateCp({ models: cp.models.filter((_, i) => i !== mIdx) })
961
+ }
962
+ const addModel = () => {
963
+ updateCp({ models: [...cp.models, { modelId: '', displayName: '' }] })
964
+ }
965
+
966
+ const updateHeader = (hIdx: number, patch: Partial<CustomProviderHeader>) => {
967
+ const updatedHeaders = cp.headers.map((h, i) => i === hIdx ? { ...h, ...patch } : h)
968
+ updateCp({ headers: updatedHeaders })
969
+ }
970
+ const deleteHeader = (hIdx: number) => {
971
+ updateCp({ headers: cp.headers.filter((_, i) => i !== hIdx) })
972
+ }
973
+ const addHeader = () => {
974
+ updateCp({ headers: [...cp.headers, { name: '', value: '' }] })
975
+ }
976
+
977
+ return (
978
+ <div key={cpIdx} className={`provider-card ${isActive ? 'active-provider' : ''}`}>
979
+ <div className="provider-card-header">
980
+ <div className="provider-card-title">
981
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
982
+ <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" />
983
+ </svg>
984
+ {cp.displayName || cp.id || 'Unnamed Provider'}
985
+ </div>
986
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
987
+ {isActive && <span className="provider-active-badge">Active</span>}
988
+ <button
989
+ className="btn btn-primary"
990
+ style={{ padding: '3px 8px', fontSize: '12px', background: savedProviderId === cp.id ? '#10b981' : '' }}
991
+ onClick={() => handleSaveProvider(cp.id)}
992
+ title="Save this provider settings"
993
+ >
994
+ {savedProviderId === cp.id ? 'Saved! ✓' : 'Save'}
995
+ </button>
996
+ <button
997
+ className="btn btn-danger"
998
+ style={{ padding: '3px 8px', fontSize: '12px' }}
999
+ onClick={deleteCp}
1000
+ title="Delete this provider"
1001
+ >
1002
+ Delete
1003
+ </button>
1004
+ </div>
1005
+ </div>
1006
+
1007
+ <div className="provider-card-body">
1008
+ <div className="setting-row">
1009
+ <label>Name</label>
1010
+ <input
1011
+ type="text"
1012
+ value={cp.displayName}
1013
+ onChange={(e) => {
1014
+ const name = e.target.value
1015
+ const computedId = name
1016
+ .toLowerCase()
1017
+ .replace(/[^a-z0-9_-]/g, '-')
1018
+ .replace(/-+/g, '-')
1019
+ .replace(/^-|-$/g, '') || `provider-${cpIdx + 1}`
1020
+ updateCp({ displayName: name, id: computedId })
1021
+ }}
1022
+ placeholder="e.g. DeepSeek"
1023
+ />
1024
+ </div>
1025
+
1026
+ <div className="setting-row">
1027
+ <label>Base URL</label>
1028
+ <input
1029
+ type="text"
1030
+ value={cp.baseUrl}
1031
+ onChange={(e) => updateCp({ baseUrl: e.target.value })}
1032
+ placeholder="https://api.myprovider.com/v1"
1033
+ />
1034
+ </div>
1035
+
1036
+ <div className="setting-row">
1037
+ <label>API Key</label>
1038
+ <input
1039
+ type="password"
1040
+ value={cp.apiKey}
1041
+ onChange={(e) => updateCp({ apiKey: e.target.value })}
1042
+ placeholder="Optional. Leave empty if you manage auth via headers."
1043
+ />
1044
+ </div>
1045
+
1046
+ {/* Models */}
1047
+ <div style={{ marginTop: '12px' }}>
1048
+ <label style={{ fontWeight: 600, fontSize: '13px', display: 'block', marginBottom: '8px' }}>Model list</label>
1049
+ {cp.models.map((m, mIdx) => (
1050
+ <div key={mIdx} style={{ display: 'flex', gap: '8px', marginBottom: '6px', alignItems: 'center' }}>
1051
+ <input
1052
+ type="text"
1053
+ value={m.modelId}
1054
+ onChange={(e) => updateModel(mIdx, { modelId: e.target.value, displayName: e.target.value })}
1055
+ placeholder="e.g. llama-3.1-8b"
1056
+ style={{ flex: 1 }}
1057
+ />
1058
+ <button
1059
+ className="btn btn-danger"
1060
+ style={{ padding: '4px 8px', fontSize: '12px', flexShrink: 0 }}
1061
+ onClick={() => deleteModel(mIdx)}
1062
+ title="Remove model"
1063
+ >🗑</button>
1064
+ </div>
1065
+ ))}
1066
+ <button
1067
+ className="btn btn-secondary"
1068
+ style={{ fontSize: '12px', marginTop: '4px' }}
1069
+ onClick={addModel}
1070
+ >
1071
+ + Add model
1072
+ </button>
1073
+ </div>
1074
+
1075
+ {/* Headers */}
1076
+ <div style={{ marginTop: '12px' }}>
1077
+ <label style={{ fontWeight: 600, fontSize: '13px', display: 'block', marginBottom: '8px' }}>Headers (optional)</label>
1078
+ {cp.headers.map((h, hIdx) => (
1079
+ <div key={hIdx} style={{ display: 'flex', gap: '8px', marginBottom: '6px', alignItems: 'center' }}>
1080
+ <input
1081
+ type="text"
1082
+ value={h.name}
1083
+ onChange={(e) => updateHeader(hIdx, { name: e.target.value })}
1084
+ placeholder="Header-Name"
1085
+ style={{ flex: 1 }}
1086
+ />
1087
+ <input
1088
+ type="text"
1089
+ value={h.value}
1090
+ onChange={(e) => updateHeader(hIdx, { value: e.target.value })}
1091
+ placeholder="value"
1092
+ style={{ flex: 1 }}
1093
+ />
1094
+ <button
1095
+ className="btn btn-danger"
1096
+ style={{ padding: '4px 8px', fontSize: '12px', flexShrink: 0 }}
1097
+ onClick={() => deleteHeader(hIdx)}
1098
+ title="Remove header"
1099
+ >🗑</button>
1100
+ </div>
1101
+ ))}
1102
+ <button
1103
+ className="btn btn-secondary"
1104
+ style={{ fontSize: '12px', marginTop: '4px' }}
1105
+ onClick={addHeader}
1106
+ >
1107
+ + Add header
1108
+ </button>
1109
+ </div>
1110
+ </div>
1111
+ </div>
1112
+ )
1113
+ })}
1114
+ </div>
1115
+
1116
+ <div className="setting-actions" style={{ marginTop: '16px' }}>
1117
+ <button
1118
+ className="btn btn-secondary"
1119
+ onClick={() => {
1120
+ const newId = `provider-${(config.customProviders ?? []).length + 1}`
1121
+ updateField('customProviders', [
1122
+ ...(config.customProviders ?? []),
1123
+ { id: newId, displayName: '', baseUrl: '', apiKey: '', models: [], headers: [] }
1124
+ ])
1125
+ }}
1126
+ >
1127
+ + Add custom provider
1128
+ </button>
1129
+ </div>
1130
+ </section>
1131
+
880
1132
  {isDesktopApp && (
881
1133
  <section className="setting-section">
882
1134
  <div className="section-heading">
883
1135
  <div>
884
1136
  <p className="section-kicker">Desktop updates</p>
885
-
886
1137
  <h2 className="section-title">Signed Tauri Channel</h2>
887
1138
  </div>
888
1139
  <p className="section-description">Check and explicitly install signed Tauri releases from your configured update channel.</p>
@@ -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',
@@ -379,6 +435,54 @@ export default function SettingsWindow() {
379
435
  }
380
436
  }
381
437
 
438
+ const handleSaveWithoutClosing = async () => {
439
+ const finalConfig = { ...config }
440
+
441
+ if (config.geminiModel === 'custom') {
442
+ finalConfig.geminiModel = customGemini || 'gemini-2.5-flash'
443
+ }
444
+ if (config.openaiModel === 'custom') {
445
+ finalConfig.openaiModel = customOpenAI || 'gpt-4o'
446
+ }
447
+ if (config.openrouterModel === 'custom') {
448
+ finalConfig.openrouterModel = customOpenRouter || 'openai/gpt-4o-mini'
449
+ }
450
+ if (config.deepseekModel === 'custom') {
451
+ finalConfig.deepseekModel = customDeepSeek || 'deepseek-v4-flash'
452
+ }
453
+ if (config.anthropicModel === 'custom') {
454
+ finalConfig.anthropicModel = customAnthropic || 'claude-3-5-sonnet-latest'
455
+ }
456
+ if (config.hfModel === 'custom') {
457
+ finalConfig.hfModel = customHF || 'meta-llama/Meta-Llama-3-8B-Instruct'
458
+ }
459
+ if (config.localModelName === 'custom') {
460
+ finalConfig.localModelName = customLocal || 'local-model'
461
+ }
462
+ if (config.ollamaModel === 'custom') {
463
+ finalConfig.ollamaModel = customOllama || 'llama3:latest'
464
+ }
465
+
466
+ try {
467
+ await setProfileValue('name', userName)
468
+ await setProfileValue('preferences', userPreferences)
469
+ } catch (e) {
470
+ console.error("Failed to save user profile memory:", e)
471
+ }
472
+
473
+ if (window.settingsApi) {
474
+ await window.settingsApi.saveSettings(finalConfig)
475
+ applyThemeStyles(finalConfig)
476
+ } else {
477
+ try {
478
+ await setProfileValue('user-settings', JSON.stringify(finalConfig))
479
+ applyThemeStyles(finalConfig)
480
+ } catch (e) {
481
+ console.error("Failed to save user settings:", e)
482
+ }
483
+ }
484
+ }
485
+
382
486
  const handleReset = async () => {
383
487
  if (confirm('Reset all settings to default?')) {
384
488
  setConfig(DEFAULT_CONFIG)
@@ -616,6 +720,17 @@ export default function SettingsWindow() {
616
720
  </span>
617
721
  <strong>Plugins</strong>
618
722
  </button>
723
+ <button className={`tab-btn ${activeTab === 'sect-agents' ? 'active' : ''}`} onClick={() => setActiveTab('sect-agents')}>
724
+ <span style={{ display: 'inline-flex', alignItems: 'center' }}>
725
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
726
+ <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
727
+ <circle cx="9" cy="7" r="4"></circle>
728
+ <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
729
+ <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
730
+ </svg>
731
+ </span>
732
+ <strong>Multi-Agent (Beta)</strong>
733
+ </button>
619
734
  </nav>
620
735
 
621
736
  <div className="settings-content">
@@ -652,6 +767,7 @@ export default function SettingsWindow() {
652
767
  handleCheckUpdates={handleCheckUpdates}
653
768
  handleInstallUpdate={handleInstallUpdate}
654
769
  isDesktopApp={isDesktopApp}
770
+ onSaveWithoutClosing={handleSaveWithoutClosing}
655
771
  />
656
772
  )}
657
773
 
@@ -705,6 +821,14 @@ export default function SettingsWindow() {
705
821
  handleConnectPlugin={handleConnectPlugin}
706
822
  />
707
823
  )}
824
+
825
+ {activeTab === 'sect-agents' && (
826
+ <AgentsTab
827
+ config={config}
828
+ updateField={updateField}
829
+ dynamicOllamaModels={dynamicOllamaModels}
830
+ />
831
+ )}
708
832
  </div>
709
833
  </main>
710
834
 
@@ -1683,12 +1683,28 @@ h1 {
1683
1683
  width: 15px;
1684
1684
  }
1685
1685
 
1686
+ .agent-activity-icon[data-has-img="true"]::before,
1687
+ .agent-activity-icon[data-has-img="true"]::after {
1688
+ display: none !important;
1689
+ }
1690
+
1691
+ .agent-activity-icon img {
1692
+ width: 14px;
1693
+ height: 14px;
1694
+ display: block;
1695
+ object-fit: contain;
1696
+ filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.12));
1697
+ pointer-events: none;
1698
+ user-select: none;
1699
+ }
1700
+
1686
1701
  .agent-activity-icon::before,
1687
1702
  .agent-activity-icon::after {
1688
1703
  content: "";
1689
1704
  position: absolute;
1690
1705
  }
1691
1706
 
1707
+
1692
1708
  .agent-activity-item[data-kind="folder"] .agent-activity-icon::before {
1693
1709
  background: var(--text-muted);
1694
1710
  border-radius: 2px 2px 1px 1px;
@@ -4167,3 +4183,87 @@ input:checked + .slider:before {
4167
4183
  -ms-user-select: text !important;
4168
4184
  }
4169
4185
 
4186
+ /* Stop button styling overrides for ChatPanel */
4187
+ #send-btn.stop-btn {
4188
+ background: #ef4444 !important;
4189
+ color: #ffffff !important;
4190
+ box-shadow: 0 8px 18px rgba(239, 68, 68, 0.28) !important;
4191
+ }
4192
+
4193
+ #send-btn.stop-btn:hover {
4194
+ background: #dc2626 !important;
4195
+ }
4196
+
4197
+ /* Breathing voice-active aura glow */
4198
+ @keyframes aura-breathe-form {
4199
+ 0% {
4200
+ box-shadow: 0 4px 15px color-mix(in srgb, var(--accent) 22%, transparent);
4201
+ }
4202
+ 50% {
4203
+ box-shadow: 0 4px 25px color-mix(in srgb, var(--accent) 42%, transparent);
4204
+ }
4205
+ 100% {
4206
+ box-shadow: 0 4px 15px color-mix(in srgb, var(--accent) 22%, transparent);
4207
+ }
4208
+ }
4209
+
4210
+ @keyframes aura-breathe-bar {
4211
+ 0% {
4212
+ box-shadow: 0 -4px 15px color-mix(in srgb, var(--accent) 22%, transparent);
4213
+ }
4214
+ 50% {
4215
+ box-shadow: 0 -4px 25px color-mix(in srgb, var(--accent) 42%, transparent);
4216
+ }
4217
+ 100% {
4218
+ box-shadow: 0 -4px 15px color-mix(in srgb, var(--accent) 22%, transparent);
4219
+ }
4220
+ }
4221
+
4222
+ .input-area.voice-active #chat-form {
4223
+ border-color: var(--accent) !important;
4224
+ animation: aura-breathe-form 2s infinite ease-in-out;
4225
+ transition: border-color 0.3s ease;
4226
+ }
4227
+
4228
+ .input-area.voice-active .voice-mode-bar {
4229
+ border-color: var(--accent) !important;
4230
+ animation: aura-breathe-bar 2s infinite ease-in-out;
4231
+ transition: border-color 0.3s ease;
4232
+ }
4233
+
4234
+ /* Saved Pictures Gallery Load More styles */
4235
+ .pictures-load-more-container {
4236
+ display: flex;
4237
+ justify-content: center;
4238
+ margin-top: 36px;
4239
+ margin-bottom: 24px;
4240
+ }
4241
+
4242
+ .pictures-load-more-btn {
4243
+ height: 40px;
4244
+ padding: 0 24px;
4245
+ border: 1px solid var(--border);
4246
+ border-radius: 10px;
4247
+ background: var(--card-bg, rgba(255, 255, 255, 0.04));
4248
+ color: var(--text-main);
4249
+ font-size: 0.88rem;
4250
+ font-weight: 500;
4251
+ cursor: pointer;
4252
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
4253
+ display: flex;
4254
+ align-items: center;
4255
+ gap: 8px;
4256
+ }
4257
+
4258
+ .pictures-load-more-btn:hover {
4259
+ background: var(--border);
4260
+ border-color: var(--accent);
4261
+ color: var(--accent);
4262
+ box-shadow: 0 0 12px color-mix(in srgb, var(--accent) 20%, transparent);
4263
+ }
4264
+
4265
+ .pictures-load-more-btn:active {
4266
+ transform: scale(0.98);
4267
+ }
4268
+
4269
+