@pheem49/mint 1.4.2 → 1.5.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 (97) hide show
  1. package/GUIDE_TH.md +113 -0
  2. package/README.md +267 -78
  3. package/assets/CLI_Screen.png +0 -0
  4. package/main.js +76 -890
  5. package/mint-cli-logic.js +3 -107
  6. package/mint-cli.js +594 -29
  7. package/models/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
  8. package/models/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +14 -0
  9. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +10 -0
  10. 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 +15 -0
  11. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json +10 -0
  12. package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json +50 -0
  13. package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json +10 -0
  14. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +10 -0
  15. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json +10 -0
  16. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json +10 -0
  17. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png +0 -0
  18. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png +0 -0
  19. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png +0 -0
  20. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png +0 -0
  21. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json +1498 -0
  22. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3 +0 -0
  23. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +47 -0
  24. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json +6658 -0
  25. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json +1299 -0
  26. 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 +23 -0
  27. package/package.json +37 -4
  28. package/src/AI_Brain/Gemini_API.js +223 -65
  29. package/src/AI_Brain/autonomous_brain.js +11 -0
  30. package/src/AI_Brain/behavior_memory.js +26 -5
  31. package/src/AI_Brain/headless_agent.js +4 -0
  32. package/src/AI_Brain/knowledge_base.js +61 -8
  33. package/src/AI_Brain/memory_store.js +354 -10
  34. package/src/Automation_Layer/file_operations.js +1 -1
  35. package/src/CLI/chat_router.js +20 -7
  36. package/src/CLI/chat_ui.js +596 -825
  37. package/src/CLI/code_agent.js +347 -56
  38. package/src/CLI/gmail_auth.js +210 -0
  39. package/src/CLI/image_input.js +90 -0
  40. package/src/CLI/list_features.js +2 -0
  41. package/src/CLI/onboarding.js +364 -55
  42. package/src/CLI/updater.js +210 -0
  43. package/src/Channels/brave_search_bridge.js +35 -0
  44. package/src/Channels/discord_bridge.js +68 -0
  45. package/src/Channels/google_search_bridge.js +38 -0
  46. package/src/Channels/line_bridge.js +60 -0
  47. package/src/Channels/slack_bridge.js +53 -0
  48. package/src/Channels/telegram_bridge.js +49 -0
  49. package/src/Channels/whatsapp_bridge.js +55 -0
  50. package/src/Command_Parser/parser.js +12 -1
  51. package/src/Plugins/gmail.js +251 -0
  52. package/src/Plugins/google_calendar.js +245 -19
  53. package/src/Plugins/notion.js +256 -0
  54. package/src/System/action_executor.js +178 -0
  55. package/src/System/bridge_manager.js +76 -0
  56. package/src/System/chat_history_manager.js +23 -5
  57. package/src/System/config_manager.js +71 -7
  58. package/src/System/custom_workflows.js +31 -2
  59. package/src/System/google_tts_urls.js +51 -0
  60. package/src/System/granular_automation.js +122 -53
  61. package/src/System/ipc_handlers.js +238 -0
  62. package/src/System/proactive_loop.js +153 -0
  63. package/src/System/safety_manager.js +273 -0
  64. package/src/System/sandbox_runner.js +182 -0
  65. package/src/System/screen_capture.js +175 -0
  66. package/src/System/system_automation.js +127 -81
  67. package/src/System/system_info.js +70 -0
  68. package/src/System/task_manager.js +15 -5
  69. package/src/System/tool_registry.js +280 -0
  70. package/src/System/window_manager.js +212 -0
  71. package/src/UI/live2d_manager.js +368 -0
  72. package/src/UI/renderer.js +208 -24
  73. package/src/UI/settings.html +24 -0
  74. package/src/UI/settings.js +14 -4
  75. package/src/UI/styles.css +466 -32
  76. package/.codex +0 -0
  77. package/docs/assets/Agent_Mint.png +0 -0
  78. package/docs/assets/CLI_Screen.png +0 -0
  79. package/docs/assets/Settings.png +0 -0
  80. package/docs/assets/icon.png +0 -0
  81. package/docs/index.html +0 -132
  82. package/docs/style.css +0 -579
  83. package/index.html +0 -16
  84. package/src/UI/index.html +0 -126
  85. package/tech_news.txt +0 -3
  86. package/test_knowledge.txt +0 -3
  87. package/tests/agent_orchestrator.test.js +0 -41
  88. package/tests/chat_router.test.js +0 -42
  89. package/tests/code_agent.test.js +0 -69
  90. package/tests/config_manager.test.js +0 -141
  91. package/tests/docker.test.js +0 -46
  92. package/tests/file_operations.test.js +0 -57
  93. package/tests/memory_store.test.js +0 -185
  94. package/tests/provider_routing.test.js +0 -67
  95. package/tests/spotify.test.js +0 -201
  96. package/tests/system_monitor.test.js +0 -37
  97. package/tests/workspace_manager.test.js +0 -56
@@ -1,88 +1,157 @@
1
- const { exec } = require('child_process');
1
+ const { execFile, spawnSync } = require('child_process');
2
2
  const { screen } = require('electron');
3
3
 
4
- /**
5
- * GranularAutomation handles low-level OS input via xdotool.
6
- * It uses a normalized coordinate system (0-1000).
7
- */
4
+ function commandExists(command) {
5
+ const lookup = process.platform === 'win32' ? 'where' : 'which';
6
+ const result = spawnSync(lookup, [command], { encoding: 'utf8', shell: false });
7
+ return result.status === 0;
8
+ }
9
+
10
+ function run(command, args = []) {
11
+ return new Promise((resolve, reject) => {
12
+ execFile(command, args, (err, stdout, stderr) => {
13
+ if (err) {
14
+ err.stderr = stderr;
15
+ reject(err);
16
+ return;
17
+ }
18
+ resolve(stdout);
19
+ });
20
+ });
21
+ }
22
+
23
+ function unsupported(feature) {
24
+ throw new Error(`${feature} is not supported on ${process.platform} by the current input automation provider.`);
25
+ }
26
+
27
+ function escapePowerShellSingleQuoted(value) {
28
+ return String(value || '').replace(/'/g, "''");
29
+ }
30
+
31
+ function keyToMacKey(key) {
32
+ const value = String(key || '').trim();
33
+ const map = {
34
+ Enter: 'return',
35
+ Return: 'return',
36
+ Escape: 'escape',
37
+ Esc: 'escape',
38
+ Space: 'space',
39
+ Backspace: 'delete',
40
+ Delete: 'forward delete',
41
+ Tab: 'tab'
42
+ };
43
+ return map[value] || value;
44
+ }
45
+
8
46
  class GranularAutomation {
9
47
  constructor() {
10
- this.screenWidth = 1920; // Default fallback
48
+ this.screenWidth = 1920;
11
49
  this.screenHeight = 1080;
12
50
  this.updateScreenSize();
13
51
  }
14
52
 
15
53
  updateScreenSize() {
16
54
  try {
17
- // In Electron main process, we can use the screen module
18
55
  const primaryDisplay = screen.getPrimaryDisplay();
19
56
  if (primaryDisplay && primaryDisplay.size) {
20
57
  this.screenWidth = primaryDisplay.size.width;
21
58
  this.screenHeight = primaryDisplay.size.height;
22
- console.log(`[Automation] Screen detected: ${this.screenWidth}x${this.screenHeight}`);
23
59
  }
24
- } catch (e) {
25
- // Fallback for CLI or cases where screen module is unavailable
26
- exec('xdpyinfo | grep dimensions', (err, stdout) => {
27
- if (!err && stdout) {
28
- const match = stdout.match(/(\d+)x(\d+) pixels/);
29
- if (match) {
30
- this.screenWidth = parseInt(match[1]);
31
- this.screenHeight = parseInt(match[2]);
32
- console.log(`[Automation] Screen detected via xdpyinfo: ${this.screenWidth}x${this.screenHeight}`);
33
- }
34
- }
35
- });
60
+ } catch (_) {
61
+ // Electron screen can be unavailable in CLI-only contexts.
36
62
  }
37
63
  }
38
64
 
39
65
  scaleX(x) {
40
- return Math.round((x / 1000) * this.screenWidth);
66
+ return Math.round((Number(x) / 1000) * this.screenWidth);
41
67
  }
42
68
 
43
69
  scaleY(y) {
44
- return Math.round((y / 1000) * this.screenHeight);
70
+ return Math.round((Number(y) / 1000) * this.screenHeight);
45
71
  }
46
72
 
47
- run(command) {
48
- return new Promise((resolve, reject) => {
49
- exec(command, (err, stdout, stderr) => {
50
- if (err) {
51
- console.error(`[Automation] xdotool error: ${stderr}`);
52
- reject(err);
53
- } else {
54
- resolve(stdout);
55
- }
56
- });
57
- });
73
+ provider() {
74
+ if (process.platform === 'darwin') return macProvider;
75
+ if (process.platform === 'win32') return windowsProvider;
76
+ return linuxProvider;
58
77
  }
59
78
 
60
- async mouseMove(x, y) {
61
- const sx = this.scaleX(x);
62
- const sy = this.scaleY(y);
63
- console.log(`[Automation] Moving mouse to ${sx}, ${sy}`);
64
- return this.run(`xdotool mousemove ${sx} ${sy}`);
79
+ mouseMove(x, y) {
80
+ return this.provider().mouseMove(this.scaleX(x), this.scaleY(y));
65
81
  }
66
82
 
67
- async mouseClick(x, y, button = 1) {
68
- const sx = this.scaleX(x);
69
- const sy = this.scaleY(y);
70
- console.log(`[Automation] Clicking ${button} at ${sx}, ${sy}`);
71
- // move first then click to be safe
72
- return this.run(`xdotool mousemove ${sx} ${sy} click ${button}`);
83
+ mouseClick(x, y, button = 1) {
84
+ return this.provider().mouseClick(this.scaleX(x), this.scaleY(y), button);
73
85
  }
74
86
 
75
- async typeText(text) {
76
- console.log(`[Automation] Typing: ${text}`);
77
- // Escape double quotes for shell
78
- const escaped = text.replace(/"/g, '\\"');
79
- return this.run(`xdotool type "${escaped}"`);
87
+ typeText(text) {
88
+ return this.provider().typeText(String(text || ''));
80
89
  }
81
90
 
82
- async keyTap(key) {
83
- console.log(`[Automation] Key tap: ${key}`);
84
- return this.run(`xdotool key "${key}"`);
91
+ keyTap(key) {
92
+ return this.provider().keyTap(String(key || ''));
85
93
  }
86
94
  }
87
95
 
88
- module.exports = new GranularAutomation();
96
+ const linuxProvider = {
97
+ mouseMove: (x, y) => run('xdotool', ['mousemove', String(x), String(y)]),
98
+ mouseClick: (x, y, button = 1) => run('xdotool', ['mousemove', String(x), String(y), 'click', String(button)]),
99
+ typeText: (text) => run('xdotool', ['type', text]),
100
+ keyTap: (key) => run('xdotool', ['key', key])
101
+ };
102
+
103
+ const macProvider = {
104
+ mouseMove(x, y) {
105
+ if (!commandExists('cliclick')) return unsupported('Mouse move');
106
+ return run('cliclick', [`m:${x},${y}`]);
107
+ },
108
+ mouseClick(x, y) {
109
+ if (!commandExists('cliclick')) return unsupported('Mouse click');
110
+ return run('cliclick', [`c:${x},${y}`]);
111
+ },
112
+ typeText(text) {
113
+ if (commandExists('cliclick')) return run('cliclick', [`t:${text}`]);
114
+ return run('osascript', ['-e', `tell application "System Events" to keystroke ${JSON.stringify(text)}`]);
115
+ },
116
+ keyTap(key) {
117
+ const macKey = keyToMacKey(key);
118
+ if (commandExists('cliclick')) return run('cliclick', [`kp:${macKey}`]);
119
+ if (macKey.length === 1) {
120
+ return run('osascript', ['-e', `tell application "System Events" to keystroke ${JSON.stringify(macKey)}`]);
121
+ }
122
+ return unsupported('Special key tap without cliclick');
123
+ }
124
+ };
125
+
126
+ const windowsProvider = {
127
+ mouseMove(x, y) {
128
+ const script = `[void][Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(${x}, ${y})`;
129
+ return run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script]);
130
+ },
131
+ mouseClick(x, y, button = 1) {
132
+ const down = Number(button) === 2 ? '0x0008' : '0x0002';
133
+ const up = Number(button) === 2 ? '0x0010' : '0x0004';
134
+ const script = [
135
+ "Add-Type -MemberDefinition '[DllImport(\"user32.dll\")] public static extern bool SetCursorPos(int X,int Y); [DllImport(\"user32.dll\")] public static extern void mouse_event(int dwFlags,int dx,int dy,int dwData,int dwExtraInfo);' -Name NativeMouse -Namespace Mint;",
136
+ `[Mint.NativeMouse]::SetCursorPos(${x}, ${y}) | Out-Null;`,
137
+ `[Mint.NativeMouse]::mouse_event(${down},0,0,0,0);`,
138
+ `[Mint.NativeMouse]::mouse_event(${up},0,0,0,0);`
139
+ ].join(' ');
140
+ return run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script]);
141
+ },
142
+ typeText(text) {
143
+ const safe = escapePowerShellSingleQuoted(text);
144
+ const script = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${safe}')`;
145
+ return run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script]);
146
+ },
147
+ keyTap(key) {
148
+ const safe = escapePowerShellSingleQuoted(key);
149
+ const script = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('{${safe}}')`;
150
+ return run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script]);
151
+ }
152
+ };
153
+
154
+ const instance = new GranularAutomation();
155
+ instance._providers = { linuxProvider, macProvider, windowsProvider, commandExists };
156
+
157
+ module.exports = instance;
@@ -0,0 +1,238 @@
1
+ function registerIpcHandlers({
2
+ app,
3
+ ipcMain,
4
+ shell,
5
+ clipboard,
6
+ windowManager,
7
+ proactiveLoop,
8
+ screenCapture,
9
+ services
10
+ }) {
11
+ const {
12
+ handleChat,
13
+ resetChat,
14
+ getChatTranscript,
15
+ refreshApiKeyFromConfig,
16
+ getSystemInfo,
17
+ getWeather,
18
+ readConfig,
19
+ writeConfig,
20
+ parseCommand,
21
+ executeAction,
22
+ getGoogleTtsUrls,
23
+ customWorkflows
24
+ } = services;
25
+
26
+ ipcMain.handle('chat-message', async (event, message, base64Image = null, base64Audio = null) => {
27
+ try {
28
+ const rawResponse = await handleChat(message, base64Image, base64Audio);
29
+ const aiResponse = parseCommand(rawResponse);
30
+
31
+ if (aiResponse.action && aiResponse.action.type !== 'none') {
32
+ try {
33
+ const actionResult = await executeAction(aiResponse.action, { clipboard });
34
+ if (actionResult && typeof actionResult === 'string') {
35
+ aiResponse.response += `\n\n${actionResult}`;
36
+ }
37
+ } catch (err) {
38
+ console.error("Action execution error:", err);
39
+ aiResponse.response += "\n\n(Note: I tried to execute the action, but an error occurred.)";
40
+ }
41
+ }
42
+
43
+ return aiResponse;
44
+ } catch (error) {
45
+ console.error('Chat error:', error);
46
+ return { response: 'Error communicating with Gemini API. Check your console and API key.', action: { type: 'none' } };
47
+ }
48
+ });
49
+
50
+ ipcMain.on('close-window', () => {
51
+ const mainWindow = windowManager.getMainWindow();
52
+ if (mainWindow) mainWindow.hide();
53
+ });
54
+
55
+ ipcMain.on('minimize-window', () => {
56
+ const mainWindow = windowManager.getMainWindow();
57
+ if (mainWindow) mainWindow.minimize();
58
+ });
59
+
60
+ ipcMain.on('quit-app', () => {
61
+ app.isQuiting = true;
62
+ app.quit();
63
+ });
64
+
65
+ ipcMain.on('maximize-window', () => {
66
+ const mainWindow = windowManager.getMainWindow();
67
+ if (!mainWindow) return;
68
+ if (mainWindow.isMaximized()) {
69
+ mainWindow.unmaximize();
70
+ } else {
71
+ mainWindow.maximize();
72
+ }
73
+ });
74
+
75
+ ipcMain.handle('reset-chat', () => {
76
+ resetChat();
77
+ return { success: true };
78
+ });
79
+
80
+ ipcMain.handle('get-chat-history', () => getChatTranscript());
81
+
82
+ ipcMain.handle('open-settings', () => {
83
+ windowManager.createSettingsWindow();
84
+ });
85
+
86
+ ipcMain.handle('get-settings', () => readConfig());
87
+
88
+ ipcMain.handle('save-settings', (event, config) => {
89
+ console.log('[Settings] Saving new config. MCP Servers count:', Object.keys(config.mcpServers || {}).length);
90
+ const result = writeConfig(config);
91
+ refreshApiKeyFromConfig();
92
+
93
+ const mainWindow = windowManager.getMainWindow();
94
+ if (mainWindow && !mainWindow.isDestroyed()) {
95
+ mainWindow.webContents.send('settings-changed', config);
96
+ }
97
+
98
+ if (proactiveLoop.isRunning()) {
99
+ proactiveLoop.start(config.proactiveInterval);
100
+ }
101
+
102
+ if (config.enableCustomWorkflows !== false) {
103
+ customWorkflows.startMonitoring(mainWindow.webContents);
104
+ } else {
105
+ customWorkflows.stopMonitoring();
106
+ }
107
+
108
+ if (config.showDesktopWidget === false) {
109
+ windowManager.closeWidgetWindow();
110
+ } else {
111
+ windowManager.ensureWidgetWindow();
112
+ }
113
+
114
+ return result;
115
+ });
116
+
117
+ ipcMain.on('set-ai-state', (event, state) => {
118
+ const widgetWindow = windowManager.getWidgetWindow();
119
+ if (widgetWindow && !widgetWindow.isDestroyed()) {
120
+ widgetWindow.webContents.send('widget-state', state);
121
+ }
122
+ });
123
+
124
+ ipcMain.on('close-settings', () => {
125
+ const settingsWindow = windowManager.getSettingsWindow();
126
+ if (settingsWindow) settingsWindow.close();
127
+ });
128
+
129
+ ipcMain.handle('open-custom-workflows', () => {
130
+ customWorkflows.openConfigFile();
131
+ });
132
+
133
+ ipcMain.handle('reload-custom-workflows', () => {
134
+ customWorkflows.loadWorkflows();
135
+ return { success: true };
136
+ });
137
+
138
+ ipcMain.on('spotlight-close', () => {
139
+ const spotlightWindow = windowManager.getSpotlightWindow();
140
+ if (spotlightWindow) spotlightWindow.close();
141
+ });
142
+
143
+ ipcMain.on('spotlight-hide', () => {
144
+ const spotlightWindow = windowManager.getSpotlightWindow();
145
+ if (spotlightWindow) spotlightWindow.hide();
146
+ });
147
+
148
+ ipcMain.on('spotlight-submit', async (event, query) => {
149
+ console.log('[Spotlight] Submit:', query);
150
+ const spotlightWindow = windowManager.getSpotlightWindow();
151
+ if (spotlightWindow) spotlightWindow.hide();
152
+
153
+ const mainWindow = windowManager.getMainWindow();
154
+ if (mainWindow) {
155
+ mainWindow.show();
156
+ mainWindow.webContents.send('spotlight-to-chat', query);
157
+ }
158
+ });
159
+
160
+ ipcMain.on('spotlight-resize', (event, width, height) => {
161
+ const spotlightWindow = windowManager.getSpotlightWindow();
162
+ if (spotlightWindow) spotlightWindow.setSize(width, height);
163
+ });
164
+
165
+ ipcMain.handle('open-external', (event, url) => {
166
+ shell.openExternal(url);
167
+ });
168
+
169
+ ipcMain.handle('clipboard-read', () => clipboard.readText());
170
+
171
+ ipcMain.handle('clipboard-write', (event, text) => {
172
+ clipboard.writeText(text);
173
+ return { success: true };
174
+ });
175
+
176
+ ipcMain.handle('get-tts-urls', async (event, text) => {
177
+ try {
178
+ const isThai = /[\u0E00-\u0E7F]/.test(text);
179
+ return getGoogleTtsUrls(text, {
180
+ lang: isThai ? 'th' : 'en',
181
+ host: 'https://translate.google.com',
182
+ });
183
+ } catch (e) {
184
+ console.error("TTS Error:", e);
185
+ return [];
186
+ }
187
+ });
188
+
189
+ ipcMain.handle('get-system-info', async () => getSystemInfo());
190
+ ipcMain.handle('get-weather', async (event, city) => getWeather(city));
191
+
192
+ ipcMain.handle('start-screen-capture', () => screenCapture.startScreenCapture());
193
+ ipcMain.on('vision-selection', (event, base64Image) => screenCapture.handleSelection(base64Image));
194
+ ipcMain.on('vision-translate-start', (event, rect) => screenCapture.startLiveTranslate(rect));
195
+ ipcMain.on('vision-translate-stop', () => screenCapture.stopLiveTranslate());
196
+ ipcMain.on('vision-overlay-interactable', (event, isInteractable) => screenCapture.setOverlayInteractable(isInteractable));
197
+ ipcMain.on('vision-cancel', () => screenCapture.cancel());
198
+ ipcMain.handle('capture-silent-screen', () => screenCapture.captureSilentScreen());
199
+
200
+ ipcMain.on('toggle-proactive', (event, isOn) => {
201
+ if (isOn) {
202
+ proactiveLoop.start();
203
+ } else {
204
+ proactiveLoop.stop();
205
+ }
206
+ });
207
+
208
+ ipcMain.on('record-behavior', (event, contextDescription) => {
209
+ proactiveLoop.recordBehavior(contextDescription);
210
+ });
211
+
212
+ ipcMain.handle('execute-proactive-action', async (event, action) => {
213
+ if (!action || action.type === 'none') {
214
+ return { success: false, message: 'ไม่มี action ที่จะดำเนินการค่ะ' };
215
+ }
216
+ try {
217
+ const result = await executeAction(action, { clipboard });
218
+ const messages = {
219
+ open_url: `เปิดเว็บไซต์ให้แล้วค่ะ 🌐`,
220
+ open_app: `เปิดแอป ${action.target} ให้แล้วค่ะ 🚀`,
221
+ search: `ค้นหา "${action.target}" ให้แล้วค่ะ 🔍`,
222
+ web_automation: result || 'ดำเนินการเสร็จแล้วค่ะ ✅',
223
+ create_folder: `สร้างโฟลเดอร์ "${action.target}" แล้วค่ะ 📁`,
224
+ clipboard_write: `คัดลอกข้อความแล้วค่ะ 📋`,
225
+ learn_file: result || `เรียนรู้เอกสารเรียบร้อยค่ะ 📚`,
226
+ };
227
+ return {
228
+ success: true,
229
+ message: messages[action.type] || 'ดำเนินการเสร็จแล้วค่ะ ✅'
230
+ };
231
+ } catch (err) {
232
+ console.error('[ProactiveAction] Error:', err);
233
+ return { success: false, message: `เกิดข้อผิดพลาด: ${err.message}` };
234
+ }
235
+ });
236
+ }
237
+
238
+ module.exports = { registerIpcHandlers };
@@ -0,0 +1,153 @@
1
+ const { BrowserWindow, desktopCapturer, screen, powerMonitor } = require('electron');
2
+ const path = require('path');
3
+
4
+ const IDLE_THRESHOLD_SEC = 300;
5
+
6
+ let proactiveEngine = null;
7
+ function getProactiveEngine() {
8
+ if (!proactiveEngine) {
9
+ proactiveEngine = require('../AI_Brain/proactive_engine');
10
+ }
11
+ return proactiveEngine;
12
+ }
13
+
14
+ let behaviorMemory = null;
15
+ function getBehaviorMemory() {
16
+ if (!behaviorMemory) {
17
+ behaviorMemory = require('../AI_Brain/behavior_memory');
18
+ }
19
+ return behaviorMemory;
20
+ }
21
+
22
+ function createProactiveLoop({ app, projectRoot, readConfig, getMainWindow }) {
23
+ let proactiveGlowWindow = null;
24
+ let proactiveIntervalHandle = null;
25
+ let idleWatcherHandle = null;
26
+
27
+ async function runProactiveCycle() {
28
+ const mainWindow = getMainWindow();
29
+ if (!mainWindow || mainWindow.isDestroyed()) return;
30
+
31
+ try {
32
+ showProactiveGlow();
33
+
34
+ const primaryDisplay = screen.getPrimaryDisplay();
35
+ const width = Math.floor(primaryDisplay.size.width * 0.5);
36
+ const height = Math.floor(primaryDisplay.size.height * 0.5);
37
+ const sources = await desktopCapturer.getSources({
38
+ types: ['screen'],
39
+ thumbnailSize: { width, height }
40
+ });
41
+
42
+ const primarySource = sources[0];
43
+ if (!primarySource || !primarySource.thumbnail) return;
44
+
45
+ const base64Image = primarySource.thumbnail.toJPEG(60).toString('base64');
46
+ const { analyzeAndSuggest } = getProactiveEngine();
47
+ const { recordBehavior, getBehaviorSummary } = getBehaviorMemory();
48
+ const result = await analyzeAndSuggest(base64Image, getBehaviorSummary());
49
+
50
+ if (result && result.message && Array.isArray(result.suggestions)) {
51
+ if (result.context) recordBehavior(result.context);
52
+
53
+ const currentMainWindow = getMainWindow();
54
+ if (currentMainWindow && !currentMainWindow.isDestroyed()) {
55
+ currentMainWindow.webContents.send('proactive-suggestion', result);
56
+ }
57
+ }
58
+
59
+ hideProactiveGlow();
60
+ } catch (err) {
61
+ console.error('[Proactive] Cycle error:', err.message);
62
+ hideProactiveGlow();
63
+ }
64
+ }
65
+
66
+ function createProactiveGlowWindow() {
67
+ if (proactiveGlowWindow) return proactiveGlowWindow;
68
+ const { width, height } = screen.getPrimaryDisplay().bounds;
69
+
70
+ proactiveGlowWindow = new BrowserWindow({
71
+ width,
72
+ height,
73
+ x: 0,
74
+ y: 0,
75
+ frame: false,
76
+ transparent: true,
77
+ alwaysOnTop: true,
78
+ skipTaskbar: true,
79
+ focusable: false,
80
+ show: false,
81
+ webPreferences: {
82
+ nodeIntegration: false,
83
+ contextIsolation: true
84
+ }
85
+ });
86
+
87
+ proactiveGlowWindow.setIgnoreMouseEvents(true);
88
+ proactiveGlowWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
89
+ proactiveGlowWindow.setAlwaysOnTop(true, 'screen-saver');
90
+ proactiveGlowWindow.loadFile(path.join(projectRoot, 'src/UI/proactive-glow.html'));
91
+ proactiveGlowWindow.on('closed', () => { proactiveGlowWindow = null; });
92
+ return proactiveGlowWindow;
93
+ }
94
+
95
+ function showProactiveGlow() {
96
+ if (!proactiveGlowWindow) createProactiveGlowWindow();
97
+ if (proactiveGlowWindow) proactiveGlowWindow.showInactive();
98
+ }
99
+
100
+ function hideProactiveGlow() {
101
+ if (proactiveGlowWindow && !proactiveGlowWindow.isDestroyed()) {
102
+ proactiveGlowWindow.hide();
103
+ }
104
+ }
105
+
106
+ function start(intervalSec) {
107
+ stop();
108
+ const cfg = readConfig();
109
+ const ms = (intervalSec || cfg.proactiveInterval || 60) * 1000;
110
+ console.log(`[Proactive] Starting loop — interval: ${ms / 1000}s`);
111
+ proactiveIntervalHandle = setInterval(runProactiveCycle, ms);
112
+ }
113
+
114
+ function stop() {
115
+ if (proactiveIntervalHandle) {
116
+ clearInterval(proactiveIntervalHandle);
117
+ proactiveIntervalHandle = null;
118
+ console.log('[Proactive] Stopped proactive loop.');
119
+ }
120
+ }
121
+
122
+ function startIdleWatcher() {
123
+ if (idleWatcherHandle) return;
124
+ idleWatcherHandle = setInterval(() => {
125
+ if (!proactiveIntervalHandle) return;
126
+ if (!app.isReady()) return;
127
+
128
+ const idleSec = powerMonitor.getSystemIdleTime();
129
+ if (idleSec < IDLE_THRESHOLD_SEC) return;
130
+
131
+ console.log(`[System Idle] User idle for ${idleSec}s. Pausing Proactive loop to save resources.`);
132
+ stop();
133
+
134
+ const resumeChecker = setInterval(() => {
135
+ if (powerMonitor.getSystemIdleTime() < 10) {
136
+ console.log('[System Idle] User returned. Resuming Proactive loop.');
137
+ clearInterval(resumeChecker);
138
+ start();
139
+ }
140
+ }, 5000);
141
+ }, 60000);
142
+ }
143
+
144
+ return {
145
+ start,
146
+ stop,
147
+ startIdleWatcher,
148
+ isRunning: () => Boolean(proactiveIntervalHandle),
149
+ recordBehavior: (...args) => getBehaviorMemory().recordBehavior(...args)
150
+ };
151
+ }
152
+
153
+ module.exports = { createProactiveLoop };