@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
@@ -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]
@@ -752,6 +606,11 @@ export default function MintDashboard() {
752
606
  else if (provider === 'huggingface') providerName = 'HF'
753
607
  else if (provider === 'local_openai') providerName = 'Local'
754
608
  else if (provider === 'ollama') providerName = 'Ollama'
609
+ else if (provider.startsWith('custom:')) {
610
+ const id = provider.replace(/^custom:/, '')
611
+ const cp = (settingsConfig?.customProviders ?? []).find((p: any) => p.id === id)
612
+ providerName = cp?.displayName || id
613
+ }
755
614
 
756
615
  if (providerName && providerName === provider) {
757
616
  providerName = providerName.charAt(0).toUpperCase() + providerName.slice(1)
@@ -769,7 +628,14 @@ export default function MintDashboard() {
769
628
  case 'huggingface': return config.hfModel || 'meta-llama/Meta-Llama-3-8B-Instruct'
770
629
  case 'local_openai': return config.localModelName || 'llama3'
771
630
  case 'ollama': return config.ollamaModel || 'llama3:latest'
772
- default: return ''
631
+ default: {
632
+ if (provider.startsWith('custom:')) {
633
+ const id = provider.replace(/^custom:/, '')
634
+ const cp = (config.customProviders ?? []).find((p: any) => p.id === id)
635
+ return (config.customModelSelections ?? {})[id] ?? cp?.models[0]?.modelId ?? ''
636
+ }
637
+ return ''
638
+ }
773
639
  }
774
640
  }
775
641
 
@@ -811,6 +677,12 @@ export default function MintDashboard() {
811
677
  config.localModelName = modelName
812
678
  } else if (provider === 'ollama') {
813
679
  config.ollamaModel = modelName
680
+ } else if (provider.startsWith('custom:')) {
681
+ const id = provider.replace(/^custom:/, '')
682
+ config.customModelSelections = {
683
+ ...(config.customModelSelections ?? {}),
684
+ [id]: modelName
685
+ }
814
686
  }
815
687
  await window.settingsApi.saveSettings(config)
816
688
  setSettingsConfig(config)
@@ -886,7 +758,6 @@ export default function MintDashboard() {
886
758
  onSelectImage={selectImage}
887
759
  onSelectDocument={selectDocument}
888
760
  onPasteImage={pasteImage}
889
- onReadClipboardImage={readClipboardImage}
890
761
  onSetMessage={setMessage}
891
762
  onSendVoiceMessage={sendVoiceMessage}
892
763
  onRemoveImage={(idx: number) => {
@@ -901,6 +772,7 @@ export default function MintDashboard() {
901
772
  settingsConfig={settingsConfig}
902
773
  onSetModel={changeModel}
903
774
  onApproval={handleApproval}
775
+ onCancelMessage={handleCancelMessage}
904
776
  onToggleMobileSidebar={() => setMobileSidebarOpen(!mobileSidebarOpen)}
905
777
  />
906
778
  </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(INITIAL_VISIBLE_PICTURES)
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
- if (view !== 'pictures') {
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
- <div className="pictures-grid">
65
- {visiblePictures.map((picture, index) => (
66
- <article className="picture-card" key={picture.id}>
67
- <img src={convertFileSrc(picture.thumbnailPath || picture.thumbnailUrl || picture.path)} alt={picture.message || picture.filename} loading={index < 6 ? 'eager' : 'lazy'} decoding="async" />
68
- <div className="picture-card-meta"><span>{picture.message || picture.filename}</span></div>
69
- </article>
70
- ))}
71
- </div>
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
  )