@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
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
listSavedPictures,
|
|
12
12
|
saveInteractionAgentActivity,
|
|
13
13
|
streamChatMessage,
|
|
14
|
+
cancelChatMessage,
|
|
14
15
|
submitToolApproval,
|
|
15
16
|
listen,
|
|
16
|
-
readClipboardImage as readTauriClipboardImage,
|
|
17
17
|
type AgentProgress,
|
|
18
18
|
type ChatResponse,
|
|
19
19
|
type ChatSession,
|
|
@@ -25,6 +25,13 @@ import ChatPanel from './ChatPanel'
|
|
|
25
25
|
import DashboardSidebar, { type DashboardView } from './DashboardSidebar'
|
|
26
26
|
import PicturesLibrary from './PicturesLibrary'
|
|
27
27
|
import ImageStudioPanel from './ImageStudioPanel'
|
|
28
|
+
import {
|
|
29
|
+
errorMessage,
|
|
30
|
+
readImage,
|
|
31
|
+
readDocument,
|
|
32
|
+
createTrimmedImagePreview,
|
|
33
|
+
applyThemeStyles,
|
|
34
|
+
} from '../../shared/utils/ui'
|
|
28
35
|
|
|
29
36
|
const DEFAULT_CONFIG = {
|
|
30
37
|
theme: 'dark',
|
|
@@ -66,142 +73,6 @@ const MOCK_WELCOME_INTERACTION = {
|
|
|
66
73
|
createdAt: new Date().toISOString(),
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
function errorMessage(reason: unknown) {
|
|
70
|
-
return reason instanceof Error ? reason.message : String(reason)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function readImage(file: File): Promise<string> {
|
|
74
|
-
return new Promise((resolve, reject) => {
|
|
75
|
-
const reader = new FileReader()
|
|
76
|
-
reader.onload = () => resolve(String(reader.result))
|
|
77
|
-
reader.onerror = () => reject(reader.error ?? new Error('Unable to read image'))
|
|
78
|
-
reader.readAsDataURL(file)
|
|
79
|
-
})
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function readDocument(file: File): Promise<string> {
|
|
83
|
-
return new Promise((resolve, reject) => {
|
|
84
|
-
const reader = new FileReader()
|
|
85
|
-
reader.onload = () => resolve(String(reader.result))
|
|
86
|
-
reader.onerror = () => reject(reader.error ?? new Error('Unable to read document'))
|
|
87
|
-
reader.readAsDataURL(file)
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async function createTrimmedImagePreview(dataUri: string): Promise<string> {
|
|
92
|
-
const image = await new Promise<HTMLImageElement>((resolve, reject) => {
|
|
93
|
-
const nextImage = new Image()
|
|
94
|
-
nextImage.onload = () => resolve(nextImage)
|
|
95
|
-
nextImage.onerror = () => reject(new Error('Unable to prepare image preview'))
|
|
96
|
-
nextImage.src = dataUri
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
const canvas = document.createElement('canvas')
|
|
100
|
-
canvas.width = image.naturalWidth || image.width
|
|
101
|
-
canvas.height = image.naturalHeight || image.height
|
|
102
|
-
const context = canvas.getContext('2d', { willReadFrequently: true })
|
|
103
|
-
if (!context || canvas.width === 0 || canvas.height === 0) return dataUri
|
|
104
|
-
|
|
105
|
-
context.drawImage(image, 0, 0)
|
|
106
|
-
const pixels = context.getImageData(0, 0, canvas.width, canvas.height)
|
|
107
|
-
let minX = canvas.width
|
|
108
|
-
let minY = canvas.height
|
|
109
|
-
let maxX = -1
|
|
110
|
-
let maxY = -1
|
|
111
|
-
|
|
112
|
-
for (let y = 0; y < canvas.height; y += 1) {
|
|
113
|
-
for (let x = 0; x < canvas.width; x += 1) {
|
|
114
|
-
const alpha = pixels.data[(y * canvas.width + x) * 4 + 3]
|
|
115
|
-
if (alpha > 12) {
|
|
116
|
-
minX = Math.min(minX, x)
|
|
117
|
-
minY = Math.min(minY, y)
|
|
118
|
-
maxX = Math.max(maxX, x)
|
|
119
|
-
maxY = Math.max(maxY, y)
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (maxX < minX || maxY < minY) return dataUri
|
|
125
|
-
|
|
126
|
-
const padding = 8
|
|
127
|
-
const sx = Math.max(0, minX - padding)
|
|
128
|
-
const sy = Math.max(0, minY - padding)
|
|
129
|
-
const sw = Math.min(canvas.width - sx, maxX - minX + 1 + padding * 2)
|
|
130
|
-
const sh = Math.min(canvas.height - sy, maxY - minY + 1 + padding * 2)
|
|
131
|
-
|
|
132
|
-
if (sw >= canvas.width * 0.92 && sh >= canvas.height * 0.92) return dataUri
|
|
133
|
-
|
|
134
|
-
const previewCanvas = document.createElement('canvas')
|
|
135
|
-
previewCanvas.width = sw
|
|
136
|
-
previewCanvas.height = sh
|
|
137
|
-
const previewContext = previewCanvas.getContext('2d')
|
|
138
|
-
if (!previewContext) return dataUri
|
|
139
|
-
previewContext.drawImage(canvas, sx, sy, sw, sh, 0, 0, sw, sh)
|
|
140
|
-
return previewCanvas.toDataURL('image/png')
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const lightenColor = (hex: string, amount: number) => {
|
|
144
|
-
const clean = hex.replace('#', '')
|
|
145
|
-
if (clean.length !== 6) return hex
|
|
146
|
-
const num = parseInt(clean, 16)
|
|
147
|
-
const r = Math.min(255, (num >> 16) + amount)
|
|
148
|
-
const g = Math.min(255, ((num >> 8) & 0x00FF) + amount)
|
|
149
|
-
const b = Math.min(255, (num & 0x0000FF) + amount)
|
|
150
|
-
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const hexToRgb = (hex: string) => {
|
|
154
|
-
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
|
155
|
-
return result ? {
|
|
156
|
-
r: parseInt(result[1], 16),
|
|
157
|
-
g: parseInt(result[2], 16),
|
|
158
|
-
b: parseInt(result[3], 16),
|
|
159
|
-
} : { r: 15, g: 23, b: 42 }
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const applyThemeStyles = (cfg: any) => {
|
|
163
|
-
const theme = cfg.theme || 'dark'
|
|
164
|
-
const accentColor = cfg.accentColor || '#4f83e6'
|
|
165
|
-
const systemTextColor = cfg.systemTextColor || '#f8fafc'
|
|
166
|
-
|
|
167
|
-
document.documentElement.setAttribute('data-theme', theme)
|
|
168
|
-
document.documentElement.style.setProperty('--accent', accentColor)
|
|
169
|
-
document.documentElement.style.setProperty('--accent-hover', lightenColor(accentColor, 20))
|
|
170
|
-
document.documentElement.style.setProperty('--text-main', systemTextColor)
|
|
171
|
-
document.documentElement.style.setProperty('--glass-blur', cfg.glassBlur || 'blur(16px)')
|
|
172
|
-
document.body.style.fontFamily = cfg.fontFamily || "'Outfit', sans-serif"
|
|
173
|
-
document.documentElement.style.fontSize = cfg.fontSize || '18px'
|
|
174
|
-
|
|
175
|
-
if (theme === 'custom') {
|
|
176
|
-
if (cfg.customBgStart && cfg.customBgEnd) {
|
|
177
|
-
document.documentElement.style.setProperty('--bg-color', cfg.customBgStart)
|
|
178
|
-
document.documentElement.style.setProperty('--bg-gradient', `linear-gradient(135deg, ${cfg.customBgStart} 0%, ${cfg.customBgEnd} 100%)`)
|
|
179
|
-
}
|
|
180
|
-
if (cfg.customPanelBg) {
|
|
181
|
-
const rgb = hexToRgb(cfg.customPanelBg)
|
|
182
|
-
document.documentElement.style.setProperty('--panel-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.75)`)
|
|
183
|
-
document.documentElement.style.setProperty('--panel-raised', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.82)`)
|
|
184
|
-
document.documentElement.style.setProperty('--panel-soft', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.46)`)
|
|
185
|
-
document.documentElement.style.setProperty('--chrome-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.88)`)
|
|
186
|
-
document.documentElement.style.setProperty('--surface-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.62)`)
|
|
187
|
-
document.documentElement.style.setProperty('--surface-strong', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.86)`)
|
|
188
|
-
document.documentElement.style.setProperty('--input-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.72)`)
|
|
189
|
-
}
|
|
190
|
-
return
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
;[
|
|
194
|
-
'--bg-color',
|
|
195
|
-
'--bg-gradient',
|
|
196
|
-
'--panel-bg',
|
|
197
|
-
'--panel-raised',
|
|
198
|
-
'--panel-soft',
|
|
199
|
-
'--chrome-bg',
|
|
200
|
-
'--surface-bg',
|
|
201
|
-
'--surface-strong',
|
|
202
|
-
'--input-bg',
|
|
203
|
-
].forEach((name) => document.documentElement.style.removeProperty(name))
|
|
204
|
-
}
|
|
205
76
|
|
|
206
77
|
export default function MintDashboard() {
|
|
207
78
|
const [view, setView] = useState<DashboardView>('chat')
|
|
@@ -415,6 +286,20 @@ export default function MintDashboard() {
|
|
|
415
286
|
}
|
|
416
287
|
}
|
|
417
288
|
|
|
289
|
+
async function handleCancelMessage() {
|
|
290
|
+
if (!sending || !streamingConversationId) return
|
|
291
|
+
try {
|
|
292
|
+
await cancelChatMessage(streamingConversationId)
|
|
293
|
+
} catch (e) {
|
|
294
|
+
console.error("Failed to cancel message stream:", e)
|
|
295
|
+
} finally {
|
|
296
|
+
setSending(false)
|
|
297
|
+
setStreamingConversationId(null)
|
|
298
|
+
setSendingMessage('')
|
|
299
|
+
setSendingImageCount(0)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
418
303
|
async function sendPrompt(
|
|
419
304
|
promptText: string,
|
|
420
305
|
options: {
|
|
@@ -587,37 +472,6 @@ export default function MintDashboard() {
|
|
|
587
472
|
return true
|
|
588
473
|
}
|
|
589
474
|
|
|
590
|
-
async function readClipboardImage() {
|
|
591
|
-
try {
|
|
592
|
-
const dataUri = await readTauriClipboardImage()
|
|
593
|
-
if (dataUri) {
|
|
594
|
-
const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
|
|
595
|
-
setImageAttachments((current) => [...current, { dataUri, previewDataUri, name: 'Pasted image' }])
|
|
596
|
-
return true
|
|
597
|
-
}
|
|
598
|
-
} catch (err) {
|
|
599
|
-
console.warn('Tauri clipboard fallback error:', err)
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
try {
|
|
603
|
-
if (!navigator.clipboard?.read) return false
|
|
604
|
-
const items = await navigator.clipboard.read()
|
|
605
|
-
for (const item of items) {
|
|
606
|
-
const imageType = item.types.find((type) => type.startsWith('image/'))
|
|
607
|
-
if (!imageType) continue
|
|
608
|
-
const blob = await item.getType(imageType)
|
|
609
|
-
const file = new File([blob], 'Pasted image', { type: imageType })
|
|
610
|
-
const dataUri = await readImage(file)
|
|
611
|
-
const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
|
|
612
|
-
setImageAttachments((current) => [...current, { dataUri, previewDataUri, name: 'Pasted image' }])
|
|
613
|
-
return true
|
|
614
|
-
}
|
|
615
|
-
return false
|
|
616
|
-
} catch {
|
|
617
|
-
// Some environments expose pasted images only through ClipboardEvent.
|
|
618
|
-
return false
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
475
|
|
|
622
476
|
async function selectDocument(event: ChangeEvent<HTMLInputElement>) {
|
|
623
477
|
const file = event.target.files?.[0]
|
|
@@ -886,7 +740,6 @@ export default function MintDashboard() {
|
|
|
886
740
|
onSelectImage={selectImage}
|
|
887
741
|
onSelectDocument={selectDocument}
|
|
888
742
|
onPasteImage={pasteImage}
|
|
889
|
-
onReadClipboardImage={readClipboardImage}
|
|
890
743
|
onSetMessage={setMessage}
|
|
891
744
|
onSendVoiceMessage={sendVoiceMessage}
|
|
892
745
|
onRemoveImage={(idx: number) => {
|
|
@@ -901,6 +754,7 @@ export default function MintDashboard() {
|
|
|
901
754
|
settingsConfig={settingsConfig}
|
|
902
755
|
onSetModel={changeModel}
|
|
903
756
|
onApproval={handleApproval}
|
|
757
|
+
onCancelMessage={handleCancelMessage}
|
|
904
758
|
onToggleMobileSidebar={() => setMobileSidebarOpen(!mobileSidebarOpen)}
|
|
905
759
|
/>
|
|
906
760
|
</main>
|
|
@@ -2,10 +2,6 @@ import { useEffect, useMemo, useState } from 'react'
|
|
|
2
2
|
import { type PictureEntry, convertFileSrc } from '../tauri'
|
|
3
3
|
import type { DashboardView } from './DashboardSidebar'
|
|
4
4
|
|
|
5
|
-
const INITIAL_VISIBLE_PICTURES = 18
|
|
6
|
-
const PICTURE_RENDER_BATCH_SIZE = 18
|
|
7
|
-
const PICTURE_RENDER_BATCH_DELAY_MS = 80
|
|
8
|
-
|
|
9
5
|
interface PicturesLibraryProps {
|
|
10
6
|
view: DashboardView
|
|
11
7
|
pictures: PictureEntry[]
|
|
@@ -14,31 +10,16 @@ interface PicturesLibraryProps {
|
|
|
14
10
|
}
|
|
15
11
|
|
|
16
12
|
export default function PicturesLibrary({ view, pictures, onSetView, onRefreshPictures }: PicturesLibraryProps) {
|
|
17
|
-
const [visibleCount, setVisibleCount] = useState(
|
|
13
|
+
const [visibleCount, setVisibleCount] = useState(24)
|
|
18
14
|
const visiblePictures = useMemo(
|
|
19
15
|
() => pictures.slice(0, visibleCount),
|
|
20
16
|
[pictures, visibleCount],
|
|
21
17
|
)
|
|
22
18
|
|
|
23
19
|
useEffect(() => {
|
|
24
|
-
|
|
25
|
-
setVisibleCount(INITIAL_VISIBLE_PICTURES)
|
|
26
|
-
return
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
setVisibleCount(INITIAL_VISIBLE_PICTURES)
|
|
20
|
+
setVisibleCount(24)
|
|
30
21
|
}, [view, pictures])
|
|
31
22
|
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
if (view !== 'pictures' || visibleCount >= pictures.length) return
|
|
34
|
-
|
|
35
|
-
const timer = window.setTimeout(() => {
|
|
36
|
-
setVisibleCount((current) => Math.min(current + PICTURE_RENDER_BATCH_SIZE, pictures.length))
|
|
37
|
-
}, PICTURE_RENDER_BATCH_DELAY_MS)
|
|
38
|
-
|
|
39
|
-
return () => window.clearTimeout(timer)
|
|
40
|
-
}, [view, pictures.length, visibleCount])
|
|
41
|
-
|
|
42
23
|
return (
|
|
43
24
|
<section className={`pictures-library ${view === 'pictures' ? 'is-visible' : ''}`} aria-hidden={view !== 'pictures'}>
|
|
44
25
|
<header className="pictures-header">
|
|
@@ -61,14 +42,27 @@ export default function PicturesLibrary({ view, pictures, onSetView, onRefreshPi
|
|
|
61
42
|
<span>Images appear here after a message with an attachment is sent successfully.</span>
|
|
62
43
|
</div>
|
|
63
44
|
) : (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
45
|
+
<>
|
|
46
|
+
<div className="pictures-grid">
|
|
47
|
+
{visiblePictures.map((picture, index) => (
|
|
48
|
+
<article className="picture-card" key={picture.id}>
|
|
49
|
+
<img src={convertFileSrc(picture.thumbnailPath || picture.thumbnailUrl || picture.path)} alt={picture.message || picture.filename} loading={index < 6 ? 'eager' : 'lazy'} decoding="async" />
|
|
50
|
+
<div className="picture-card-meta"><span>{picture.message || picture.filename}</span></div>
|
|
51
|
+
</article>
|
|
52
|
+
))}
|
|
53
|
+
</div>
|
|
54
|
+
{visibleCount < pictures.length && (
|
|
55
|
+
<div className="pictures-load-more-container">
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
className="pictures-load-more-btn"
|
|
59
|
+
onClick={() => setVisibleCount((prev) => prev + 24)}
|
|
60
|
+
>
|
|
61
|
+
Load More ({pictures.length - visibleCount} remaining)
|
|
62
|
+
</button>
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
</>
|
|
72
66
|
)}
|
|
73
67
|
</section>
|
|
74
68
|
)
|
|
@@ -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
|
+
}
|