@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
@@ -12,9 +12,10 @@ import {
12
12
  selectWorkspaceDirectory,
13
13
  saveInteractionAgentActivity,
14
14
  streamChatMessage,
15
+ cancelChatMessage,
15
16
  submitToolApproval,
16
17
  listen,
17
- readClipboardImage as readTauriClipboardImage,
18
+ readClipboardImage,
18
19
  type AgentProgress,
19
20
  type ChatResponse,
20
21
  type ChatSession,
@@ -22,6 +23,7 @@ import {
22
23
  type PictureEntry,
23
24
  type RuntimeStatus,
24
25
  } from '../tauri'
26
+
25
27
  import ChatPanel from './ChatPanel'
26
28
  import DashboardSidebar, { type DashboardView } from './DashboardSidebar'
27
29
  import ImageStudioPanel from './ImageStudioPanel'
@@ -30,6 +32,13 @@ import type { ModelInteraction } from './ModelPanel'
30
32
  import PicturesLibrary from './PicturesLibrary'
31
33
  import WorkspacePanel from './WorkspacePanel'
32
34
  import WorkflowBuilderPanel from './WorkflowBuilderPanel'
35
+ import {
36
+ errorMessage,
37
+ readImage,
38
+ readDocument,
39
+ createTrimmedImagePreview,
40
+ applyThemeStyles,
41
+ } from '../../shared/utils/ui'
33
42
 
34
43
  const EXPRESSIONS = [
35
44
  "Default",
@@ -88,142 +97,6 @@ const MOCK_WELCOME_INTERACTION = {
88
97
  createdAt: new Date().toISOString(),
89
98
  }
90
99
 
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
100
 
228
101
  export default function MintDashboard() {
229
102
  const [view, setView] = useState<DashboardView>('chat')
@@ -267,6 +140,7 @@ export default function MintDashboard() {
267
140
  const [conversationId, setConversationId] = useState(activeConversationId)
268
141
  const [chatSessions, setChatSessions] = useState<ChatSession[]>([])
269
142
  const chatEnd = useRef<HTMLDivElement | null>(null)
143
+ const lastNativePasteTimeRef = useRef(0)
270
144
  const startupReady = (dashboardDataReady && modelReady) || startupTimedOut
271
145
  const [proactiveSuggestion, setProactiveSuggestion] = useState<any>(null)
272
146
 
@@ -522,6 +396,20 @@ export default function MintDashboard() {
522
396
  }
523
397
  }
524
398
 
399
+ async function handleCancelMessage() {
400
+ if (!sending || !streamingConversationId) return
401
+ try {
402
+ await cancelChatMessage(streamingConversationId)
403
+ } catch (e) {
404
+ console.error("Failed to cancel message stream:", e)
405
+ } finally {
406
+ setSending(false)
407
+ setStreamingConversationId(null)
408
+ setSendingMessage('')
409
+ setSendingImageCount(0)
410
+ }
411
+ }
412
+
525
413
  async function sendPrompt(
526
414
  promptText: string,
527
415
  options: {
@@ -690,7 +578,28 @@ export default function MintDashboard() {
690
578
  }
691
579
  }
692
580
 
693
- if (!file) return false
581
+ if (!file) {
582
+ // Fallback for Tauri desktop app where WebKitGTK/WebView clipboard paste events do not provide file handles
583
+ const now = Date.now()
584
+ if (now - lastNativePasteTimeRef.current > 100) {
585
+ lastNativePasteTimeRef.current = now
586
+ readClipboardImage().then((dataUri) => {
587
+ if (dataUri) {
588
+ const name = 'Pasted image'
589
+ createTrimmedImagePreview(dataUri)
590
+ .catch(() => dataUri)
591
+ .then((previewDataUri) => {
592
+ setImageAttachments((current) => [...current, { dataUri, previewDataUri, name }])
593
+ })
594
+ }
595
+ }).catch((err) => {
596
+ console.warn('Failed to read clipboard image via Tauri API:', err)
597
+ })
598
+ }
599
+ return false
600
+ }
601
+
602
+
694
603
 
695
604
  readImage(file)
696
605
  .then((dataUri) => {
@@ -705,37 +614,7 @@ export default function MintDashboard() {
705
614
  return true
706
615
  }
707
616
 
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
617
 
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
618
 
740
619
  async function selectDocument(event: ChangeEvent<HTMLInputElement>) {
741
620
  const file = event.target.files?.[0]
@@ -882,6 +761,11 @@ export default function MintDashboard() {
882
761
  else if (provider === 'huggingface') providerName = 'HF'
883
762
  else if (provider === 'local_openai') providerName = 'Local'
884
763
  else if (provider === 'ollama') providerName = 'Ollama'
764
+ else if (provider.startsWith('custom:')) {
765
+ const id = provider.replace(/^custom:/, '')
766
+ const cp = (settingsConfig?.customProviders ?? []).find((p: any) => p.id === id)
767
+ providerName = cp?.displayName || id
768
+ }
885
769
 
886
770
  if (providerName && providerName === provider) {
887
771
  providerName = providerName.charAt(0).toUpperCase() + providerName.slice(1)
@@ -899,7 +783,14 @@ export default function MintDashboard() {
899
783
  case 'huggingface': return config.hfModel || 'meta-llama/Meta-Llama-3-8B-Instruct'
900
784
  case 'local_openai': return config.localModelName || 'llama3'
901
785
  case 'ollama': return config.ollamaModel || 'llama3:latest'
902
- default: return ''
786
+ default: {
787
+ if (provider.startsWith('custom:')) {
788
+ const id = provider.replace(/^custom:/, '')
789
+ const cp = (config.customProviders ?? []).find((p: any) => p.id === id)
790
+ return (config.customModelSelections ?? {})[id] ?? cp?.models[0]?.modelId ?? ''
791
+ }
792
+ return ''
793
+ }
903
794
  }
904
795
  }
905
796
 
@@ -941,6 +832,12 @@ export default function MintDashboard() {
941
832
  config.localModelName = modelName
942
833
  } else if (provider === 'ollama') {
943
834
  config.ollamaModel = modelName
835
+ } else if (provider.startsWith('custom:')) {
836
+ const id = provider.replace(/^custom:/, '')
837
+ config.customModelSelections = {
838
+ ...(config.customModelSelections ?? {}),
839
+ [id]: modelName
840
+ }
944
841
  }
945
842
  await window.settingsApi.saveSettings(config)
946
843
  setSettingsConfig(config)
@@ -1110,7 +1007,6 @@ export default function MintDashboard() {
1110
1007
  onSelectImage={selectImage}
1111
1008
  onSelectDocument={selectDocument}
1112
1009
  onPasteImage={pasteImage}
1113
- onReadClipboardImage={readClipboardImage}
1114
1010
  onSetMessage={setMessage}
1115
1011
  onSendVoiceMessage={sendVoiceMessage}
1116
1012
  onRemoveImage={(idx: number) => {
@@ -1126,6 +1022,7 @@ export default function MintDashboard() {
1126
1022
  settingsConfig={settingsConfig}
1127
1023
  onSetModel={changeModel}
1128
1024
  onApproval={handleApproval}
1025
+ onCancelMessage={handleCancelMessage}
1129
1026
  />
1130
1027
  </main>
1131
1028
  <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
- <div className="pictures-grid">
38
- {pictures.map((picture, index) => (
39
- <article className="picture-card" key={picture.id}>
40
- <img src={convertFileSrc(picture.thumbnailPath || picture.thumbnailUrl || picture.path)} alt={picture.message || picture.filename} loading={index < 6 ? 'eager' : 'lazy'} decoding="async" />
41
- <div className="picture-card-meta"><span>{picture.message || picture.filename}</span></div>
42
- </article>
43
- ))}
44
- </div>
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
  )