@pheem49/mint 1.8.1 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +3 -3
- package/Cargo.toml +1 -1
- package/README.md +0 -17
- package/Release_Note.md +92 -58
- package/bin/mint +0 -0
- package/crates/mint-cli/src/agent.rs +100 -6
- package/crates/mint-cli/src/main.rs +166 -52
- package/crates/mint-core/src/agent_loop.rs +1 -0
- package/crates/mint-core/src/api_server.rs +55 -2
- package/crates/mint-core/src/browser.rs +55 -0
- package/crates/mint-core/src/channels.rs +1 -0
- package/crates/mint-core/src/chat.rs +3 -0
- package/crates/mint-core/src/config.rs +61 -0
- package/crates/mint-core/src/lib.rs +14 -0
- package/crates/mint-core/src/orchestration.rs +180 -11
- package/crates/mint-core/src/tts.rs +3 -1
- package/flatpak/com.pheem49.mint.desktop +9 -0
- package/flatpak/com.pheem49.mint.metainfo.xml +35 -0
- package/flatpak/com.pheem49.mint.yaml +52 -0
- package/package.json +1 -1
- package/shared-modules/.github/workflows/ci.yml +120 -0
- package/shared-modules/.github/workflows/update.yaml +28 -0
- package/shared-modules/CODEOWNERS +23 -0
- package/shared-modules/README.md +79 -0
- package/shared-modules/SDL/README.md +3 -0
- package/shared-modules/SDL/SDL-1.2.15.json +51 -0
- package/shared-modules/SDL/SDL_Pango-0.1.2-API-adds.patch +118 -0
- package/shared-modules/SDL/SDL_image-1.2.12.json +25 -0
- package/shared-modules/SDL/SDL_mixer-1.2.12.json +27 -0
- package/shared-modules/SDL/SDL_net-1.2.8.json +19 -0
- package/shared-modules/SDL/SDL_pango-0.1.2.json +26 -0
- package/shared-modules/SDL/SDL_ttf-2.0.11.json +28 -0
- package/shared-modules/SDL/libsdl-1.2.15-strict-prototypes.patch +316 -0
- package/shared-modules/SDL/sdl-check-for-SDL_VIDEO_X11_BACKINGSTORE.patch +24 -0
- package/shared-modules/SDL/sdl-libx11-build.patch +59 -0
- package/shared-modules/SDL/sdl-sysloadso-buffer-length.patch +27 -0
- package/shared-modules/SDL/sdl12-compat.json +22 -0
- package/shared-modules/SDL2/SDL2_image.json +27 -0
- package/shared-modules/SDL2/SDL2_ttf.json +29 -0
- package/shared-modules/cld2/CMakeLists.txt +157 -0
- package/shared-modules/cld2/cld2.json +24 -0
- package/shared-modules/clutter/clutter.json +72 -0
- package/shared-modules/dbus-glib/dbus-glib.json +29 -0
- package/shared-modules/glew/glew.json +36 -0
- package/shared-modules/glu/glu-9.json +12 -0
- package/shared-modules/gtk2/arc-gtk2-theme-Replace-Inkscape-dependency-with-rsvg-convert.patch +180 -0
- package/shared-modules/gtk2/gtk2-common-themes.json +360 -0
- package/shared-modules/gtk2/gtk2-fix-crash-in-show-uri.patch +30 -0
- package/shared-modules/gtk2/gtk2-gcc14.patch +281 -0
- package/shared-modules/gtk2/gtk2-gcc15.patch +250 -0
- package/shared-modules/gtk2/gtk2-murrine-c99.patch +34 -0
- package/shared-modules/gtk2/gtk2-use-adwaita-theme.patch +80 -0
- package/shared-modules/gtk2/gtk2.json +120 -0
- package/shared-modules/gtk2/ibus-gcc15.patch +13 -0
- package/shared-modules/gtk2/murrine-engine-fix-crash.patch +12 -0
- package/shared-modules/gtk2/readme.md +10 -0
- package/shared-modules/intltool/intltool-0.51.json +20 -0
- package/shared-modules/intltool/intltool-perl5.26-regex-fixes.patch +59 -0
- package/shared-modules/lame/0001-Build-xmm_quantize_sub.c-with-msse.patch +34 -0
- package/shared-modules/lame/0002-Include-GTK-1-autoconf-directives-in-build-system.patch +222 -0
- package/shared-modules/lame/0003-Patch-out-remaining-ansi2knr.patch +70 -0
- package/shared-modules/lame/0004-initscr-is-not-used-anywhere-in-lame-sourcetree.patch +41 -0
- package/shared-modules/lame/0005-Update-autoconf.patch +81 -0
- package/shared-modules/lame/lame-3.99.5.json +32 -0
- package/shared-modules/libappindicator/libappindicator-gtk2-12.10.json +140 -0
- package/shared-modules/libappindicator/libappindicator-gtk3-12.10.json +140 -0
- package/shared-modules/libappindicator/libappindicator-gtk3-introspection-12.10.json +140 -0
- package/shared-modules/libappindicator/patches/0001-libappindicator-ftbfs.patch +20 -0
- package/shared-modules/libappindicator/patches/0002-libappindicator-no-python.patch +888 -0
- package/shared-modules/libappindicator/patches/0003-libappindicator-fix-crash-from-incorrect-signal-emission.patch +74 -0
- package/shared-modules/libappindicator/patches/0004-libappindicator-fix-crash-iterating-icon-themes.patch +11 -0
- package/shared-modules/libayatana-appindicator/0001-Fix-HAVE_VALGRIND-AM_CONDITIONAL.patch +51 -0
- package/shared-modules/libayatana-appindicator/0001-Make-introspection-configurable.patch +44 -0
- package/shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json +121 -0
- package/shared-modules/libcanberra/0001-gtk-Don-t-assume-all-GdkDisplays-are-GdkX11Displays-.patch +123 -0
- package/shared-modules/libcanberra/libcanberra.json +44 -0
- package/shared-modules/libmad/libmad.json +19 -0
- package/shared-modules/libsecret/libsecret.json +23 -0
- package/shared-modules/libsoup/libsoup-2.4.json +14 -0
- package/shared-modules/libusb/libusb.json +27 -0
- package/shared-modules/linux-audio/dssi.json +31 -0
- package/shared-modules/linux-audio/fftw3f-static.json +44 -0
- package/shared-modules/linux-audio/fftw3f.json +42 -0
- package/shared-modules/linux-audio/fluidsynth2-static.json +30 -0
- package/shared-modules/linux-audio/fluidsynth2.json +19 -0
- package/shared-modules/linux-audio/ladspa.json +26 -0
- package/shared-modules/linux-audio/lash.json +35 -0
- package/shared-modules/linux-audio/libinstpatch.json +16 -0
- package/shared-modules/linux-audio/liblo-static.json +22 -0
- package/shared-modules/linux-audio/liblo.json +17 -0
- package/shared-modules/linux-audio/lilv-static.json +94 -0
- package/shared-modules/linux-audio/lilv.json +82 -0
- package/shared-modules/linux-audio/lrdf.json +57 -0
- package/shared-modules/linux-audio/lv2.json +21 -0
- package/shared-modules/linux-audio/patches/lash/0001-configure-remove-aclocal-m4.patch +29 -0
- package/shared-modules/linux-audio/patches/lash/0002-Fix-build-with-recent-gcc.patch +73 -0
- package/shared-modules/linux-audio/patches/lash/0003-Fix-linking.patch +136 -0
- package/shared-modules/linux-audio/patches/raptor-libxml.patch +31 -0
- package/shared-modules/linux-audio/patches/stk-install.sh +54 -0
- package/shared-modules/linux-audio/stk.json +28 -0
- package/shared-modules/lua5.1/0004-Fix-stack-overflow-in-vararg-functions.patch +21 -0
- package/shared-modules/lua5.1/lua-5.1.5-so.patch +43 -0
- package/shared-modules/lua5.1/lua-5.1.5.json +42 -0
- package/shared-modules/lua5.3/lua-5.3.5.json +41 -0
- package/shared-modules/lua5.3/lua.pc.in +8 -0
- package/shared-modules/lua5.3/luaconf.patch +47 -0
- package/shared-modules/lua5.3/makefile-add-liblua.so-rule.patch +47 -0
- package/shared-modules/lua5.4/lua-5.4.json +41 -0
- package/shared-modules/lua5.4/lua.pc.in +8 -0
- package/shared-modules/lua5.4/luaconf.patch +34 -0
- package/shared-modules/lua5.4/makefile-add-liblua.so-rule.patch +42 -0
- package/shared-modules/luajit/luajit.json +30 -0
- package/shared-modules/lzo/lzo.json +27 -0
- package/shared-modules/mac/mac.json +18 -0
- package/shared-modules/physfs/physfs.json +26 -0
- package/shared-modules/pygame/audiofile-gcc6.patch +21 -0
- package/shared-modules/pygame/cython-fixes.patch +34 -0
- package/shared-modules/pygame/fluidsynth-no-rawmidi.patch +69 -0
- package/shared-modules/pygame/glib.patch +25 -0
- package/shared-modules/pygame/pygame1-python2.json +157 -0
- package/shared-modules/pygtk/pygtk-c99.patch +15 -0
- package/shared-modules/pygtk/pygtk.json +117 -0
- package/shared-modules/pygtk/pygtk.patch +39 -0
- package/shared-modules/python2.7/python-2.7.json +48 -0
- package/shared-modules/smpeg/smpeg-0.4.5.json +50 -0
- package/shared-modules/smpeg/smpeg-am-prog-as.patch +12 -0
- package/shared-modules/smpeg/smpeg-export-mpegaudio-class.patch +17 -0
- package/shared-modules/smpeg/smpeg-gcc6.patch +40 -0
- package/shared-modules/smpeg/smpeg-no-gtk.patch +37 -0
- package/shared-modules/squashfs-tools/squashfs-tools.json +25 -0
- package/shared-modules/update-checker.sh +50 -0
- package/shared-modules/vorbisgain/0001-temp_files.patch +75 -0
- package/shared-modules/vorbisgain/0002-errno.patch +33 -0
- package/shared-modules/vorbisgain/0003-manpage.patch +248 -0
- package/shared-modules/vorbisgain/0004-vorbisgain_mtime.patch +183 -0
- package/shared-modules/vorbisgain/0005-double_fclose.patch +21 -0
- package/shared-modules/vorbisgain/0006-manpage_hyphens.patch +56 -0
- package/shared-modules/vorbisgain/0007-recursively_spelling.patch +14 -0
- package/shared-modules/vorbisgain/0008-manpage_recursion_mistake.patch +23 -0
- package/shared-modules/vorbisgain/0009-hardening.patch +13 -0
- package/shared-modules/vorbisgain/0010-fclose.patch +13 -0
- package/shared-modules/vorbisgain/README.md +23 -0
- package/shared-modules/vorbisgain/vorbisgain_0.37-2.json +29 -0
- package/src/renderer/shared/agentProgress.ts +145 -0
- package/src/renderer/shared/calculator.ts +52 -0
- package/src/renderer/shared/components/AgentActivityDrawer.tsx +72 -0
- package/src/renderer/shared/components/AgentActivityTable.tsx +66 -0
- package/src/renderer/shared/components/ApprovalCard.tsx +93 -0
- package/src/renderer/shared/components/ChatCodeBlock.tsx +75 -0
- package/src/renderer/shared/components/ThinkingBlock.tsx +101 -0
- package/src/renderer/shared/constants/models.ts +91 -0
- package/src/renderer/shared/platform.ts +82 -0
- package/src/renderer/shared/types.ts +140 -0
- package/src/renderer/shared/utils/agentActivity.ts +187 -0
- package/src/renderer/shared/utils/approval.ts +1 -0
- package/src/renderer/shared/utils/approval.tsx +64 -0
- package/src/renderer/shared/utils/markdown.tsx +347 -0
- package/src/renderer/shared/utils/providers.ts +36 -0
- package/src/renderer/shared/utils/speech.ts +216 -0
- package/src/renderer/shared/utils/ui.ts +147 -0
- package/src/renderer/src/agentProgress.ts +11 -39
- package/src/renderer/src/calculator.ts +3 -46
- package/src/renderer/src/components/ChatPanel.tsx +194 -1303
- package/src/renderer/src/components/MintDashboard.tsx +23 -169
- package/src/renderer/src/components/PicturesLibrary.tsx +32 -8
- package/src/renderer/src/components/Settings/AgentsTab.tsx +362 -0
- package/src/renderer/src/components/Settings/AutomationTab.tsx +0 -22
- package/src/renderer/src/components/SettingsWindow.tsx +53 -3
- package/src/renderer/src/css/styles.css +84 -0
- package/src/renderer/src/tauri.ts +128 -143
- package/src/renderer/src-web/agentProgress.ts +11 -39
- package/src/renderer/src-web/calculator.ts +3 -46
- package/src/renderer/src-web/components/ChatPanel.tsx +194 -1301
- package/src/renderer/src-web/components/MintDashboard.tsx +23 -169
- package/src/renderer/src-web/components/PicturesLibrary.tsx +23 -29
- package/src/renderer/src-web/components/Settings/AgentsTab.tsx +362 -0
- package/src/renderer/src-web/components/Settings/AutomationTab.tsx +0 -22
- package/src/renderer/src-web/components/SettingsWindow.tsx +53 -3
- package/src/renderer/src-web/css/styles.css +84 -0
- package/src/renderer/src-web/tauri.ts +180 -102
- package/src-tauri/src/lib.rs +137 -33
- package/src-tauri/src/webhooks.rs +1 -0
- package/src-tauri/src/workflows.rs +1 -0
- package/src-tauri/tauri.conf.json +1 -1
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
selectWorkspaceDirectory,
|
|
13
13
|
saveInteractionAgentActivity,
|
|
14
14
|
streamChatMessage,
|
|
15
|
+
cancelChatMessage,
|
|
15
16
|
submitToolApproval,
|
|
16
17
|
listen,
|
|
17
|
-
readClipboardImage as readTauriClipboardImage,
|
|
18
18
|
type AgentProgress,
|
|
19
19
|
type ChatResponse,
|
|
20
20
|
type ChatSession,
|
|
@@ -30,6 +30,13 @@ import type { ModelInteraction } from './ModelPanel'
|
|
|
30
30
|
import PicturesLibrary from './PicturesLibrary'
|
|
31
31
|
import WorkspacePanel from './WorkspacePanel'
|
|
32
32
|
import WorkflowBuilderPanel from './WorkflowBuilderPanel'
|
|
33
|
+
import {
|
|
34
|
+
errorMessage,
|
|
35
|
+
readImage,
|
|
36
|
+
readDocument,
|
|
37
|
+
createTrimmedImagePreview,
|
|
38
|
+
applyThemeStyles,
|
|
39
|
+
} from '../../shared/utils/ui'
|
|
33
40
|
|
|
34
41
|
const EXPRESSIONS = [
|
|
35
42
|
"Default",
|
|
@@ -88,142 +95,6 @@ const MOCK_WELCOME_INTERACTION = {
|
|
|
88
95
|
createdAt: new Date().toISOString(),
|
|
89
96
|
}
|
|
90
97
|
|
|
91
|
-
function errorMessage(reason: unknown) {
|
|
92
|
-
return reason instanceof Error ? reason.message : String(reason)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function readImage(file: File): Promise<string> {
|
|
96
|
-
return new Promise((resolve, reject) => {
|
|
97
|
-
const reader = new FileReader()
|
|
98
|
-
reader.onload = () => resolve(String(reader.result))
|
|
99
|
-
reader.onerror = () => reject(reader.error ?? new Error('Unable to read image'))
|
|
100
|
-
reader.readAsDataURL(file)
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function readDocument(file: File): Promise<string> {
|
|
105
|
-
return new Promise((resolve, reject) => {
|
|
106
|
-
const reader = new FileReader()
|
|
107
|
-
reader.onload = () => resolve(String(reader.result))
|
|
108
|
-
reader.onerror = () => reject(reader.error ?? new Error('Unable to read document'))
|
|
109
|
-
reader.readAsDataURL(file)
|
|
110
|
-
})
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async function createTrimmedImagePreview(dataUri: string): Promise<string> {
|
|
114
|
-
const image = await new Promise<HTMLImageElement>((resolve, reject) => {
|
|
115
|
-
const nextImage = new Image()
|
|
116
|
-
nextImage.onload = () => resolve(nextImage)
|
|
117
|
-
nextImage.onerror = () => reject(new Error('Unable to prepare image preview'))
|
|
118
|
-
nextImage.src = dataUri
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
const canvas = document.createElement('canvas')
|
|
122
|
-
canvas.width = image.naturalWidth || image.width
|
|
123
|
-
canvas.height = image.naturalHeight || image.height
|
|
124
|
-
const context = canvas.getContext('2d', { willReadFrequently: true })
|
|
125
|
-
if (!context || canvas.width === 0 || canvas.height === 0) return dataUri
|
|
126
|
-
|
|
127
|
-
context.drawImage(image, 0, 0)
|
|
128
|
-
const pixels = context.getImageData(0, 0, canvas.width, canvas.height)
|
|
129
|
-
let minX = canvas.width
|
|
130
|
-
let minY = canvas.height
|
|
131
|
-
let maxX = -1
|
|
132
|
-
let maxY = -1
|
|
133
|
-
|
|
134
|
-
for (let y = 0; y < canvas.height; y += 1) {
|
|
135
|
-
for (let x = 0; x < canvas.width; x += 1) {
|
|
136
|
-
const alpha = pixels.data[(y * canvas.width + x) * 4 + 3]
|
|
137
|
-
if (alpha > 12) {
|
|
138
|
-
minX = Math.min(minX, x)
|
|
139
|
-
minY = Math.min(minY, y)
|
|
140
|
-
maxX = Math.max(maxX, x)
|
|
141
|
-
maxY = Math.max(maxY, y)
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (maxX < minX || maxY < minY) return dataUri
|
|
147
|
-
|
|
148
|
-
const padding = 8
|
|
149
|
-
const sx = Math.max(0, minX - padding)
|
|
150
|
-
const sy = Math.max(0, minY - padding)
|
|
151
|
-
const sw = Math.min(canvas.width - sx, maxX - minX + 1 + padding * 2)
|
|
152
|
-
const sh = Math.min(canvas.height - sy, maxY - minY + 1 + padding * 2)
|
|
153
|
-
|
|
154
|
-
if (sw >= canvas.width * 0.92 && sh >= canvas.height * 0.92) return dataUri
|
|
155
|
-
|
|
156
|
-
const previewCanvas = document.createElement('canvas')
|
|
157
|
-
previewCanvas.width = sw
|
|
158
|
-
previewCanvas.height = sh
|
|
159
|
-
const previewContext = previewCanvas.getContext('2d')
|
|
160
|
-
if (!previewContext) return dataUri
|
|
161
|
-
previewContext.drawImage(canvas, sx, sy, sw, sh, 0, 0, sw, sh)
|
|
162
|
-
return previewCanvas.toDataURL('image/png')
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const lightenColor = (hex: string, amount: number) => {
|
|
166
|
-
const clean = hex.replace('#', '')
|
|
167
|
-
if (clean.length !== 6) return hex
|
|
168
|
-
const num = parseInt(clean, 16)
|
|
169
|
-
const r = Math.min(255, (num >> 16) + amount)
|
|
170
|
-
const g = Math.min(255, ((num >> 8) & 0x00FF) + amount)
|
|
171
|
-
const b = Math.min(255, (num & 0x0000FF) + amount)
|
|
172
|
-
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const hexToRgb = (hex: string) => {
|
|
176
|
-
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
|
177
|
-
return result ? {
|
|
178
|
-
r: parseInt(result[1], 16),
|
|
179
|
-
g: parseInt(result[2], 16),
|
|
180
|
-
b: parseInt(result[3], 16),
|
|
181
|
-
} : { r: 15, g: 23, b: 42 }
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const applyThemeStyles = (cfg: any) => {
|
|
185
|
-
const theme = cfg.theme || 'dark'
|
|
186
|
-
const accentColor = cfg.accentColor || '#4f83e6'
|
|
187
|
-
const systemTextColor = cfg.systemTextColor || '#f8fafc'
|
|
188
|
-
|
|
189
|
-
document.documentElement.setAttribute('data-theme', theme)
|
|
190
|
-
document.documentElement.style.setProperty('--accent', accentColor)
|
|
191
|
-
document.documentElement.style.setProperty('--accent-hover', lightenColor(accentColor, 20))
|
|
192
|
-
document.documentElement.style.setProperty('--text-main', systemTextColor)
|
|
193
|
-
document.documentElement.style.setProperty('--glass-blur', cfg.glassBlur || 'blur(16px)')
|
|
194
|
-
document.body.style.fontFamily = cfg.fontFamily || "'Outfit', sans-serif"
|
|
195
|
-
document.documentElement.style.fontSize = cfg.fontSize || '18px'
|
|
196
|
-
|
|
197
|
-
if (theme === 'custom') {
|
|
198
|
-
if (cfg.customBgStart && cfg.customBgEnd) {
|
|
199
|
-
document.documentElement.style.setProperty('--bg-color', cfg.customBgStart)
|
|
200
|
-
document.documentElement.style.setProperty('--bg-gradient', `linear-gradient(135deg, ${cfg.customBgStart} 0%, ${cfg.customBgEnd} 100%)`)
|
|
201
|
-
}
|
|
202
|
-
if (cfg.customPanelBg) {
|
|
203
|
-
const rgb = hexToRgb(cfg.customPanelBg)
|
|
204
|
-
document.documentElement.style.setProperty('--panel-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.75)`)
|
|
205
|
-
document.documentElement.style.setProperty('--panel-raised', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.82)`)
|
|
206
|
-
document.documentElement.style.setProperty('--panel-soft', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.46)`)
|
|
207
|
-
document.documentElement.style.setProperty('--chrome-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.88)`)
|
|
208
|
-
document.documentElement.style.setProperty('--surface-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.62)`)
|
|
209
|
-
document.documentElement.style.setProperty('--surface-strong', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.86)`)
|
|
210
|
-
document.documentElement.style.setProperty('--input-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.72)`)
|
|
211
|
-
}
|
|
212
|
-
return
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
;[
|
|
216
|
-
'--bg-color',
|
|
217
|
-
'--bg-gradient',
|
|
218
|
-
'--panel-bg',
|
|
219
|
-
'--panel-raised',
|
|
220
|
-
'--panel-soft',
|
|
221
|
-
'--chrome-bg',
|
|
222
|
-
'--surface-bg',
|
|
223
|
-
'--surface-strong',
|
|
224
|
-
'--input-bg',
|
|
225
|
-
].forEach((name) => document.documentElement.style.removeProperty(name))
|
|
226
|
-
}
|
|
227
98
|
|
|
228
99
|
export default function MintDashboard() {
|
|
229
100
|
const [view, setView] = useState<DashboardView>('chat')
|
|
@@ -522,6 +393,20 @@ export default function MintDashboard() {
|
|
|
522
393
|
}
|
|
523
394
|
}
|
|
524
395
|
|
|
396
|
+
async function handleCancelMessage() {
|
|
397
|
+
if (!sending || !streamingConversationId) return
|
|
398
|
+
try {
|
|
399
|
+
await cancelChatMessage(streamingConversationId)
|
|
400
|
+
} catch (e) {
|
|
401
|
+
console.error("Failed to cancel message stream:", e)
|
|
402
|
+
} finally {
|
|
403
|
+
setSending(false)
|
|
404
|
+
setStreamingConversationId(null)
|
|
405
|
+
setSendingMessage('')
|
|
406
|
+
setSendingImageCount(0)
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
525
410
|
async function sendPrompt(
|
|
526
411
|
promptText: string,
|
|
527
412
|
options: {
|
|
@@ -705,37 +590,6 @@ export default function MintDashboard() {
|
|
|
705
590
|
return true
|
|
706
591
|
}
|
|
707
592
|
|
|
708
|
-
async function readClipboardImage() {
|
|
709
|
-
try {
|
|
710
|
-
const dataUri = await readTauriClipboardImage()
|
|
711
|
-
if (dataUri) {
|
|
712
|
-
const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
|
|
713
|
-
setImageAttachments((current) => [...current, { dataUri, previewDataUri, name: 'Pasted image' }])
|
|
714
|
-
return true
|
|
715
|
-
}
|
|
716
|
-
} catch (err) {
|
|
717
|
-
console.warn('Tauri clipboard fallback error:', err)
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
try {
|
|
721
|
-
if (!navigator.clipboard?.read) return false
|
|
722
|
-
const items = await navigator.clipboard.read()
|
|
723
|
-
for (const item of items) {
|
|
724
|
-
const imageType = item.types.find((type) => type.startsWith('image/'))
|
|
725
|
-
if (!imageType) continue
|
|
726
|
-
const blob = await item.getType(imageType)
|
|
727
|
-
const file = new File([blob], 'Pasted image', { type: imageType })
|
|
728
|
-
const dataUri = await readImage(file)
|
|
729
|
-
const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
|
|
730
|
-
setImageAttachments((current) => [...current, { dataUri, previewDataUri, name: 'Pasted image' }])
|
|
731
|
-
return true
|
|
732
|
-
}
|
|
733
|
-
return false
|
|
734
|
-
} catch {
|
|
735
|
-
// Some environments expose pasted images only through ClipboardEvent.
|
|
736
|
-
return false
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
593
|
|
|
740
594
|
async function selectDocument(event: ChangeEvent<HTMLInputElement>) {
|
|
741
595
|
const file = event.target.files?.[0]
|
|
@@ -1110,7 +964,6 @@ export default function MintDashboard() {
|
|
|
1110
964
|
onSelectImage={selectImage}
|
|
1111
965
|
onSelectDocument={selectDocument}
|
|
1112
966
|
onPasteImage={pasteImage}
|
|
1113
|
-
onReadClipboardImage={readClipboardImage}
|
|
1114
967
|
onSetMessage={setMessage}
|
|
1115
968
|
onSendVoiceMessage={sendVoiceMessage}
|
|
1116
969
|
onRemoveImage={(idx: number) => {
|
|
@@ -1126,6 +979,7 @@ export default function MintDashboard() {
|
|
|
1126
979
|
settingsConfig={settingsConfig}
|
|
1127
980
|
onSetModel={changeModel}
|
|
1128
981
|
onApproval={handleApproval}
|
|
982
|
+
onCancelMessage={handleCancelMessage}
|
|
1129
983
|
/>
|
|
1130
984
|
</main>
|
|
1131
985
|
<PicturesLibrary view={view} pictures={pictures} onSetView={setView} onRefreshPictures={refreshPictures} />
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
1
2
|
import { type PictureEntry, convertFileSrc } from '../tauri'
|
|
2
3
|
import type { DashboardView } from './DashboardSidebar'
|
|
3
4
|
|
|
@@ -9,6 +10,16 @@ interface PicturesLibraryProps {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export default function PicturesLibrary({ view, pictures, onSetView, onRefreshPictures }: PicturesLibraryProps) {
|
|
13
|
+
const [visibleCount, setVisibleCount] = useState(24)
|
|
14
|
+
const visiblePictures = useMemo(
|
|
15
|
+
() => pictures.slice(0, visibleCount),
|
|
16
|
+
[pictures, visibleCount],
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
setVisibleCount(24)
|
|
21
|
+
}, [view, pictures])
|
|
22
|
+
|
|
12
23
|
return (
|
|
13
24
|
<section className={`pictures-library ${view === 'pictures' ? 'is-visible' : ''}`} aria-hidden={view !== 'pictures'}>
|
|
14
25
|
<header className="pictures-header">
|
|
@@ -34,14 +45,27 @@ export default function PicturesLibrary({ view, pictures, onSetView, onRefreshPi
|
|
|
34
45
|
<span>Images appear here after a message with an attachment is sent successfully.</span>
|
|
35
46
|
</div>
|
|
36
47
|
) : (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
<>
|
|
49
|
+
<div className="pictures-grid">
|
|
50
|
+
{visiblePictures.map((picture, index) => (
|
|
51
|
+
<article className="picture-card" key={picture.id}>
|
|
52
|
+
<img src={convertFileSrc(picture.thumbnailPath || picture.thumbnailUrl || picture.path)} alt={picture.message || picture.filename} loading={index < 6 ? 'eager' : 'lazy'} decoding="async" />
|
|
53
|
+
<div className="picture-card-meta"><span>{picture.message || picture.filename}</span></div>
|
|
54
|
+
</article>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
{visibleCount < pictures.length && (
|
|
58
|
+
<div className="pictures-load-more-container">
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
className="pictures-load-more-btn"
|
|
62
|
+
onClick={() => setVisibleCount((prev) => prev + 24)}
|
|
63
|
+
>
|
|
64
|
+
Load More ({pictures.length - visibleCount} remaining)
|
|
65
|
+
</button>
|
|
66
|
+
</div>
|
|
67
|
+
)}
|
|
68
|
+
</>
|
|
45
69
|
)}
|
|
46
70
|
</section>
|
|
47
71
|
)
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_CONFIG,
|
|
4
|
+
GEMINI_MODELS,
|
|
5
|
+
OPENAI_MODELS,
|
|
6
|
+
OPENROUTER_MODELS,
|
|
7
|
+
DEEPSEEK_MODELS,
|
|
8
|
+
ANTHROPIC_MODELS,
|
|
9
|
+
HF_MODELS,
|
|
10
|
+
LOCAL_MODELS
|
|
11
|
+
} from '../SettingsWindow'
|
|
12
|
+
|
|
13
|
+
interface Agent {
|
|
14
|
+
id: string
|
|
15
|
+
name: string
|
|
16
|
+
provider: string
|
|
17
|
+
model: string
|
|
18
|
+
apiKey?: string
|
|
19
|
+
systemInstruction: string
|
|
20
|
+
enabled: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface AgentsTabProps {
|
|
24
|
+
config: typeof DEFAULT_CONFIG & { agents?: Agent[] }
|
|
25
|
+
updateField: (field: any, value: any) => void
|
|
26
|
+
dynamicOllamaModels?: string[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function AgentsTab({ config, updateField, dynamicOllamaModels = [] }: AgentsTabProps) {
|
|
30
|
+
const agents = config.agents || []
|
|
31
|
+
const [editingAgent, setEditingAgent] = useState<Agent | null>(null)
|
|
32
|
+
const [name, setName] = useState('')
|
|
33
|
+
const [provider, setProvider] = useState('gemini')
|
|
34
|
+
const [model, setModel] = useState('gemini-2.5-flash')
|
|
35
|
+
const [apiKey, setApiKey] = useState('')
|
|
36
|
+
const [systemInstruction, setSystemInstruction] = useState('')
|
|
37
|
+
const [isEditing, setIsEditing] = useState(false)
|
|
38
|
+
|
|
39
|
+
const handleProviderChange = (newProvider: string) => {
|
|
40
|
+
setProvider(newProvider)
|
|
41
|
+
let defaultModel = ''
|
|
42
|
+
if (newProvider === 'gemini') defaultModel = config.geminiModel || GEMINI_MODELS[0]
|
|
43
|
+
else if (newProvider === 'openai') defaultModel = config.openaiModel || OPENAI_MODELS[0]
|
|
44
|
+
else if (newProvider === 'anthropic') defaultModel = config.anthropicModel || ANTHROPIC_MODELS[0]
|
|
45
|
+
else if (newProvider === 'openrouter') defaultModel = config.openrouterModel || OPENROUTER_MODELS[0]
|
|
46
|
+
else if (newProvider === 'deepseek') defaultModel = config.deepseekModel || DEEPSEEK_MODELS[0]
|
|
47
|
+
else if (newProvider === 'huggingface') defaultModel = config.hfModel || HF_MODELS[0]
|
|
48
|
+
else if (newProvider === 'local_openai') defaultModel = config.localModelName || LOCAL_MODELS[0]
|
|
49
|
+
else if (newProvider === 'ollama') defaultModel = config.ollamaModel || dynamicOllamaModels[0] || 'llama3:latest'
|
|
50
|
+
setModel(defaultModel)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const handleEdit = (agent: Agent) => {
|
|
54
|
+
setEditingAgent(agent)
|
|
55
|
+
setName(agent.name)
|
|
56
|
+
setProvider(agent.provider)
|
|
57
|
+
setModel(agent.model)
|
|
58
|
+
setApiKey(agent.apiKey || '')
|
|
59
|
+
setSystemInstruction(agent.systemInstruction)
|
|
60
|
+
setIsEditing(true)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const handleSaveAgent = () => {
|
|
64
|
+
if (!name.trim()) {
|
|
65
|
+
alert('Please enter an agent name.')
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let updatedAgents: Agent[] = []
|
|
70
|
+
if (editingAgent) {
|
|
71
|
+
// Update existing agent
|
|
72
|
+
updatedAgents = agents.map(a =>
|
|
73
|
+
a.id === editingAgent.id
|
|
74
|
+
? { ...a, name, provider, model, apiKey: apiKey || undefined, systemInstruction }
|
|
75
|
+
: a
|
|
76
|
+
)
|
|
77
|
+
} else {
|
|
78
|
+
// Add new agent
|
|
79
|
+
const newAgent: Agent = {
|
|
80
|
+
id: `agent-${Date.now()}`,
|
|
81
|
+
name,
|
|
82
|
+
provider,
|
|
83
|
+
model,
|
|
84
|
+
apiKey: apiKey || undefined,
|
|
85
|
+
systemInstruction,
|
|
86
|
+
enabled: true
|
|
87
|
+
}
|
|
88
|
+
updatedAgents = [...agents, newAgent]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
updateField('agents', updatedAgents)
|
|
92
|
+
closeForm()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const handleDeleteAgent = (id: string) => {
|
|
96
|
+
if (confirm('Are you sure you want to delete this agent?')) {
|
|
97
|
+
const updatedAgents = agents.filter(a => a.id !== id)
|
|
98
|
+
updateField('agents', updatedAgents)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const handleToggleAgent = (id: string, enabled: boolean) => {
|
|
103
|
+
const updatedAgents = agents.map(a =>
|
|
104
|
+
a.id === id ? { ...a, enabled } : a
|
|
105
|
+
)
|
|
106
|
+
updateField('agents', updatedAgents)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const handleDisableAll = () => {
|
|
110
|
+
const updatedAgents = agents.map(a => ({ ...a, enabled: false }))
|
|
111
|
+
updateField('agents', updatedAgents)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const closeForm = () => {
|
|
115
|
+
setEditingAgent(null)
|
|
116
|
+
setName('')
|
|
117
|
+
setProvider('gemini')
|
|
118
|
+
setModel('gemini-2.5-flash')
|
|
119
|
+
setApiKey('')
|
|
120
|
+
setSystemInstruction('')
|
|
121
|
+
setIsEditing(false)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const handleAddClick = () => {
|
|
125
|
+
setEditingAgent(null)
|
|
126
|
+
setName('')
|
|
127
|
+
setProvider('gemini')
|
|
128
|
+
setModel('gemini-2.5-flash')
|
|
129
|
+
setApiKey('')
|
|
130
|
+
setSystemInstruction('')
|
|
131
|
+
setIsEditing(true)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Filter providers that have configured API keys, plus local ones
|
|
135
|
+
const availableProviders = [
|
|
136
|
+
{ value: 'gemini', label: 'Google Gemini', hasKey: !!config.apiKey },
|
|
137
|
+
{ value: 'openai', label: 'OpenAI', hasKey: !!config.openaiApiKey },
|
|
138
|
+
{ value: 'anthropic', label: 'Anthropic Claude', hasKey: !!config.anthropicApiKey },
|
|
139
|
+
{ value: 'openrouter', label: 'OpenRouter', hasKey: !!config.openrouterApiKey },
|
|
140
|
+
{ value: 'deepseek', label: 'DeepSeek', hasKey: !!config.deepseekApiKey },
|
|
141
|
+
{ value: 'ollama', label: 'Ollama (Local)', hasKey: true },
|
|
142
|
+
{ value: 'huggingface', label: 'Hugging Face', hasKey: !!config.hfApiKey },
|
|
143
|
+
{ value: 'local_openai', label: 'Local (LM Studio / OpenAI Compatible)', hasKey: true },
|
|
144
|
+
].filter(p => p.hasKey);
|
|
145
|
+
|
|
146
|
+
// Find out what models list corresponds to the selected provider
|
|
147
|
+
let modelList: string[] = []
|
|
148
|
+
let defaultGeneralModel = ''
|
|
149
|
+
if (provider === 'gemini') {
|
|
150
|
+
modelList = GEMINI_MODELS
|
|
151
|
+
defaultGeneralModel = config.geminiModel
|
|
152
|
+
} else if (provider === 'openai') {
|
|
153
|
+
modelList = OPENAI_MODELS
|
|
154
|
+
defaultGeneralModel = config.openaiModel
|
|
155
|
+
} else if (provider === 'openrouter') {
|
|
156
|
+
modelList = OPENROUTER_MODELS
|
|
157
|
+
defaultGeneralModel = config.openrouterModel
|
|
158
|
+
} else if (provider === 'deepseek') {
|
|
159
|
+
modelList = DEEPSEEK_MODELS
|
|
160
|
+
defaultGeneralModel = config.deepseekModel
|
|
161
|
+
} else if (provider === 'anthropic') {
|
|
162
|
+
modelList = ANTHROPIC_MODELS
|
|
163
|
+
defaultGeneralModel = config.anthropicModel
|
|
164
|
+
} else if (provider === 'huggingface') {
|
|
165
|
+
modelList = HF_MODELS
|
|
166
|
+
defaultGeneralModel = config.hfModel
|
|
167
|
+
} else if (provider === 'local_openai') {
|
|
168
|
+
modelList = LOCAL_MODELS
|
|
169
|
+
defaultGeneralModel = config.localModelName
|
|
170
|
+
} else if (provider === 'ollama') {
|
|
171
|
+
modelList = dynamicOllamaModels
|
|
172
|
+
defaultGeneralModel = config.ollamaModel
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Sort modelList so defaultGeneralModel is first (at the top)
|
|
176
|
+
const sortedModelList = defaultGeneralModel
|
|
177
|
+
? [defaultGeneralModel, ...modelList.filter(m => m !== defaultGeneralModel)]
|
|
178
|
+
: modelList;
|
|
179
|
+
|
|
180
|
+
const isCustomModel = !sortedModelList.includes(model)
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<div className="tab-pane active animate-fade-in" style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
|
|
184
|
+
<section className="setting-section">
|
|
185
|
+
<div className="section-heading" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
186
|
+
<div>
|
|
187
|
+
<p className="section-kicker">Multi-Agent System</p>
|
|
188
|
+
<h2 className="section-title">Agent Collaboration Settings</h2>
|
|
189
|
+
<p className="section-description">
|
|
190
|
+
Configure multiple distinct AI agents with specific roles. You can adjust the models, system instructions, and credentials for each agent.
|
|
191
|
+
</p>
|
|
192
|
+
</div>
|
|
193
|
+
{!isEditing && (
|
|
194
|
+
<div style={{ display: 'flex', gap: '10px', height: 'fit-content', flexShrink: 0 }}>
|
|
195
|
+
<button className="btn btn-secondary" onClick={handleDisableAll} style={{ whiteSpace: 'nowrap' }}>
|
|
196
|
+
Disable All
|
|
197
|
+
</button>
|
|
198
|
+
<button className="btn btn-primary" onClick={handleAddClick} style={{ whiteSpace: 'nowrap' }}>
|
|
199
|
+
+ Add Custom Agent
|
|
200
|
+
</button>
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div className="toggle-row" style={{ margin: '16px 0 24px 0' }}>
|
|
206
|
+
<div>
|
|
207
|
+
<label>Enable Multi-Agent Collaboration</label>
|
|
208
|
+
<p className="hint">Allow multiple specialized agents to collaborate sequentially (Planner → Coder → Reviewer).</p>
|
|
209
|
+
</div>
|
|
210
|
+
<label className="settings-toggle-switch">
|
|
211
|
+
<input
|
|
212
|
+
type="checkbox"
|
|
213
|
+
checked={config.enableAgentCollaboration}
|
|
214
|
+
onChange={(e) => updateField('enableAgentCollaboration', e.target.checked)}
|
|
215
|
+
/>
|
|
216
|
+
<span className="settings-toggle-slider"></span>
|
|
217
|
+
</label>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
{isEditing ? (
|
|
221
|
+
<div className="form-grid single" style={{ background: 'var(--panel-soft)', padding: '20px', borderRadius: '8px', border: '1px solid var(--panel-raised)', marginTop: '10px' }}>
|
|
222
|
+
<h3 style={{ margin: '0 0 16px 0', fontSize: '18px', color: 'var(--accent)' }}>
|
|
223
|
+
{editingAgent ? `Edit Agent: ${editingAgent.name}` : 'Create New Agent'}
|
|
224
|
+
</h3>
|
|
225
|
+
|
|
226
|
+
<div className="setting-row">
|
|
227
|
+
<label>Agent Name</label>
|
|
228
|
+
<input
|
|
229
|
+
type="text"
|
|
230
|
+
value={name}
|
|
231
|
+
onChange={(e) => setName(e.target.value)}
|
|
232
|
+
placeholder="e.g. Code Reviewer"
|
|
233
|
+
/>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<div className="setting-row">
|
|
237
|
+
<label>AI Provider</label>
|
|
238
|
+
<select value={provider} onChange={(e) => handleProviderChange(e.target.value)}>
|
|
239
|
+
{availableProviders.map(p => (
|
|
240
|
+
<option key={p.value} value={p.value}>{p.label}</option>
|
|
241
|
+
))}
|
|
242
|
+
</select>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<div className="setting-row">
|
|
246
|
+
<label>Model Name</label>
|
|
247
|
+
<select
|
|
248
|
+
value={isCustomModel ? 'custom' : model}
|
|
249
|
+
onChange={(e) => {
|
|
250
|
+
const val = e.target.value
|
|
251
|
+
if (val === 'custom') {
|
|
252
|
+
setModel('')
|
|
253
|
+
} else {
|
|
254
|
+
setModel(val)
|
|
255
|
+
}
|
|
256
|
+
}}
|
|
257
|
+
>
|
|
258
|
+
{sortedModelList.map(m => (
|
|
259
|
+
<option key={m} value={m}>{m}</option>
|
|
260
|
+
))}
|
|
261
|
+
<option value="custom">Custom...</option>
|
|
262
|
+
</select>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
{isCustomModel && (
|
|
266
|
+
<div className="setting-row">
|
|
267
|
+
<label>Custom Model Name</label>
|
|
268
|
+
<input
|
|
269
|
+
type="text"
|
|
270
|
+
value={model}
|
|
271
|
+
onChange={(e) => setModel(e.target.value)}
|
|
272
|
+
placeholder="Enter custom model name..."
|
|
273
|
+
/>
|
|
274
|
+
</div>
|
|
275
|
+
)}
|
|
276
|
+
|
|
277
|
+
<div className="setting-row">
|
|
278
|
+
<label>Custom API Key (Optional)</label>
|
|
279
|
+
<input
|
|
280
|
+
type="password"
|
|
281
|
+
value={apiKey}
|
|
282
|
+
onChange={(e) => setApiKey(e.target.value)}
|
|
283
|
+
placeholder="Leave blank to use global provider API key"
|
|
284
|
+
/>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
<div className="setting-row">
|
|
288
|
+
<label>System Instructions / Role</label>
|
|
289
|
+
<textarea
|
|
290
|
+
value={systemInstruction}
|
|
291
|
+
onChange={(e) => setSystemInstruction(e.target.value)}
|
|
292
|
+
placeholder="Describe this agent's goal, instructions, and personality..."
|
|
293
|
+
style={{ minHeight: '120px', resize: 'vertical' }}
|
|
294
|
+
/>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
<div style={{ display: 'flex', gap: '10px', marginTop: '16px', justifyContent: 'flex-end' }}>
|
|
298
|
+
<button className="btn btn-secondary" onClick={closeForm}>Cancel</button>
|
|
299
|
+
<button className="btn btn-primary" onClick={handleSaveAgent}>Save Agent</button>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
) : (
|
|
303
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px', marginTop: '16px' }}>
|
|
304
|
+
{agents.map((agent) => (
|
|
305
|
+
<div
|
|
306
|
+
key={agent.id}
|
|
307
|
+
style={{
|
|
308
|
+
display: 'flex',
|
|
309
|
+
flexDirection: 'column',
|
|
310
|
+
padding: '16px',
|
|
311
|
+
background: 'var(--panel-bg)',
|
|
312
|
+
border: '1px solid var(--panel-raised)',
|
|
313
|
+
borderRadius: '8px',
|
|
314
|
+
gap: '8px'
|
|
315
|
+
}}
|
|
316
|
+
>
|
|
317
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
318
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
|
319
|
+
<strong style={{ fontSize: '18px' }}>{agent.name}</strong>
|
|
320
|
+
<span
|
|
321
|
+
style={{
|
|
322
|
+
fontSize: '11px',
|
|
323
|
+
padding: '2px 6px',
|
|
324
|
+
borderRadius: '4px',
|
|
325
|
+
background: 'var(--panel-raised)',
|
|
326
|
+
color: 'var(--accent)'
|
|
327
|
+
}}
|
|
328
|
+
>
|
|
329
|
+
{agent.provider} ({agent.model})
|
|
330
|
+
</span>
|
|
331
|
+
</div>
|
|
332
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '14px' }}>
|
|
333
|
+
<label className="settings-toggle-switch">
|
|
334
|
+
<input
|
|
335
|
+
type="checkbox"
|
|
336
|
+
checked={agent.enabled}
|
|
337
|
+
onChange={(e) => handleToggleAgent(agent.id, e.target.checked)}
|
|
338
|
+
/>
|
|
339
|
+
<span className="settings-toggle-slider"></span>
|
|
340
|
+
</label>
|
|
341
|
+
<button className="btn btn-secondary" style={{ padding: '4px 8px', fontSize: '12px' }} onClick={() => handleEdit(agent)}>Edit</button>
|
|
342
|
+
{!['planner', 'coder', 'reviewer'].includes(agent.id) && (
|
|
343
|
+
<button className="btn btn-danger" style={{ padding: '4px 8px', fontSize: '12px' }} onClick={() => handleDeleteAgent(agent.id)}>Delete</button>
|
|
344
|
+
)}
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
<p style={{ margin: '4px 0 0 0', fontSize: '14px', color: 'var(--text-soft)', fontStyle: 'italic' }}>
|
|
348
|
+
"{agent.systemInstruction}"
|
|
349
|
+
</p>
|
|
350
|
+
</div>
|
|
351
|
+
))}
|
|
352
|
+
{agents.length === 0 && (
|
|
353
|
+
<div style={{ textAlign: 'center', padding: '32px', color: 'var(--text-soft)', fontStyle: 'italic' }}>
|
|
354
|
+
No agents configured. Click "+ Add Custom Agent" to get started.
|
|
355
|
+
</div>
|
|
356
|
+
)}
|
|
357
|
+
</div>
|
|
358
|
+
)}
|
|
359
|
+
</section>
|
|
360
|
+
</div>
|
|
361
|
+
)
|
|
362
|
+
}
|
|
@@ -168,28 +168,6 @@ export default function AutomationTab({
|
|
|
168
168
|
</div>
|
|
169
169
|
</section>
|
|
170
170
|
|
|
171
|
-
<section className="setting-section">
|
|
172
|
-
<div className="section-heading">
|
|
173
|
-
<div>
|
|
174
|
-
<p className="section-kicker">Code mode</p>
|
|
175
|
-
<h2 className="section-title">Agent Collaboration</h2>
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
<div className="toggle-row">
|
|
179
|
-
<div>
|
|
180
|
-
<label>Enable Multi-Agent Review</label>
|
|
181
|
-
<p className="hint">Allow a secondary model to review code written by the primary model.</p>
|
|
182
|
-
</div>
|
|
183
|
-
<label className="settings-toggle-switch">
|
|
184
|
-
<input
|
|
185
|
-
type="checkbox"
|
|
186
|
-
checked={config.enableAgentCollaboration}
|
|
187
|
-
onChange={(e) => updateField('enableAgentCollaboration', e.target.checked)}
|
|
188
|
-
/>
|
|
189
|
-
<span className="settings-toggle-slider"></span>
|
|
190
|
-
</label>
|
|
191
|
-
</div>
|
|
192
|
-
</section>
|
|
193
171
|
</div>
|
|
194
172
|
)
|
|
195
173
|
}
|