@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.
- package/GUIDE_TH.md +113 -0
- package/README.md +267 -78
- package/assets/CLI_Screen.png +0 -0
- package/main.js +76 -890
- package/mint-cli-logic.js +3 -107
- package/mint-cli.js +594 -29
- package/models/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +14 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +10 -0
- 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
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json +50 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json +1498 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3 +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +47 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json +6658 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json +1299 -0
- 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
- package/package.json +37 -4
- package/src/AI_Brain/Gemini_API.js +223 -65
- package/src/AI_Brain/autonomous_brain.js +11 -0
- package/src/AI_Brain/behavior_memory.js +26 -5
- package/src/AI_Brain/headless_agent.js +4 -0
- package/src/AI_Brain/knowledge_base.js +61 -8
- package/src/AI_Brain/memory_store.js +354 -10
- package/src/Automation_Layer/file_operations.js +1 -1
- package/src/CLI/chat_router.js +20 -7
- package/src/CLI/chat_ui.js +596 -825
- package/src/CLI/code_agent.js +347 -56
- package/src/CLI/gmail_auth.js +210 -0
- package/src/CLI/image_input.js +90 -0
- package/src/CLI/list_features.js +2 -0
- package/src/CLI/onboarding.js +364 -55
- package/src/CLI/updater.js +210 -0
- package/src/Channels/brave_search_bridge.js +35 -0
- package/src/Channels/discord_bridge.js +68 -0
- package/src/Channels/google_search_bridge.js +38 -0
- package/src/Channels/line_bridge.js +60 -0
- package/src/Channels/slack_bridge.js +53 -0
- package/src/Channels/telegram_bridge.js +49 -0
- package/src/Channels/whatsapp_bridge.js +55 -0
- package/src/Command_Parser/parser.js +12 -1
- package/src/Plugins/gmail.js +251 -0
- package/src/Plugins/google_calendar.js +245 -19
- package/src/Plugins/notion.js +256 -0
- package/src/System/action_executor.js +178 -0
- package/src/System/bridge_manager.js +76 -0
- package/src/System/chat_history_manager.js +23 -5
- package/src/System/config_manager.js +71 -7
- package/src/System/custom_workflows.js +31 -2
- package/src/System/google_tts_urls.js +51 -0
- package/src/System/granular_automation.js +122 -53
- package/src/System/ipc_handlers.js +238 -0
- package/src/System/proactive_loop.js +153 -0
- package/src/System/safety_manager.js +273 -0
- package/src/System/sandbox_runner.js +182 -0
- package/src/System/screen_capture.js +175 -0
- package/src/System/system_automation.js +127 -81
- package/src/System/system_info.js +70 -0
- package/src/System/task_manager.js +15 -5
- package/src/System/tool_registry.js +280 -0
- package/src/System/window_manager.js +212 -0
- package/src/UI/live2d_manager.js +368 -0
- package/src/UI/renderer.js +208 -24
- package/src/UI/settings.html +24 -0
- package/src/UI/settings.js +14 -4
- package/src/UI/styles.css +466 -32
- package/.codex +0 -0
- package/docs/assets/Agent_Mint.png +0 -0
- package/docs/assets/CLI_Screen.png +0 -0
- package/docs/assets/Settings.png +0 -0
- package/docs/assets/icon.png +0 -0
- package/docs/index.html +0 -132
- package/docs/style.css +0 -579
- package/index.html +0 -16
- package/src/UI/index.html +0 -126
- package/tech_news.txt +0 -3
- package/test_knowledge.txt +0 -3
- package/tests/agent_orchestrator.test.js +0 -41
- package/tests/chat_router.test.js +0 -42
- package/tests/code_agent.test.js +0 -69
- package/tests/config_manager.test.js +0 -141
- package/tests/docker.test.js +0 -46
- package/tests/file_operations.test.js +0 -57
- package/tests/memory_store.test.js +0 -185
- package/tests/provider_routing.test.js +0 -67
- package/tests/spotify.test.js +0 -201
- package/tests/system_monitor.test.js +0 -37
- package/tests/workspace_manager.test.js +0 -56
|
@@ -1,88 +1,157 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { execFile, spawnSync } = require('child_process');
|
|
2
2
|
const { screen } = require('electron');
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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;
|
|
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 (
|
|
25
|
-
//
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
return this.run(`xdotool key "${key}"`);
|
|
91
|
+
keyTap(key) {
|
|
92
|
+
return this.provider().keyTap(String(key || ''));
|
|
85
93
|
}
|
|
86
94
|
}
|
|
87
95
|
|
|
88
|
-
|
|
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 };
|