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