@pheem49/mint 1.5.3 → 1.5.5

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.
@@ -22,6 +22,7 @@ const chatProviderSelect = document.getElementById('chat-provider-select');
22
22
  const imagePreviewContainer = document.getElementById('image-preview-container');
23
23
  const imagePreview = document.getElementById('image-preview');
24
24
  const removeImageBtn = document.getElementById('remove-image-btn');
25
+ const agentModeToggle = document.getElementById('agent-mode-toggle');
25
26
  const modelMount = document.getElementById('model-mount');
26
27
  const modelShell = document.getElementById('model-shell');
27
28
  const modelStatus = document.getElementById('model-status');
@@ -73,6 +74,12 @@ function buildProviderPicker(settings = currentSettings) {
73
74
  chatProviderSelect.value = settings.aiProvider || 'gemini';
74
75
  }
75
76
 
77
+ function syncAgentModeToggle(settings = currentSettings) {
78
+ if (!agentModeToggle) return;
79
+ agentModeToggle.checked = settings.assistantMode === 'agent';
80
+ agentModeToggle.closest('.smart-context-control')?.classList.toggle('is-active', agentModeToggle.checked);
81
+ }
82
+
76
83
  async function changeChatProvider(provider) {
77
84
  if (!PROVIDER_PICKER_OPTIONS.some(([value]) => value === provider)) return;
78
85
  const nextSettings = { ...currentSettings, aiProvider: provider };
@@ -222,9 +229,11 @@ async function loadTheme() {
222
229
  ttsSpeed = config.ttsSpeed !== undefined ? config.ttsSpeed : 1.0;
223
230
  ttsPitch = config.ttsPitch !== undefined ? config.ttsPitch : 1.0;
224
231
  buildProviderPicker(currentSettings);
232
+ syncAgentModeToggle(currentSettings);
225
233
  } catch (e) {
226
234
  applyTheme('dark', '#8b5cf6', '#f8fafc');
227
235
  buildProviderPicker(currentSettings);
236
+ syncAgentModeToggle(currentSettings);
228
237
  }
229
238
  }
230
239
 
@@ -248,12 +257,35 @@ window.api.onSettingsChanged((config) => {
248
257
  ttsSpeed = config.ttsSpeed !== undefined ? config.ttsSpeed : 1.0;
249
258
  ttsPitch = config.ttsPitch !== undefined ? config.ttsPitch : 1.0;
250
259
  buildProviderPicker(currentSettings);
260
+ syncAgentModeToggle(currentSettings);
251
261
  });
252
262
 
253
263
  chatProviderSelect?.addEventListener('change', (event) => {
254
264
  changeChatProvider(event.target.value);
255
265
  });
256
266
 
267
+ agentModeToggle?.addEventListener('change', async () => {
268
+ const nextSettings = {
269
+ ...currentSettings,
270
+ assistantMode: agentModeToggle.checked ? 'agent' : 'chat'
271
+ };
272
+ agentModeToggle.disabled = true;
273
+ try {
274
+ const result = await window.api.saveSettings(nextSettings);
275
+ if (!result || result.success !== false) {
276
+ currentSettings = nextSettings;
277
+ } else {
278
+ throw new Error(result.message || 'Unable to save assistant mode');
279
+ }
280
+ } catch (error) {
281
+ console.error('Failed to change assistant mode:', error);
282
+ setMintActivity('error');
283
+ } finally {
284
+ syncAgentModeToggle(currentSettings);
285
+ agentModeToggle.disabled = false;
286
+ }
287
+ });
288
+
257
289
  // --- Voice Input Setup ---
258
290
  let mediaRecorder = null;
259
291
  let audioChunks = [];
@@ -468,7 +500,9 @@ async function sendVoiceMessage(base64Audio) {
468
500
  await speakText(normalizeAiText(response.response), { onEnd: resumeSpeechIfNeeded });
469
501
  notifyAiIfNeeded();
470
502
 
471
- if (response.action && response.action.type !== 'none') {
503
+ if (response.approval?.required) {
504
+ appendApprovalCard(msgDiv, response.approval);
505
+ } else if (response.action && response.action.type !== 'none') {
472
506
  appendActionCard(msgDiv, response.action);
473
507
  }
474
508
  } catch (error) {
@@ -799,6 +833,125 @@ function formatTime(isoString) {
799
833
  }
800
834
  }
801
835
 
836
+ function compactSmartContext(context) {
837
+ if (!context || typeof context !== 'object') return null;
838
+ const activeWindow = context.activeWindow || {};
839
+ const currentApp = context.currentApp || {};
840
+ const browser = context.browser || null;
841
+ return {
842
+ capturedAt: context.capturedAt,
843
+ platform: context.platform,
844
+ currentApp: currentApp.name || activeWindow.appName || activeWindow.processName || '',
845
+ processName: currentApp.processName || activeWindow.processName || '',
846
+ pid: currentApp.pid || activeWindow.pid || null,
847
+ activeWindowTitle: activeWindow.title || '',
848
+ browser: browser ? {
849
+ title: browser.title || '',
850
+ url: browser.url || '',
851
+ urlUnavailableReason: browser.urlUnavailableReason || ''
852
+ } : null,
853
+ selectedText: context.selectedText || '',
854
+ clipboardText: context.clipboardText || ''
855
+ };
856
+ }
857
+
858
+ function appendSmartContextToMessage(message, context) {
859
+ const compact = compactSmartContext(context);
860
+ if (!compact) return message;
861
+ return [
862
+ message,
863
+ '',
864
+ '[SMART_CONTEXT]',
865
+ 'Use this structured desktop context together with the attached screenshot. Do not mention it unless it helps answer the user.',
866
+ JSON.stringify(compact, null, 2),
867
+ '[/SMART_CONTEXT]'
868
+ ].join('\n');
869
+ }
870
+
871
+ function shouldShowAgentActivity(options = {}) {
872
+ return options.showAgentActivity !== false && currentSettings.assistantMode === 'agent';
873
+ }
874
+
875
+ function createAgentActivityCard() {
876
+ const messageDiv = document.createElement('div');
877
+ messageDiv.classList.add('message', 'ai-message', 'agent-activity-message');
878
+
879
+ const bubble = document.createElement('div');
880
+ bubble.classList.add('message-bubble', 'agent-activity-card');
881
+
882
+ const header = document.createElement('div');
883
+ header.className = 'agent-activity-header';
884
+ const title = document.createElement('span');
885
+ title.textContent = 'Agent Activity';
886
+ const status = document.createElement('span');
887
+ status.className = 'agent-activity-status';
888
+ status.textContent = 'Running';
889
+ header.appendChild(title);
890
+ header.appendChild(status);
891
+
892
+ const list = document.createElement('div');
893
+ list.className = 'agent-activity-list';
894
+
895
+ bubble.appendChild(header);
896
+ bubble.appendChild(list);
897
+ messageDiv.appendChild(bubble);
898
+ chatContainer.appendChild(messageDiv);
899
+ scrollToBottom();
900
+
901
+ return {
902
+ element: messageDiv,
903
+ list,
904
+ status,
905
+ add(label, state = 'running', detail = '') {
906
+ const item = document.createElement('div');
907
+ item.className = 'agent-activity-item';
908
+ item.dataset.state = state;
909
+
910
+ const dot = document.createElement('span');
911
+ dot.className = 'agent-activity-dot';
912
+
913
+ const content = document.createElement('span');
914
+ content.className = 'agent-activity-text';
915
+ content.textContent = detail ? `${label}: ${detail}` : label;
916
+
917
+ item.appendChild(dot);
918
+ item.appendChild(content);
919
+ list.appendChild(item);
920
+ scrollToBottom();
921
+ return item;
922
+ },
923
+ update(item, state, label, detail = '') {
924
+ if (!item) return;
925
+ item.dataset.state = state;
926
+ const content = item.querySelector('.agent-activity-text');
927
+ if (content && label) {
928
+ content.textContent = detail ? `${label}: ${detail}` : label;
929
+ }
930
+ },
931
+ finish(state = 'done', label = 'Done') {
932
+ status.textContent = label;
933
+ status.dataset.state = state;
934
+ }
935
+ };
936
+ }
937
+
938
+ function describeSmartContextActivity(context, hasScreenshot) {
939
+ const compact = compactSmartContext(context) || {};
940
+ const parts = [];
941
+ if (hasScreenshot) parts.push('screen');
942
+ if (compact.currentApp) parts.push(compact.currentApp);
943
+ if (compact.activeWindowTitle) parts.push(compact.activeWindowTitle);
944
+ if (compact.selectedText) parts.push('selected text');
945
+ if (compact.clipboardText) parts.push('clipboard');
946
+ return parts.slice(0, 3).join(' · ') || 'desktop context';
947
+ }
948
+
949
+ function describeActionActivity(action) {
950
+ if (!action || action.type === 'none') return 'No desktop action';
951
+ const meta = getActionCardMeta(action);
952
+ return meta.detail ? `${meta.title} · ${meta.detail}` : meta.title;
953
+ }
954
+
802
955
  // Clear chat history
803
956
  async function clearChatHistory(confirmMessage = 'Clear current chat history?') {
804
957
  const shouldClear = window.confirm(confirmMessage);
@@ -1143,29 +1296,193 @@ function autoChunkAiText(text) {
1143
1296
  }
1144
1297
 
1145
1298
  function appendActionCard(messageDiv, action) {
1299
+ if (!messageDiv || !action || action.type === 'none') return;
1300
+
1301
+ const meta = getActionCardMeta(action);
1146
1302
  const card = document.createElement('div');
1147
1303
  card.classList.add('action-card');
1304
+ card.dataset.actionType = action.type || 'unknown';
1148
1305
 
1149
- let icon = '';
1150
- let text = '';
1151
-
1152
- if (action.type === 'open_url') {
1153
- icon = '🌐';
1154
- text = `Opened URL: ${action.target}`;
1155
- } else if (action.type === 'open_app') {
1156
- icon = '🚀';
1157
- text = `Launched App: ${action.target}`;
1158
- } else if (action.type === 'search') {
1159
- icon = '🔍';
1160
- text = `Searched info: ${action.target}`;
1161
- } else {
1162
- return; // Do nothing if none or unknown
1306
+ const icon = document.createElement('span');
1307
+ icon.className = 'action-card-icon';
1308
+ icon.textContent = meta.icon;
1309
+
1310
+ const content = document.createElement('div');
1311
+ content.className = 'action-card-content';
1312
+
1313
+ const title = document.createElement('div');
1314
+ title.className = 'action-card-title';
1315
+ title.textContent = meta.title;
1316
+ content.appendChild(title);
1317
+
1318
+ if (meta.detail) {
1319
+ const detail = document.createElement('div');
1320
+ detail.className = 'action-card-detail';
1321
+ detail.textContent = meta.detail;
1322
+ content.appendChild(detail);
1163
1323
  }
1164
1324
 
1165
- card.textContent = `${icon} ${text}`;
1325
+ card.appendChild(icon);
1326
+ card.appendChild(content);
1327
+ messageDiv.querySelector('.message-bubble')?.appendChild(card);
1328
+ }
1329
+
1330
+ function getActionCardMeta(action) {
1331
+ const target = formatActionTarget(action);
1332
+ const type = action?.type || 'unknown';
1333
+ const targetOrFallback = target || 'No target';
1334
+
1335
+ const map = {
1336
+ open_url: ['🌐', 'Opened URL', target],
1337
+ search: ['🔍', 'Searched the web', target],
1338
+ open_app: ['🚀', 'Launched app', target],
1339
+ web_automation: ['🧭', 'Ran browser automation', target],
1340
+ create_folder: ['📁', 'Created folder', target],
1341
+ open_file: ['📄', 'Opened file', target],
1342
+ open_folder: ['📂', 'Opened folder', target],
1343
+ delete_file: ['🗑️', 'Deleted file', target],
1344
+ find_path: ['🔎', action.openAfter ? 'Found and opened path' : 'Found path', buildFindPathDetail(action)],
1345
+ clipboard_write: ['📋', 'Updated clipboard', target],
1346
+ learn_file: ['📚', 'Indexed file', target],
1347
+ learn_folder: ['📚', 'Indexed folder', target],
1348
+ system_info: ['💻', target ? 'Checked weather' : 'Checked system info', target],
1349
+ plugin: ['🔌', 'Ran plugin', target],
1350
+ mcp_tool: ['🧩', 'Called MCP tool', target],
1351
+ mouse_move: ['↗', 'Moved pointer', target],
1352
+ mouse_click: ['☝', 'Clicked screen', buildMouseDetail(action)],
1353
+ type_text: ['⌨', 'Typed text', target],
1354
+ key_tap: ['⌨', 'Pressed key', target],
1355
+ system_automation: ['⚙', 'Changed system setting', target]
1356
+ };
1357
+
1358
+ const [icon, title, detail] = map[type] || ['⚡', `Ran action: ${type}`, targetOrFallback];
1359
+ return { icon, title, detail };
1360
+ }
1361
+
1362
+ function buildFindPathDetail(action) {
1363
+ const target = formatActionTarget(action);
1364
+ const typeLabel = action.pathType && action.pathType !== 'any' ? ` (${action.pathType})` : '';
1365
+ return target ? `${target}${typeLabel}` : typeLabel.trim();
1366
+ }
1367
+
1368
+ function buildMouseDetail(action) {
1369
+ const point = formatActionTarget(action);
1370
+ const button = action.button ? `button ${action.button}` : 'left button';
1371
+ return point ? `${point} · ${button}` : button;
1372
+ }
1373
+
1374
+ function formatActionTarget(action) {
1375
+ if (!action || typeof action !== 'object') return '';
1376
+ if (action.server && action.target) return `${action.server}:${action.target}`;
1377
+ if (action.pluginName) return `${action.pluginName} ${action.target || ''}`.trim();
1378
+ if (action.target) return String(action.target);
1379
+ if (Number.isFinite(action.x) && Number.isFinite(action.y)) return `${action.x}, ${action.y}`;
1380
+ return '';
1381
+ }
1382
+
1383
+ function getApprovalCopy(approval) {
1384
+ const action = approval?.action || {};
1385
+ const actionType = action.type || 'unknown';
1386
+ const target = formatActionTarget(action);
1387
+ const isDangerous = approval?.tier === 'dangerous';
1388
+ return {
1389
+ title: isDangerous ? 'Dangerous action requires approval' : 'Action requires approval',
1390
+ body: target ? `${actionType}: ${target}` : actionType,
1391
+ reason: approval?.reason || 'This action needs your permission before Mint can run it.',
1392
+ approveLabel: isDangerous ? 'Allow Dangerous Action' : 'Allow Action'
1393
+ };
1394
+ }
1395
+
1396
+ function appendApprovalCard(messageDiv, approval, activity = null) {
1397
+ if (!messageDiv || !approval?.action || !window.api?.executeApprovedAction) return;
1398
+
1399
+ const copy = getApprovalCopy(approval);
1400
+ const card = document.createElement('div');
1401
+ card.classList.add('action-card', 'approval-card');
1402
+ card.dataset.tier = approval.tier || 'approval';
1403
+
1404
+ const content = document.createElement('div');
1405
+ content.className = 'approval-card-content';
1166
1406
 
1167
- // Append after the bubble
1168
- messageDiv.querySelector('.message-bubble').appendChild(card);
1407
+ const title = document.createElement('div');
1408
+ title.className = 'approval-card-title';
1409
+ title.textContent = copy.title;
1410
+
1411
+ const body = document.createElement('div');
1412
+ body.className = 'approval-card-body';
1413
+ body.textContent = copy.body;
1414
+
1415
+ const reason = document.createElement('div');
1416
+ reason.className = 'approval-card-reason';
1417
+ reason.textContent = copy.reason;
1418
+
1419
+ content.appendChild(title);
1420
+ content.appendChild(body);
1421
+ content.appendChild(reason);
1422
+
1423
+ const actions = document.createElement('div');
1424
+ actions.className = 'approval-card-actions';
1425
+
1426
+ const approveBtn = document.createElement('button');
1427
+ approveBtn.type = 'button';
1428
+ approveBtn.className = 'approval-btn approval-btn-approve';
1429
+ approveBtn.textContent = copy.approveLabel;
1430
+
1431
+ const cancelBtn = document.createElement('button');
1432
+ cancelBtn.type = 'button';
1433
+ cancelBtn.className = 'approval-btn approval-btn-cancel';
1434
+ cancelBtn.textContent = 'Cancel';
1435
+
1436
+ const setDone = (message, state) => {
1437
+ approveBtn.disabled = true;
1438
+ cancelBtn.disabled = true;
1439
+ card.dataset.state = state;
1440
+ reason.textContent = message;
1441
+ };
1442
+
1443
+ approveBtn.addEventListener('click', async () => {
1444
+ approveBtn.disabled = true;
1445
+ cancelBtn.disabled = true;
1446
+ reason.textContent = 'Running approved action...';
1447
+ const runStep = activity?.add('Running approved action', 'running', describeActionActivity(approval.action));
1448
+ setMintActivity('thinking');
1449
+
1450
+ try {
1451
+ const result = await window.api.executeApprovedAction(approval.action);
1452
+ if (!result || result.success === false) {
1453
+ setDone(result?.message || 'Action failed.', 'error');
1454
+ activity?.update(runStep, 'error', 'Action failed', result?.message || '');
1455
+ activity?.finish('error', 'Failed');
1456
+ setMintActivity('error');
1457
+ return;
1458
+ }
1459
+
1460
+ setDone(result.message || 'Action completed.', 'approved');
1461
+ activity?.update(runStep, 'done', 'Action completed', result.message || describeActionActivity(approval.action));
1462
+ activity?.finish('done', 'Completed');
1463
+ setMintActivity('idle');
1464
+ } catch (error) {
1465
+ console.error('[Approval] Failed to execute action:', error);
1466
+ setDone(error.message || 'Action failed.', 'error');
1467
+ activity?.update(runStep, 'error', 'Action failed', error.message || '');
1468
+ activity?.finish('error', 'Failed');
1469
+ setMintActivity('error');
1470
+ }
1471
+ });
1472
+
1473
+ cancelBtn.addEventListener('click', () => {
1474
+ setDone('Cancelled by user.', 'cancelled');
1475
+ activity?.add('Approval cancelled', 'cancelled');
1476
+ activity?.finish('cancelled', 'Cancelled');
1477
+ setMintActivity('idle');
1478
+ });
1479
+
1480
+ actions.appendChild(approveBtn);
1481
+ actions.appendChild(cancelBtn);
1482
+ card.appendChild(content);
1483
+ card.appendChild(actions);
1484
+
1485
+ messageDiv.querySelector('.message-bubble')?.appendChild(card);
1169
1486
  }
1170
1487
 
1171
1488
  function showTyping() {
@@ -1300,31 +1617,55 @@ async function sendTextMessage(text, options = {}) {
1300
1617
  rememberConversationLanguage(displayText || cleanText);
1301
1618
  }
1302
1619
 
1620
+ const activity = shouldShowAgentActivity(options) ? createAgentActivityCard() : null;
1621
+ const contextStep = activity?.add('Preparing desktop context', 'running');
1622
+
1303
1623
  // Show typing early so user knows we are processing
1304
1624
  showTyping();
1305
1625
  setMintActivity('thinking');
1306
1626
 
1627
+ let messageToSend = cleanText;
1628
+
1307
1629
  // Check Smart Context Toggle
1308
1630
  const smartToggle = document.getElementById('smart-context-toggle');
1309
1631
  if (allowSmartContext && smartToggle && smartToggle.checked && !imageToSend) {
1310
1632
  try {
1311
- const silentCapture = await window.api.captureSilentScreen();
1633
+ const [silentCapture, smartContext] = await Promise.all([
1634
+ window.api.captureSilentScreen(),
1635
+ window.api.getSmartContext ? window.api.getSmartContext() : Promise.resolve(null)
1636
+ ]);
1312
1637
  if (silentCapture) {
1313
1638
  // Set imageToSend so it gets sent to the API, but we already appended the chat bubble
1314
1639
  imageToSend = silentCapture;
1315
1640
  }
1641
+ if (smartContext) {
1642
+ messageToSend = appendSmartContextToMessage(cleanText, smartContext);
1643
+ }
1644
+ if (activity && contextStep) {
1645
+ activity.update(
1646
+ contextStep,
1647
+ 'done',
1648
+ 'Read Smart Context',
1649
+ describeSmartContextActivity(smartContext, Boolean(silentCapture))
1650
+ );
1651
+ }
1316
1652
  } catch (err) {
1317
1653
  console.error("Smart Context capture failed:", err);
1654
+ activity?.update(contextStep, 'error', 'Smart Context unavailable', err.message || '');
1318
1655
  }
1656
+ } else if (activity && contextStep) {
1657
+ activity.update(contextStep, 'skipped', 'Smart Context skipped', imageToSend ? 'image already attached' : 'toggle is off');
1319
1658
  }
1320
1659
 
1321
1660
  // Hide proactive bar if user is actively typing a message
1322
1661
  hideProactiveBar();
1662
+ const modelStep = activity?.add('Waiting for model response', 'running');
1323
1663
 
1324
1664
  try {
1325
1665
  // Send to main process (text, image, audio=null)
1326
- const response = await window.api.sendMessage(cleanText, imageToSend, null);
1666
+ const response = await window.api.sendMessage(messageToSend, imageToSend, null);
1327
1667
  removeTyping();
1668
+ activity?.update(modelStep, 'done', 'Model response received');
1328
1669
 
1329
1670
  if (typeof response.response !== 'string') {
1330
1671
  response.response = normalizeAiText(response.response);
@@ -1332,6 +1673,7 @@ async function sendTextMessage(text, options = {}) {
1332
1673
 
1333
1674
  // Handle system_info action: fetch data and append to AI message
1334
1675
  if (response.action && response.action.type === 'system_info') {
1676
+ const infoStep = activity?.add('Running local info action', 'running', describeActionActivity(response.action));
1335
1677
  const city = (response.action.target || '').trim();
1336
1678
  // Only treat as weather if city looks like a real location name (not blank, not 'date', not 'time')
1337
1679
  const weatherKeywords = ['date', 'time', 'วัน', 'เวลา', 'today', 'now'];
@@ -1341,12 +1683,14 @@ async function sendTextMessage(text, options = {}) {
1341
1683
  // Weather query
1342
1684
  const weather = await window.api.getWeather(city);
1343
1685
  response.response += `\n\n🌡️ ${weather.data}`;
1686
+ activity?.update(infoStep, 'done', 'Weather info added', city);
1344
1687
  } else {
1345
1688
  // General system info (date, time, RAM, CPU)
1346
1689
  const info = await window.api.getSystemInfo();
1347
1690
  const machine = info.machine && info.machine.display ? `\n🖥️ รุ่นเครื่อง: ${info.machine.display}` : '';
1348
1691
  const distro = info.distro ? `\nระบบ: ${info.distro}` : '';
1349
1692
  response.response += `\n\n📅 วันนี้: ${info.date}\n⏰ เวลา: ${info.time}${machine}${distro}\n💻 CPU: ${info.cpu.model} (${info.cpu.cores} คอร์)\n💻 RAM: ${info.ram.used} / ${info.ram.total} (${info.ram.percent})`;
1693
+ activity?.update(infoStep, 'done', 'System info added');
1350
1694
  }
1351
1695
  }
1352
1696
 
@@ -1362,12 +1706,27 @@ async function sendTextMessage(text, options = {}) {
1362
1706
  notifyAiIfNeeded();
1363
1707
 
1364
1708
  // Append action card if applicable
1365
- if (response.action && response.action.type !== 'none' && response.action.type !== 'system_info') {
1709
+ if (response.approval?.required) {
1710
+ activity?.add('Selected action', 'approval', describeActionActivity(response.approval.action));
1711
+ activity?.add('Waiting for approval', 'running', response.approval.reason || '');
1712
+ activity?.finish('waiting', 'Waiting');
1713
+ appendApprovalCard(msgDiv, response.approval, activity);
1714
+ } else if (response.action && response.action.type !== 'none' && response.action.type !== 'system_info') {
1715
+ activity?.add('Selected action', 'done', describeActionActivity(response.action));
1366
1716
  appendActionCard(msgDiv, response.action);
1717
+ activity?.finish('done', 'Completed');
1718
+ } else if (response.action && response.action.type === 'system_info') {
1719
+ activity?.add('Selected action', 'done', describeActionActivity(response.action));
1720
+ activity?.finish('done', 'Completed');
1721
+ } else {
1722
+ activity?.add('No desktop action selected', 'done');
1723
+ activity?.finish('done', 'Completed');
1367
1724
  }
1368
1725
  } catch (error) {
1369
1726
  removeTyping();
1370
1727
  setMintActivity('error');
1728
+ activity?.update(modelStep, 'error', 'Model request failed', error.message || '');
1729
+ activity?.finish('error', 'Failed');
1371
1730
  appendMessage("Sorry, I encountered an error communicating with the main process.", 'ai');
1372
1731
  console.error(error);
1373
1732
  resumeSpeechIfNeeded();
@@ -8,6 +8,7 @@ const DEFAULT_CONFIG = {
8
8
  apiKey: '',
9
9
  geminiModel: 'gemini-2.5-flash',
10
10
  language: 'th-TH',
11
+ assistantMode: 'chat',
11
12
  proactiveInterval: 60,
12
13
  proactiveCooldown: 120,
13
14
  glassBlur: 'blur(16px)',
@@ -57,7 +57,7 @@ spotlightInput.addEventListener('input', () => {
57
57
  label: `Result: ${result}`,
58
58
  desc: 'Calculation result (Press Enter to copy)',
59
59
  icon: '🧮',
60
- action: { type: 'copy', value: result.toString() }
60
+ action: { type: 'clipboard_write', target: result.toString() }
61
61
  }]);
62
62
  return;
63
63
  } catch {}
@@ -119,17 +119,21 @@ spotlightInput.addEventListener('keydown', (e) => {
119
119
  }
120
120
  });
121
121
 
122
- function handleAction(action) {
122
+ async function handleAction(action) {
123
123
  if (action.type === 'chat') {
124
124
  window.spotlightAPI.submit(action.query);
125
- } else if (action.type === 'open_url') {
126
- window.spotlightAPI.submit(`เปิดเว็บ ${action.target}`);
127
- } else if (action.type === 'open_app') {
128
- window.spotlightAPI.submit(`เปิดโปรแกรม ${action.target}`);
129
- } else if (action.type === 'copy') {
130
- // We need a clipboard API in spotlight preload or just send as chat message that triggers copy
131
- window.spotlightAPI.submit(`copy ${action.value}`);
125
+ return;
126
+ }
127
+
128
+ if (window.spotlightAPI.executeAction) {
129
+ const result = await window.spotlightAPI.executeAction(action);
130
+ if (!result || result.success === false) {
131
+ window.spotlightAPI.submit(`Spotlight action failed: ${result?.message || 'Unknown error'}`);
132
+ }
133
+ return;
132
134
  }
135
+
136
+ window.spotlightAPI.submit(action.target || action.value || '');
133
137
  }
134
138
 
135
139
  // Auto-focus on show