@pheem49/mint 1.5.5 → 1.6.1

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 (222) hide show
  1. package/.codex +0 -0
  2. package/.github/FUNDING.yml +2 -0
  3. package/.github/workflows/ci.yml +45 -0
  4. package/.github/workflows/release.yml +79 -0
  5. package/Cargo.lock +5792 -0
  6. package/Cargo.toml +32 -0
  7. package/README.md +387 -353
  8. package/assets/icon.png +0 -0
  9. package/bin/mint +0 -0
  10. package/crates/mint-cli/Cargo.toml +23 -0
  11. package/crates/mint-cli/src/agent.rs +851 -0
  12. package/crates/mint-cli/src/gmail.rs +216 -0
  13. package/crates/mint-cli/src/image.rs +142 -0
  14. package/crates/mint-cli/src/main.rs +2837 -0
  15. package/crates/mint-cli/src/mcp.rs +63 -0
  16. package/crates/mint-cli/src/onboard.rs +1149 -0
  17. package/crates/mint-cli/src/setup.rs +390 -0
  18. package/crates/mint-cli/src/skills.rs +8 -0
  19. package/crates/mint-cli/src/updater.rs +279 -0
  20. package/crates/mint-core/Cargo.toml +22 -0
  21. package/crates/mint-core/src/agent_loop.rs +94 -0
  22. package/crates/mint-core/src/api_server.rs +991 -0
  23. package/crates/mint-core/src/channels.rs +248 -0
  24. package/crates/mint-core/src/chat.rs +895 -0
  25. package/crates/mint-core/src/code_tools.rs +729 -0
  26. package/crates/mint-core/src/config.rs +368 -0
  27. package/crates/mint-core/src/files.rs +159 -0
  28. package/crates/mint-core/src/knowledge.rs +541 -0
  29. package/crates/mint-core/src/lib.rs +84 -0
  30. package/crates/mint-core/src/mcp.rs +273 -0
  31. package/crates/mint-core/src/memory.rs +673 -0
  32. package/crates/mint-core/src/orchestration.rs +2157 -0
  33. package/crates/mint-core/src/pictures.rs +314 -0
  34. package/crates/mint-core/src/plugins.rs +727 -0
  35. package/crates/mint-core/src/safety.rs +416 -0
  36. package/crates/mint-core/src/semantic.rs +254 -0
  37. package/crates/mint-core/src/shell.rs +317 -0
  38. package/crates/mint-core/src/skills.rs +71 -0
  39. package/crates/mint-core/src/symbols.rs +157 -0
  40. package/crates/mint-core/src/tasks.rs +308 -0
  41. package/crates/mint-core/src/tts.rs +92 -0
  42. package/crates/mint-core/src/weather.rs +93 -0
  43. package/crates/mint-core/src/web_search.rs +200 -0
  44. package/crates/mint-core/src/workflows.rs +81 -0
  45. package/crates/mint-core/tests/mcp_stdio.rs +45 -0
  46. package/crates/mint-core/tests/memory_persistence.rs +172 -0
  47. package/crates/mint-core/tests/pictures_storage.rs +14 -0
  48. package/crates/mint-core/tests/task_lifecycle.rs +87 -0
  49. package/package.json +35 -99
  50. package/src/bin/index.js +16 -0
  51. package/src/renderer/index-web.html +17 -0
  52. package/src/renderer/index.html +17 -0
  53. package/src/renderer/public/Live2DCubismCore.js +9 -0
  54. package/src/renderer/public/assets/icon.png +0 -0
  55. package/src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.model3.json +36 -0
  56. package/src/renderer/src/App.tsx +33 -0
  57. package/src/renderer/src/calculator.ts +47 -0
  58. package/src/renderer/src/components/ChatPanel.tsx +1598 -0
  59. package/src/renderer/src/components/DashboardSidebar.tsx +358 -0
  60. package/src/renderer/src/components/Live2DStage.tsx +374 -0
  61. package/src/renderer/src/components/MintDashboard.tsx +950 -0
  62. package/src/renderer/src/components/ModelPanel.tsx +154 -0
  63. package/src/renderer/src/components/PicturesLibrary.tsx +46 -0
  64. package/src/renderer/src/components/ProactiveGlow.tsx +19 -0
  65. package/src/renderer/src/components/ScreenPicker.tsx +579 -0
  66. package/src/renderer/src/components/SettingsWindow.tsx +1467 -0
  67. package/src/renderer/src/components/SpotlightWindow.tsx +280 -0
  68. package/src/renderer/src/components/WidgetWindow.tsx +36 -0
  69. package/src/renderer/src/components/WorkspacePanel.tsx +268 -0
  70. package/src/{UI → renderer/src/css}/settings.css +69 -16
  71. package/src/renderer/src/css/spotlight.css +113 -0
  72. package/src/renderer/src/css/styles.css +3722 -0
  73. package/src/renderer/src/css/widget.css +185 -0
  74. package/src/renderer/src/env.d.ts +116 -0
  75. package/src/renderer/src/index.css +379 -0
  76. package/src/renderer/src/main.tsx +13 -0
  77. package/src/renderer/src/tauri.ts +996 -0
  78. package/src/renderer/src-web/App.tsx +25 -0
  79. package/src/renderer/src-web/calculator.ts +47 -0
  80. package/src/renderer/src-web/components/ChatPanel.tsx +1662 -0
  81. package/src/renderer/src-web/components/DashboardSidebar.tsx +242 -0
  82. package/src/renderer/src-web/components/MintDashboard.tsx +763 -0
  83. package/src/renderer/src-web/components/PicturesLibrary.tsx +73 -0
  84. package/src/renderer/src-web/components/SettingsWindow.tsx +1500 -0
  85. package/src/renderer/src-web/css/settings.css +1100 -0
  86. package/src/{UI → renderer/src-web/css}/spotlight.css +4 -4
  87. package/src/{UI → renderer/src-web/css}/styles.css +1055 -159
  88. package/src/{UI → renderer/src-web/css}/widget.css +2 -2
  89. package/src/renderer/src-web/env.d.ts +107 -0
  90. package/src/renderer/src-web/index.css +379 -0
  91. package/src/renderer/src-web/main.tsx +13 -0
  92. package/src/renderer/src-web/tauri.ts +983 -0
  93. package/tsconfig.json +30 -0
  94. package/vite.config.ts +33 -0
  95. package/vite.config.web.ts +51 -0
  96. package/GUIDE_TH.md +0 -125
  97. package/assets/Agent_Mint.png +0 -0
  98. package/assets/CLI_Screen.png +0 -0
  99. package/assets/Settings.png +0 -0
  100. package/benchmark_ai.js +0 -71
  101. package/install.ps1 +0 -64
  102. package/install.sh +0 -54
  103. package/main.js +0 -139
  104. package/mint-cli-logic.js +0 -3
  105. package/mint-cli.js +0 -410
  106. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +0 -47
  107. package/models/Shiroko_Model/Shiroko//342/232/241/351/253/230/344/272/256/342/232/241/344/275/277/347/224/250/346/225/231/347/250/213/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.txt +0 -23
  108. package/preload-picker.js +0 -11
  109. package/preload-settings.js +0 -11
  110. package/preload.js +0 -41
  111. package/scripts/install_linux_desktop_entry.js +0 -48
  112. package/src/AI_Brain/Gemini_API.js +0 -813
  113. package/src/AI_Brain/agent_orchestrator.js +0 -73
  114. package/src/AI_Brain/autonomous_brain.js +0 -179
  115. package/src/AI_Brain/behavior_memory.js +0 -135
  116. package/src/AI_Brain/headless_agent.js +0 -143
  117. package/src/AI_Brain/knowledge_base.js +0 -349
  118. package/src/AI_Brain/memory_store.js +0 -662
  119. package/src/AI_Brain/proactive_engine.js +0 -172
  120. package/src/AI_Brain/provider_adapter.js +0 -365
  121. package/src/Automation_Layer/browser_automation.js +0 -149
  122. package/src/Automation_Layer/file_operations.js +0 -286
  123. package/src/Automation_Layer/open_app.js +0 -85
  124. package/src/Automation_Layer/open_website.js +0 -38
  125. package/src/CLI/approval_handler.js +0 -47
  126. package/src/CLI/chat_router.js +0 -247
  127. package/src/CLI/chat_ui.js +0 -1159
  128. package/src/CLI/cli_colors.js +0 -115
  129. package/src/CLI/cli_formatters.js +0 -94
  130. package/src/CLI/code_agent.js +0 -1667
  131. package/src/CLI/code_session_memory.js +0 -62
  132. package/src/CLI/gmail_auth.js +0 -210
  133. package/src/CLI/image_input.js +0 -90
  134. package/src/CLI/intent_detectors.js +0 -181
  135. package/src/CLI/interactive_chat.js +0 -658
  136. package/src/CLI/list_features.js +0 -64
  137. package/src/CLI/onboarding.js +0 -416
  138. package/src/CLI/repo_summarizer.js +0 -282
  139. package/src/CLI/semantic_code_search.js +0 -312
  140. package/src/CLI/skill_manager.js +0 -41
  141. package/src/CLI/slash_command_handler.js +0 -418
  142. package/src/CLI/symbol_indexer.js +0 -231
  143. package/src/CLI/updater.js +0 -230
  144. package/src/CLI/workspace_manager.js +0 -90
  145. package/src/Channels/brave_search_bridge.js +0 -35
  146. package/src/Channels/discord_bridge.js +0 -66
  147. package/src/Channels/google_search_bridge.js +0 -38
  148. package/src/Channels/line_bridge.js +0 -60
  149. package/src/Channels/slack_bridge.js +0 -48
  150. package/src/Channels/telegram_bridge.js +0 -41
  151. package/src/Channels/whatsapp_bridge.js +0 -57
  152. package/src/Command_Parser/parser.js +0 -45
  153. package/src/Plugins/dev_tools.js +0 -41
  154. package/src/Plugins/discord.js +0 -20
  155. package/src/Plugins/docker.js +0 -47
  156. package/src/Plugins/gmail.js +0 -251
  157. package/src/Plugins/google_calendar.js +0 -252
  158. package/src/Plugins/mcp_manager.js +0 -95
  159. package/src/Plugins/notion.js +0 -256
  160. package/src/Plugins/obsidian.js +0 -54
  161. package/src/Plugins/plugin_manager.js +0 -81
  162. package/src/Plugins/spotify.js +0 -173
  163. package/src/Plugins/system_metrics.js +0 -31
  164. package/src/Plugins/system_monitor.js +0 -72
  165. package/src/System/action_executor.js +0 -178
  166. package/src/System/bridge_manager.js +0 -76
  167. package/src/System/chat_history_manager.js +0 -83
  168. package/src/System/config_manager.js +0 -194
  169. package/src/System/custom_workflows.js +0 -163
  170. package/src/System/daemon_manager.js +0 -67
  171. package/src/System/google_tts_urls.js +0 -51
  172. package/src/System/granular_automation.js +0 -157
  173. package/src/System/ipc_handlers.js +0 -332
  174. package/src/System/notifications.js +0 -23
  175. package/src/System/optional_require.js +0 -23
  176. package/src/System/picture_store.js +0 -109
  177. package/src/System/proactive_loop.js +0 -153
  178. package/src/System/safety_manager.js +0 -273
  179. package/src/System/sandbox_runner.js +0 -182
  180. package/src/System/screen_capture.js +0 -175
  181. package/src/System/smart_context.js +0 -227
  182. package/src/System/system_automation.js +0 -162
  183. package/src/System/system_events.js +0 -79
  184. package/src/System/system_info.js +0 -125
  185. package/src/System/task_manager.js +0 -222
  186. package/src/System/tool_registry.js +0 -293
  187. package/src/System/window_manager.js +0 -220
  188. package/src/UI/floating.css +0 -80
  189. package/src/UI/floating.html +0 -17
  190. package/src/UI/floating.js +0 -67
  191. package/src/UI/live2d_manager.js +0 -600
  192. package/src/UI/preload-floating.js +0 -7
  193. package/src/UI/preload-spotlight.js +0 -11
  194. package/src/UI/preload-widget.js +0 -5
  195. package/src/UI/proactive-glow.html +0 -42
  196. package/src/UI/renderer.js +0 -2127
  197. package/src/UI/screenPicker.html +0 -214
  198. package/src/UI/screenPicker.js +0 -262
  199. package/src/UI/settings.html +0 -577
  200. package/src/UI/settings.js +0 -770
  201. package/src/UI/spotlight.html +0 -23
  202. package/src/UI/spotlight.js +0 -185
  203. package/src/UI/widget.html +0 -29
  204. package/src/UI/widget.js +0 -10
  205. /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
  206. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/apron.exp3.json} +0 -0
  207. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/catfilter.exp3.json} +0 -0
  208. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/click.exp3.json} +0 -0
  209. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazed.exp3.json} +0 -0
  210. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253/347/234/274/347/217/240/346/221/207/346/231/203.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazedeyes.exp3.json} +0 -0
  211. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/glasses.exp3.json} +0 -0
  212. /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +0 -0
  213. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/pen.exp3.json} +0 -0
  214. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/photo.exp3.json} +0 -0
  215. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_00.png} +0 -0
  216. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_01.png} +0 -0
  217. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_02.png} +0 -0
  218. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_03.png} +0 -0
  219. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.cdi3.json} +0 -0
  220. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.moc3} +0 -0
  221. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.physics3.json} +0 -0
  222. /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.vtube.json} +0 -0
@@ -0,0 +1,983 @@
1
+ export interface RuntimeStatus {
2
+ backend: string
3
+ configPath: string
4
+ activeProvider: string
5
+ availableProviders: string[]
6
+ integrations: Record<string, unknown>
7
+ localIp?: string
8
+ }
9
+
10
+ export interface ChatResponse {
11
+ provider: string
12
+ model: string
13
+ text: string
14
+ fallbackProvider?: string | null
15
+ }
16
+
17
+ export interface TtsUrl {
18
+ shortText: string
19
+ url: string
20
+ }
21
+
22
+ export interface DocumentAttachment {
23
+ filename: string
24
+ dataUri: string
25
+ }
26
+
27
+ export type AgentProgress =
28
+ | { type: 'Thinking'; data: { elapsed_secs: number } }
29
+ | { type: 'Thought'; data: { thought: string } }
30
+ | { type: 'ToolStart'; data: { action: string; input: Record<string, unknown> } }
31
+ | { type: 'ToolEnd'; data: { action: string; input: Record<string, unknown>; result: string } }
32
+
33
+ type DesktopStreamEvent =
34
+ | { type: 'chunk'; chunk: string }
35
+ | { type: 'progress'; progress: AgentProgress }
36
+
37
+ export interface InteractionMemory {
38
+ id: number
39
+ chatId: string
40
+ userText: string
41
+ aiText: string
42
+ provider: string
43
+ model: string
44
+ fallbackProvider?: string | null
45
+ createdAt: string
46
+ }
47
+
48
+ export interface ChatSession {
49
+ id: string
50
+ title: string
51
+ kind: string
52
+ createdAt: string
53
+ updatedAt: string
54
+ }
55
+
56
+ export interface PictureEntry {
57
+ id: string
58
+ filename: string
59
+ path: string
60
+ mimeType: string
61
+ createdAt: string
62
+ source: string
63
+ message: string
64
+ thumbnailPath?: string
65
+ url?: string
66
+ thumbnailUrl?: string
67
+ }
68
+
69
+ export interface CodeEdit {
70
+ path: string
71
+ content: string
72
+ }
73
+
74
+ export interface CodeEditProposal {
75
+ approvalRequired: boolean
76
+ approvalToken: string
77
+ edits: Array<{ path: string; existed: boolean; diff: string }>
78
+ }
79
+
80
+ export const isTauriRuntime = () => (
81
+ typeof window !== 'undefined' && Boolean((window as any).__TAURI_INTERNALS__)
82
+ )
83
+
84
+ export const getLocalApiBase = () => {
85
+ const host = typeof window !== 'undefined' ? window.location.hostname : 'localhost';
86
+ return `http://${host}:3000/api`;
87
+ };
88
+
89
+ const getApiBase = getLocalApiBase
90
+
91
+ export async function getRuntimeStatus(): Promise<RuntimeStatus> {
92
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
93
+ const API_BASE = getApiBase();
94
+ try {
95
+ const res = await fetch(`${API_BASE}/status`);
96
+ return await res.json();
97
+ } catch (e) {
98
+ console.error("Failed to fetch runtime status from local server:", e);
99
+ return {
100
+ backend: 'browser-fallback',
101
+ configPath: '',
102
+ activeProvider: '',
103
+ availableProviders: [],
104
+ integrations: {}
105
+ };
106
+ }
107
+ }
108
+ const { invoke } = await import('@tauri-apps/api/core')
109
+ return invoke<RuntimeStatus>('get_runtime_status')
110
+ }
111
+
112
+ export async function sendChatMessage(
113
+ message: string,
114
+ imageDataUri?: string | null,
115
+ audioDataUri?: string | null,
116
+ documentAttachment?: DocumentAttachment | null,
117
+ workspacePath?: string | null,
118
+ chatId?: string | null,
119
+ ): Promise<ChatResponse> {
120
+ const outgoingMessage = withImagePlaceholder(message, imageDataUri)
121
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
122
+ const API_BASE = getApiBase();
123
+ try {
124
+ const res = await fetch(`${API_BASE}/chat`, {
125
+ method: 'POST',
126
+ headers: { 'Content-Type': 'application/json' },
127
+ body: JSON.stringify({ message: outgoingMessage, systemInstruction: '', chatId, imageDataUri, audioDataUri, documentAttachment })
128
+ });
129
+ const data = await res.json().catch(() => null);
130
+ if (!res.ok) {
131
+ return {
132
+ provider: 'error',
133
+ model: 'error',
134
+ text: data?.text || data?.message || data?.status || `Local API returned HTTP ${res.status}`,
135
+ };
136
+ }
137
+ if (!data || typeof data.text !== 'string') {
138
+ return {
139
+ provider: 'error',
140
+ model: 'error',
141
+ text: 'Local API returned an invalid chat response.',
142
+ };
143
+ }
144
+ return data;
145
+ } catch (e) {
146
+ console.error("Failed to send chat message to local server:", e);
147
+ return { provider: 'error', model: 'error', text: `Failed to connect to Local API Server: ${e}` };
148
+ }
149
+ }
150
+ const { invoke } = await import('@tauri-apps/api/core')
151
+ const response = await invoke<ChatResponse>('send_chat_message', {
152
+ request: { message: outgoingMessage, systemInstruction: '', chatId, imageDataUri, audioDataUri, documentAttachment, workspacePath },
153
+ })
154
+ if (imageDataUri) {
155
+ await invoke('save_pictures', {
156
+ images: imageDataUri.split(' '),
157
+ source: 'chat',
158
+ message: outgoingMessage,
159
+ })
160
+ }
161
+ return response
162
+ }
163
+
164
+ export async function streamChatMessage(
165
+ message: string,
166
+ onChunk: (chunk: string) => void,
167
+ imageDataUri?: string | null,
168
+ audioDataUri?: string | null,
169
+ systemInstruction = '',
170
+ onProgress?: (progress: AgentProgress) => void,
171
+ documentAttachment?: DocumentAttachment | null,
172
+ workspacePath?: string | null,
173
+ chatId?: string | null,
174
+ ): Promise<ChatResponse> {
175
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
176
+ const API_BASE = getApiBase();
177
+ const outgoingMessage = withImagePlaceholder(message, imageDataUri);
178
+ const res = await fetch(`${API_BASE}/chat-stream`, {
179
+ method: 'POST',
180
+ headers: { 'Content-Type': 'application/json' },
181
+ body: JSON.stringify({ message: outgoingMessage, systemInstruction, chatId, imageDataUri, audioDataUri, documentAttachment })
182
+ });
183
+ if (!res.ok) {
184
+ const data = await res.json().catch(() => null);
185
+ throw new Error(data?.text || data?.message || `HTTP ${res.status}`);
186
+ }
187
+ const reader = res.body?.getReader();
188
+ if (!reader) {
189
+ throw new Error("No response body reader");
190
+ }
191
+ const decoder = new TextDecoder();
192
+ let buffer = '';
193
+ let finalResponse: ChatResponse | null = null;
194
+ while (true) {
195
+ const { value, done } = await reader.read();
196
+ if (done) break;
197
+ buffer += decoder.decode(value, { stream: true });
198
+ const lines = buffer.split('\n');
199
+ buffer = lines.pop() || '';
200
+ for (const line of lines) {
201
+ if (!line.trim()) continue;
202
+ try {
203
+ const event = JSON.parse(line);
204
+ if (event.type === 'chunk') {
205
+ onChunk(event.chunk);
206
+ } else if (event.type === 'progress') {
207
+ onProgress?.(event.progress);
208
+ } else if (event.type === 'done') {
209
+ finalResponse = event.response;
210
+ }
211
+ } catch (e) {
212
+ console.error("Failed to parse stream line:", line, e);
213
+ }
214
+ }
215
+ }
216
+ if (finalResponse) return finalResponse;
217
+ throw new Error("Stream closed without a final response");
218
+ }
219
+ const { invoke, Channel } = await import('@tauri-apps/api/core')
220
+ const outgoingMessage = withImagePlaceholder(message, imageDataUri)
221
+ const onEvent = new Channel<DesktopStreamEvent>()
222
+ onEvent.onmessage = (event) => {
223
+ if (event.type === 'chunk') onChunk(event.chunk)
224
+ else onProgress?.(event.progress)
225
+ }
226
+ const response = await invoke<ChatResponse>('stream_chat_message', {
227
+ request: { message: outgoingMessage, systemInstruction, chatId, imageDataUri, audioDataUri, documentAttachment, workspacePath },
228
+ onEvent,
229
+ })
230
+ if (imageDataUri) {
231
+ await invoke('save_pictures', {
232
+ images: imageDataUri.split(' '),
233
+ source: 'chat',
234
+ message: outgoingMessage,
235
+ })
236
+ }
237
+ return response
238
+ }
239
+
240
+ function withImagePlaceholder(message: string, imageDataUri?: string | null) {
241
+ if (!imageDataUri || message.includes('[Image #1]')) return message
242
+ const imageCount = imageDataUri.split(/\s+/).filter(Boolean).length
243
+ const markers = Array.from({ length: imageCount }, (_, index) => `[Image #${index + 1}]`).join(' ')
244
+ return markers ? `${message} ${markers}` : message
245
+ }
246
+
247
+ export async function getTtsUrls(text: string): Promise<TtsUrl[]> {
248
+ if (typeof window === 'undefined') return []
249
+ if (!isTauriRuntime()) return []
250
+ const { invoke } = await import('@tauri-apps/api/core')
251
+ return invoke<TtsUrl[]>('get_tts_urls', { text })
252
+ }
253
+
254
+ export async function getRecentInteractions(limit = 50, chatId?: string | null): Promise<InteractionMemory[]> {
255
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
256
+ const API_BASE = getApiBase();
257
+ try {
258
+ const params = new URLSearchParams({ limit: String(limit) });
259
+ if (chatId) params.set('chatId', chatId);
260
+ const res = await fetch(`${API_BASE}/interactions?${params.toString()}`);
261
+ return await res.json();
262
+ } catch (e) {
263
+ console.error("Failed to fetch chat history from local server:", e);
264
+ return [];
265
+ }
266
+ }
267
+ const { invoke } = await import('@tauri-apps/api/core')
268
+ return invoke<InteractionMemory[]>('get_recent_interactions', { limit, chatId })
269
+ }
270
+
271
+ export async function listChatSessions(): Promise<ChatSession[]> {
272
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
273
+ const API_BASE = getApiBase();
274
+ try {
275
+ const res = await fetch(`${API_BASE}/chat-sessions`);
276
+ const data = await res.json();
277
+ return Array.isArray(data) ? data : [];
278
+ } catch (e) {
279
+ console.error("Failed to fetch chat sessions from local server:", e);
280
+ return [];
281
+ }
282
+ }
283
+ const { invoke } = await import('@tauri-apps/api/core')
284
+ return invoke<ChatSession[]>('list_chat_sessions')
285
+ }
286
+
287
+ export async function deleteChatSession(chatId: string): Promise<number> {
288
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
289
+ const API_BASE = getApiBase();
290
+ try {
291
+ const params = new URLSearchParams({ chatId });
292
+ const res = await fetch(`${API_BASE}/chat-sessions/delete?${params.toString()}`, { method: 'POST' });
293
+ const data = await res.json();
294
+ return typeof data?.deleted === 'number' ? data.deleted : 0;
295
+ } catch (e) {
296
+ console.error("Failed to delete chat session on local server:", e);
297
+ return 0;
298
+ }
299
+ }
300
+ const { invoke } = await import('@tauri-apps/api/core')
301
+ return invoke<number>('delete_chat_session', { chatId })
302
+ }
303
+
304
+ export async function renameChatSession(chatId: string, newTitle: string): Promise<number> {
305
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
306
+ const API_BASE = getApiBase();
307
+ try {
308
+ const res = await fetch(`${API_BASE}/chat-sessions/rename`, {
309
+ method: 'POST',
310
+ headers: { 'Content-Type': 'application/json' },
311
+ body: JSON.stringify({ chatId, newTitle })
312
+ });
313
+ const data = await res.json();
314
+ return typeof data?.updated === 'number' ? data.updated : 0;
315
+ } catch (e) {
316
+ console.error("Failed to rename chat session on local server:", e);
317
+ return 0;
318
+ }
319
+ }
320
+ const { invoke } = await import('@tauri-apps/api/core')
321
+ return invoke<number>('rename_chat_session', { chatId, newTitle })
322
+ }
323
+
324
+ export async function getProfileValue(key: string): Promise<string> {
325
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
326
+ const API_BASE = getApiBase();
327
+ try {
328
+ const params = new URLSearchParams({ key });
329
+ const res = await fetch(`${API_BASE}/profile?${params.toString()}`);
330
+ const data = await res.json();
331
+ return data.value || '';
332
+ } catch (e) {
333
+ console.error("Failed to get profile key from local server:", e);
334
+ return '';
335
+ }
336
+ }
337
+ const { invoke } = await import('@tauri-apps/api/core')
338
+ return invoke<string | null>('get_profile_value', { key }).then(res => res || '')
339
+ }
340
+
341
+ export async function setProfileValue(key: string, value: string): Promise<boolean> {
342
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
343
+ const API_BASE = getApiBase();
344
+ try {
345
+ const res = await fetch(`${API_BASE}/profile`, {
346
+ method: 'POST',
347
+ headers: { 'Content-Type': 'application/json' },
348
+ body: JSON.stringify({ key, value })
349
+ });
350
+ const data = await res.json();
351
+ return data.status === 'ok';
352
+ } catch (e) {
353
+ console.error("Failed to set profile key on local server:", e);
354
+ return false;
355
+ }
356
+ }
357
+ const { invoke } = await import('@tauri-apps/api/core')
358
+ return invoke<void>('set_profile_value', { key, value }).then(() => true).catch(() => false)
359
+ }
360
+
361
+ export async function clearChatHistory(chatId?: string | null): Promise<number> {
362
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
363
+ const API_BASE = getApiBase();
364
+ try {
365
+ const params = new URLSearchParams();
366
+ if (chatId) params.set('chatId', chatId);
367
+ const suffix = params.toString() ? `?${params.toString()}` : '';
368
+ const res = await fetch(`${API_BASE}/interactions/clear${suffix}`, { method: 'POST' });
369
+ const data = await res.json();
370
+ return data.status === 'ok' ? 1 : 0;
371
+ } catch (e) {
372
+ console.error("Failed to clear chat history on local server:", e);
373
+ return 0;
374
+ }
375
+ }
376
+ const { invoke } = await import('@tauri-apps/api/core')
377
+ return invoke<number>('clear_chat_history', { chatId })
378
+ }
379
+
380
+ export async function listSavedPictures(): Promise<PictureEntry[]> {
381
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
382
+ const API_BASE = getApiBase();
383
+ try {
384
+ const res = await fetch(`${API_BASE}/pictures`);
385
+ const pictures = await res.json();
386
+ return Array.isArray(pictures)
387
+ ? pictures.map((picture) => {
388
+ const pictureUrl = picture.url ? `${API_BASE.replace('/api', '')}${picture.url}` : undefined
389
+ return {
390
+ ...picture,
391
+ path: pictureUrl || picture.path,
392
+ thumbnailPath: undefined,
393
+ thumbnailUrl: pictureUrl,
394
+ url: pictureUrl || picture.url,
395
+ }
396
+ })
397
+ : [];
398
+ } catch (e) {
399
+ console.error("Failed to fetch saved pictures from local server:", e);
400
+ return [];
401
+ }
402
+ }
403
+ const { invoke } = await import('@tauri-apps/api/core')
404
+ return invoke<PictureEntry[]>('list_pictures')
405
+ }
406
+
407
+ export async function submitToolApproval(token: string, approved: boolean): Promise<void> {
408
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
409
+ return;
410
+ }
411
+ const { invoke } = await import('@tauri-apps/api/core')
412
+ return invoke('submit_tool_approval', { token, approved })
413
+ }
414
+
415
+ export async function proposeCodeEdits(root: string, edits: CodeEdit[]): Promise<CodeEditProposal> {
416
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
417
+ return { approvalRequired: false, approvalToken: '', edits: [] };
418
+ }
419
+ const { invoke } = await import('@tauri-apps/api/core')
420
+ return invoke<CodeEditProposal>('propose_desktop_code_edits', { root, edits })
421
+ }
422
+
423
+ export async function applyCodeEdits(root: string, edits: CodeEdit[], approvalToken: string) {
424
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
425
+ return;
426
+ }
427
+ const { invoke } = await import('@tauri-apps/api/core')
428
+ return invoke('apply_desktop_code_edits', { root, edits, approvalToken })
429
+ }
430
+
431
+ export async function listen<T>(event: string, handler: (event: { payload: T }) => void): Promise<() => void> {
432
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
433
+ return () => {};
434
+ }
435
+ const { listen: tauriListen } = await import('@tauri-apps/api/event');
436
+ return tauriListen<T>(event, handler);
437
+ }
438
+
439
+ export function convertFileSrc(filePath: string, protocol = 'asset'): string {
440
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
441
+ return filePath;
442
+ }
443
+ const internals = (window as any).__TAURI_INTERNALS__;
444
+ if (internals && typeof internals.convertFileSrc === 'function') {
445
+ return internals.convertFileSrc(filePath, protocol);
446
+ }
447
+ const path = filePath.startsWith('\\\\?\\') ? filePath.substring(4) : filePath;
448
+ return `https://asset.localhost/${encodeURIComponent(path)}`;
449
+ }
450
+
451
+ export function installTauriAdapters() {
452
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
453
+ console.warn(`Not running inside Tauri. Connecting to local API server fallback at ${getApiBase()}.`);
454
+ const API_BASE = getApiBase();
455
+
456
+ (window as any).settingsApi = {
457
+ getSettings: async () => {
458
+ try {
459
+ const res = await fetch(`${API_BASE}/config`);
460
+ return await res.json();
461
+ } catch (e) {
462
+ console.error("Failed to fetch settings from local server:", e);
463
+ return {};
464
+ }
465
+ },
466
+ getUpdaterStatus: async () => ({ supported: false, message: 'Desktop updates are only available in the Tauri app.' }),
467
+ checkForUpdates: async () => ({ available: false, supported: false, message: 'Desktop updates are only available in the Tauri app.' }),
468
+ installAvailableUpdate: async () => 'Desktop updates are only available in the Tauri app.',
469
+ saveSettings: async (config: any) => {
470
+ try {
471
+ const res = await fetch(`${API_BASE}/config`, {
472
+ method: 'POST',
473
+ headers: { 'Content-Type': 'application/json' },
474
+ body: JSON.stringify(config)
475
+ });
476
+ return await res.json();
477
+ } catch (e) {
478
+ console.error("Failed to save settings to local server:", e);
479
+ return {};
480
+ }
481
+ },
482
+ closeSettings: () => {
483
+ window.location.hash = '#/';
484
+ },
485
+ quitApp: () => undefined,
486
+ openExternal: async (url: string) => {
487
+ window.open(url, '_blank', 'noopener,noreferrer')
488
+ },
489
+ openFolder: async () => ({ success: false, message: 'Opening local folders is only available in the desktop app.' }),
490
+ openCustomWorkflows: async () => ({ success: false, message: 'Opening workflow files is only available in the desktop app.' }),
491
+ reloadCustomWorkflows: async () => ({ success: false, message: 'Reloading workflow files is only available in the desktop app.' }),
492
+ };
493
+
494
+ (window as any).spotlightAPI = {
495
+ submit: () => {},
496
+ executeAction: async (action: any) => {
497
+ try {
498
+ const res = await fetch(`${API_BASE}/action`, {
499
+ method: 'POST',
500
+ headers: { 'Content-Type': 'application/json' },
501
+ body: JSON.stringify(action)
502
+ });
503
+ return await res.json();
504
+ } catch (e) {
505
+ return { success: false, message: String(e) };
506
+ }
507
+ },
508
+ close: () => {},
509
+ hide: () => {},
510
+ resize: () => {},
511
+ getSettings: async () => {
512
+ try {
513
+ const res = await fetch(`${API_BASE}/config`);
514
+ return await res.json();
515
+ } catch (e) {
516
+ return {};
517
+ }
518
+ },
519
+ onSettingsChanged: () => {},
520
+ };
521
+
522
+ (window as any).widgetAPI = {
523
+ onStateChange: () => {},
524
+ };
525
+
526
+ (window as any).screenPickerApi = {
527
+ onScreenshot: () => {},
528
+ sendSelection: () => {},
529
+ startContinuousTranslation: () => {},
530
+ stopContinuousTranslation: () => {},
531
+ onTranslationResult: () => {},
532
+ closePicker: () => {},
533
+ setOverlayInteractable: () => {},
534
+ };
535
+
536
+ (window as any).api = {
537
+ sendMessage: async (message: string, imageDataUri?: string | null, audioDataUri?: string | null, documentAttachment?: DocumentAttachment | null) => {
538
+ try {
539
+ const res = await fetch(`${API_BASE}/chat`, {
540
+ method: 'POST',
541
+ headers: { 'Content-Type': 'application/json' },
542
+ body: JSON.stringify({ message, imageDataUri, audioDataUri, documentAttachment })
543
+ });
544
+ const data = await res.json().catch(() => null);
545
+ if (!res.ok) {
546
+ return {
547
+ provider: 'error',
548
+ model: 'error',
549
+ text: data?.text || data?.message || data?.status || `Local API returned HTTP ${res.status}`,
550
+ };
551
+ }
552
+ if (!data || typeof data.text !== 'string') {
553
+ return { provider: 'error', model: 'error', text: 'Local API returned an invalid chat response.' };
554
+ }
555
+ return data;
556
+ } catch (e) {
557
+ console.error("Failed to send message to local server:", e);
558
+ return { provider: 'error', model: 'error', text: `Failed to connect to Local API Server: ${e}` };
559
+ }
560
+ },
561
+ closeWindow: () => {},
562
+ minimizeWindow: () => {},
563
+ quitApp: () => undefined,
564
+ maximizeWindow: () => {},
565
+ resetChat: async () => {
566
+ try {
567
+ const res = await fetch(`${API_BASE}/interactions/clear`, { method: 'POST' });
568
+ const data = await res.json();
569
+ return data.status === 'ok' ? 1 : 0;
570
+ } catch (e) {
571
+ return 0;
572
+ }
573
+ },
574
+ getChatHistory: async () => {
575
+ try {
576
+ const res = await fetch(`${API_BASE}/interactions`);
577
+ return await res.json();
578
+ } catch (e) {
579
+ console.error("Failed to fetch chat history from local server:", e);
580
+ return [];
581
+ }
582
+ },
583
+ listSavedPictures,
584
+ openSettings: () => {
585
+ window.location.hash = '#/settings';
586
+ },
587
+ readClipboard: async () => '',
588
+ writeClipboard: async () => {},
589
+ getSystemInfo: async () => {
590
+ try {
591
+ const res = await fetch(`${API_BASE}/status`);
592
+ return await res.json();
593
+ } catch (e) {
594
+ return { backend: 'browser-fallback' };
595
+ }
596
+ },
597
+ getWeather: async (city: string) => {
598
+ try {
599
+ const res = await fetch(`${API_BASE}/weather?city=${encodeURIComponent(city)}`);
600
+ return await res.json();
601
+ } catch (e) {
602
+ return { error: String(e) };
603
+ }
604
+ },
605
+ getSettings: async () => {
606
+ try {
607
+ const res = await fetch(`${API_BASE}/config`);
608
+ return await res.json();
609
+ } catch (e) {
610
+ return {};
611
+ }
612
+ },
613
+ saveSettings: async (config: any) => {
614
+ try {
615
+ const res = await fetch(`${API_BASE}/config`, {
616
+ method: 'POST',
617
+ headers: { 'Content-Type': 'application/json' },
618
+ body: JSON.stringify(config)
619
+ });
620
+ return await res.json();
621
+ } catch (e) {
622
+ return {};
623
+ }
624
+ },
625
+ onSettingsChanged: () => {},
626
+ startVision: () => {},
627
+ onVisionReady: async () => () => {},
628
+ captureSilentScreen: async () => '',
629
+ getSmartContext: async () => {
630
+ try {
631
+ const res = await fetch(`${API_BASE}/smart-context`);
632
+ return await res.json();
633
+ } catch (e) {
634
+ return {};
635
+ }
636
+ },
637
+ onProactiveSuggestion: async () => () => {},
638
+ onProactiveNotification: async () => () => {},
639
+ toggleProactive: () => {},
640
+ recordBehavior: () => {},
641
+ executeProactiveAction: async (action: any) => {
642
+ try {
643
+ const res = await fetch(`${API_BASE}/action`, {
644
+ method: 'POST',
645
+ headers: { 'Content-Type': 'application/json' },
646
+ body: JSON.stringify(action)
647
+ });
648
+ return await res.json();
649
+ } catch (e) {
650
+ return { success: false, message: String(e) };
651
+ }
652
+ },
653
+ executeApprovedAction: async (action: any) => {
654
+ try {
655
+ const res = await fetch(`${API_BASE}/action`, {
656
+ method: 'POST',
657
+ headers: { 'Content-Type': 'application/json' },
658
+ body: JSON.stringify(action)
659
+ });
660
+ return await res.json();
661
+ } catch (e) {
662
+ return { success: false, message: String(e) };
663
+ }
664
+ },
665
+ onSpotlightToChat: async () => () => {},
666
+ notifyAiResponse: () => {},
667
+ clearAiNotifications: () => {},
668
+ getTtsUrls: async () => [],
669
+ setAiState: () => {},
670
+ };
671
+ return;
672
+ }
673
+
674
+ const settingsChanged = async (callback: (config: any) => void) => {
675
+ const { listen } = await import('@tauri-apps/api/event')
676
+ void listen<any>('settings-changed', (event) => callback(event.payload))
677
+ }
678
+ const executeAction = async (action: any, approved = false) => {
679
+ const { invoke } = await import('@tauri-apps/api/core')
680
+ return action.type === 'plugin'
681
+ ? invoke('run_native_plugin', { name: action.pluginName, instruction: action.target || '' })
682
+ : invoke('run_desktop_action', { action: { ...action, approved } })
683
+ }
684
+
685
+ window.settingsApi = {
686
+ getSettings: async () => {
687
+ const { invoke } = await import('@tauri-apps/api/core')
688
+ return invoke('get_config')
689
+ },
690
+ getUpdaterStatus: async () => {
691
+ const { invoke } = await import('@tauri-apps/api/core')
692
+ return invoke('get_updater_status')
693
+ },
694
+ checkForUpdates: async () => {
695
+ const { invoke } = await import('@tauri-apps/api/core')
696
+ return invoke('check_for_updates')
697
+ },
698
+ installAvailableUpdate: async () => {
699
+ const { invoke } = await import('@tauri-apps/api/core')
700
+ return invoke('install_available_update', { approved: true })
701
+ },
702
+ saveSettings: async (config) => {
703
+ const { invoke } = await import('@tauri-apps/api/core')
704
+ return invoke('update_config', { config })
705
+ },
706
+ closeSettings: async () => {
707
+ const { invoke } = await import('@tauri-apps/api/core')
708
+ const { getCurrentWindow } = await import('@tauri-apps/api/window')
709
+ return invoke('close_desktop_window', { label: getCurrentWindow().label })
710
+ },
711
+ quitApp: async () => {
712
+ const { invoke } = await import('@tauri-apps/api/core')
713
+ return void invoke('exit_app')
714
+ },
715
+ openExternal: async (url) => {
716
+ const { invoke } = await import('@tauri-apps/api/core')
717
+ return invoke('run_desktop_action', { action: { type: 'open_url', target: url } })
718
+ },
719
+ openFolder: async (path) => {
720
+ const { invoke } = await import('@tauri-apps/api/core')
721
+ return invoke('open_folder', { path })
722
+ },
723
+ openCustomWorkflows: async () => {
724
+ const { invoke } = await import('@tauri-apps/api/core')
725
+ return invoke('open_workflows_file')
726
+ },
727
+ reloadCustomWorkflows: async () => {
728
+ const { invoke } = await import('@tauri-apps/api/core')
729
+ return invoke('reload_custom_workflows')
730
+ },
731
+ }
732
+
733
+ window.spotlightAPI = {
734
+ submit: async (query) => {
735
+ const { invoke } = await import('@tauri-apps/api/core')
736
+ return void invoke('submit_spotlight', { query })
737
+ },
738
+ executeAction: async (action) => {
739
+ const { invoke } = await import('@tauri-apps/api/core')
740
+ if (action.type === 'clipboard_write') {
741
+ await navigator.clipboard.writeText(action.target)
742
+ return { success: true }
743
+ }
744
+ return invoke('run_desktop_action', { action })
745
+ },
746
+ close: async () => {
747
+ const { invoke } = await import('@tauri-apps/api/core')
748
+ const { getCurrentWindow } = await import('@tauri-apps/api/window')
749
+ return invoke('close_desktop_window', { label: getCurrentWindow().label })
750
+ },
751
+ hide: async () => {
752
+ const { invoke } = await import('@tauri-apps/api/core')
753
+ const { getCurrentWindow } = await import('@tauri-apps/api/window')
754
+ return invoke('hide_desktop_window', { label: getCurrentWindow().label })
755
+ },
756
+ resize: async (width, height) => {
757
+ const { invoke } = await import('@tauri-apps/api/core')
758
+ const { getCurrentWindow } = await import('@tauri-apps/api/window')
759
+ return void invoke('resize_desktop_window', {
760
+ label: getCurrentWindow().label,
761
+ width,
762
+ height,
763
+ })
764
+ },
765
+ getSettings: async () => {
766
+ const { invoke } = await import('@tauri-apps/api/core')
767
+ return invoke('get_config')
768
+ },
769
+ onSettingsChanged: settingsChanged,
770
+ }
771
+
772
+ window.widgetAPI = {
773
+ onStateChange: async (callback) => {
774
+ const { listen } = await import('@tauri-apps/api/event')
775
+ void listen<string>('widget-state', (event) => callback(event.payload))
776
+ },
777
+ }
778
+
779
+ window.screenPickerApi = {
780
+ onScreenshot: async (callback) => {
781
+ const { invoke } = await import('@tauri-apps/api/core')
782
+ try {
783
+ const image = await captureSharedScreen()
784
+ callback(image)
785
+ } catch (reason) {
786
+ console.warn('Screen share capture failed, falling back to native capture:', reason)
787
+ void invoke<string>('capture_silent_screen').then(callback)
788
+ }
789
+ },
790
+ sendSelection: async (image) => {
791
+ const { invoke } = await import('@tauri-apps/api/core')
792
+ return void invoke('submit_screen_selection', { image })
793
+ },
794
+ startContinuousTranslation: (rect) => {
795
+ let translationTimer: ReturnType<typeof setInterval> | null = null
796
+ const translate = async () => {
797
+ const { invoke } = await import('@tauri-apps/api/core')
798
+ void invoke<string>('translate_capture_region', { rect })
799
+ .then((text) => window.dispatchEvent(new CustomEvent('mint-translation', { detail: text })))
800
+ .catch((reason) => {
801
+ window.dispatchEvent(new CustomEvent('mint-translation', { detail: String(reason) }))
802
+ })
803
+ }
804
+ translate()
805
+ translationTimer = setInterval(translate, 3000)
806
+
807
+ // Clean up helper attached to window if needed
808
+ if ((window as any)._stopTranslate) (window as any)._stopTranslate()
809
+ ;(window as any)._stopTranslate = () => {
810
+ if (translationTimer) clearInterval(translationTimer)
811
+ }
812
+ },
813
+ stopContinuousTranslation: () => {
814
+ if ((window as any)._stopTranslate) {
815
+ (window as any)._stopTranslate()
816
+ }
817
+ },
818
+ onTranslationResult: (callback) => {
819
+ window.addEventListener('mint-translation', ((event: CustomEvent<string>) => {
820
+ callback(event.detail)
821
+ }) as EventListener)
822
+ },
823
+ closePicker: async () => {
824
+ const { invoke } = await import('@tauri-apps/api/core')
825
+ const { getCurrentWindow } = await import('@tauri-apps/api/window')
826
+ return invoke('close_desktop_window', { label: getCurrentWindow().label })
827
+ },
828
+ setOverlayInteractable: () => {},
829
+ }
830
+
831
+ window.api = {
832
+ sendMessage: (message, imageDataUri, audioDataUri, documentAttachment) => sendChatMessage(message, imageDataUri, audioDataUri, documentAttachment),
833
+ closeWindow: async () => {
834
+ const { invoke } = await import('@tauri-apps/api/core')
835
+ const { getCurrentWindow } = await import('@tauri-apps/api/window')
836
+ return invoke('hide_desktop_window', { label: getCurrentWindow().label })
837
+ },
838
+ minimizeWindow: async () => {
839
+ const { getCurrentWindow } = await import('@tauri-apps/api/window')
840
+ return void getCurrentWindow().minimize()
841
+ },
842
+ quitApp: async () => {
843
+ const { invoke } = await import('@tauri-apps/api/core')
844
+ return void invoke('exit_app')
845
+ },
846
+ maximizeWindow: async () => {
847
+ const { getCurrentWindow } = await import('@tauri-apps/api/window')
848
+ return void getCurrentWindow().toggleMaximize()
849
+ },
850
+ resetChat: clearChatHistory,
851
+ getChatHistory: () => getRecentInteractions(50),
852
+ listSavedPictures,
853
+ openSettings: async () => {
854
+ const { invoke } = await import('@tauri-apps/api/core')
855
+ return invoke('open_window', { kind: 'settings' })
856
+ },
857
+ readClipboard: () => navigator.clipboard.readText(),
858
+ writeClipboard: (text) => navigator.clipboard.writeText(text),
859
+ getSystemInfo: async () => ({ backend: 'rust' }),
860
+ getWeather: async (city) => {
861
+ const { invoke } = await import('@tauri-apps/api/core')
862
+ return invoke('get_weather', { city })
863
+ },
864
+ getSettings: async () => {
865
+ const { invoke } = await import('@tauri-apps/api/core')
866
+ return invoke('get_config')
867
+ },
868
+ saveSettings: async (config) => {
869
+ const { invoke } = await import('@tauri-apps/api/core')
870
+ return invoke('update_config', { config })
871
+ },
872
+ onSettingsChanged: settingsChanged,
873
+ startVision: async () => {
874
+ const { invoke } = await import('@tauri-apps/api/core')
875
+ try {
876
+ const image = await captureSharedScreen()
877
+ window.localStorage.setItem('mint:pending-screen-capture', image)
878
+ } catch (reason) {
879
+ console.warn('Screen share capture failed before opening picker:', reason)
880
+ const image = await invoke<string>('capture_silent_screen')
881
+ window.localStorage.setItem('mint:pending-screen-capture', image)
882
+ }
883
+ return invoke('start_screen_capture')
884
+ },
885
+ onVisionReady: async (callback) => {
886
+ const { listen } = await import('@tauri-apps/api/event')
887
+ return listen<string>('vision-ready', (event) => callback(event.payload))
888
+ },
889
+ captureSilentScreen: async () => {
890
+ const { invoke } = await import('@tauri-apps/api/core')
891
+ return invoke('capture_silent_screen')
892
+ },
893
+ getSmartContext: async () => {
894
+ const { invoke } = await import('@tauri-apps/api/core')
895
+ return invoke('get_smart_context')
896
+ },
897
+ onProactiveSuggestion: async (callback) => {
898
+ const { listen } = await import('@tauri-apps/api/event')
899
+ return listen<any>('proactive-suggestion', (event) => callback(event.payload))
900
+ },
901
+ onProactiveNotification: async (callback) => {
902
+ const { listen } = await import('@tauri-apps/api/event')
903
+ return listen<any>('proactive-notification', (event) => callback(event.payload))
904
+ },
905
+ toggleProactive: async (enabled) => {
906
+ const { invoke } = await import('@tauri-apps/api/core')
907
+ return void invoke('toggle_proactive', { enabled })
908
+ },
909
+ recordBehavior: async (context) => {
910
+ const { invoke } = await import('@tauri-apps/api/core')
911
+ return void invoke('save_behavior_context', { context })
912
+ },
913
+ executeProactiveAction: (action) => executeAction(action),
914
+ executeApprovedAction: (action) => executeAction(action, true),
915
+ onSpotlightToChat: async (callback) => {
916
+ const { listen } = await import('@tauri-apps/api/event')
917
+ return listen<string>('spotlight-to-chat', (event) => callback(event.payload))
918
+ },
919
+ notifyAiResponse: () => {},
920
+ clearAiNotifications: () => {},
921
+ getTtsUrls: async (text) => {
922
+ const { invoke } = await import('@tauri-apps/api/core')
923
+ return invoke('get_tts_urls', { text })
924
+ },
925
+ setAiState: async (state) => {
926
+ const { invoke } = await import('@tauri-apps/api/core')
927
+ return void invoke('set_ai_state', { state })
928
+ },
929
+ }
930
+ }
931
+
932
+ async function captureSharedScreen(): Promise<string> {
933
+ // On Linux (especially under Wayland/WebKitGTK), getDisplayMedia often returns a black screen
934
+ // or fails silently. Bypass it to force fallback to native screenshot commands.
935
+ if (navigator.userAgent.toLowerCase().includes('linux')) {
936
+ throw new Error('Linux detected, bypassing getDisplayMedia to use native screenshot tools')
937
+ }
938
+
939
+ if (!navigator.mediaDevices?.getDisplayMedia) {
940
+ throw new Error('getDisplayMedia is not available')
941
+ }
942
+
943
+ const stream = await navigator.mediaDevices.getDisplayMedia({
944
+ video: true,
945
+ audio: false,
946
+ })
947
+ try {
948
+ const video = document.createElement('video')
949
+ video.srcObject = stream
950
+ video.muted = true
951
+ await video.play()
952
+ await new Promise<void>((resolve) => {
953
+ if (video.videoWidth > 0 && video.videoHeight > 0) {
954
+ resolve()
955
+ } else {
956
+ video.onloadedmetadata = () => resolve()
957
+ }
958
+ })
959
+
960
+ const canvas = document.createElement('canvas')
961
+ canvas.width = video.videoWidth || window.screen.width
962
+ canvas.height = video.videoHeight || window.screen.height
963
+ const context = canvas.getContext('2d')
964
+ if (!context) throw new Error('Unable to create screen capture canvas')
965
+ context.drawImage(video, 0, 0, canvas.width, canvas.height)
966
+ return canvas.toDataURL('image/png')
967
+ } finally {
968
+ stream.getTracks().forEach((track) => track.stop())
969
+ }
970
+ }
971
+
972
+ export async function readClipboardImage(): Promise<string | null> {
973
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
974
+ return null
975
+ }
976
+ const { invoke } = await import('@tauri-apps/api/core')
977
+ try {
978
+ return await invoke<string>('read_clipboard_image')
979
+ } catch (err) {
980
+ console.warn('Failed to read clipboard image via Tauri command:', err)
981
+ return null
982
+ }
983
+ }