copilot-liku-cli 0.0.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/ARCHITECTURE.md +411 -0
- package/CONFIGURATION.md +302 -0
- package/CONTRIBUTING.md +225 -0
- package/ELECTRON_README.md +121 -0
- package/INSTALLATION.md +350 -0
- package/LICENSE.md +1 -0
- package/PROJECT_STATUS.md +229 -0
- package/QUICKSTART.md +255 -0
- package/README.md +167 -0
- package/TESTING.md +274 -0
- package/package.json +61 -0
- package/scripts/start.js +30 -0
- package/src/assets/tray-icon.png +0 -0
- package/src/cli/commands/agent.js +327 -0
- package/src/cli/commands/click.js +108 -0
- package/src/cli/commands/drag.js +85 -0
- package/src/cli/commands/find.js +109 -0
- package/src/cli/commands/keys.js +132 -0
- package/src/cli/commands/mouse.js +79 -0
- package/src/cli/commands/repl.js +290 -0
- package/src/cli/commands/screenshot.js +72 -0
- package/src/cli/commands/scroll.js +74 -0
- package/src/cli/commands/start.js +67 -0
- package/src/cli/commands/type.js +57 -0
- package/src/cli/commands/wait.js +84 -0
- package/src/cli/commands/window.js +104 -0
- package/src/cli/liku.js +249 -0
- package/src/cli/util/output.js +174 -0
- package/src/main/agents/base-agent.js +410 -0
- package/src/main/agents/builder.js +484 -0
- package/src/main/agents/index.js +62 -0
- package/src/main/agents/orchestrator.js +362 -0
- package/src/main/agents/researcher.js +511 -0
- package/src/main/agents/state-manager.js +344 -0
- package/src/main/agents/supervisor.js +365 -0
- package/src/main/agents/verifier.js +452 -0
- package/src/main/ai-service.js +1633 -0
- package/src/main/index.js +2208 -0
- package/src/main/inspect-service.js +467 -0
- package/src/main/system-automation.js +1186 -0
- package/src/main/ui-automation/config.js +76 -0
- package/src/main/ui-automation/core/helpers.js +41 -0
- package/src/main/ui-automation/core/index.js +15 -0
- package/src/main/ui-automation/core/powershell.js +82 -0
- package/src/main/ui-automation/elements/finder.js +274 -0
- package/src/main/ui-automation/elements/index.js +14 -0
- package/src/main/ui-automation/elements/wait.js +66 -0
- package/src/main/ui-automation/index.js +164 -0
- package/src/main/ui-automation/interactions/element-click.js +211 -0
- package/src/main/ui-automation/interactions/high-level.js +230 -0
- package/src/main/ui-automation/interactions/index.js +47 -0
- package/src/main/ui-automation/keyboard/index.js +15 -0
- package/src/main/ui-automation/keyboard/input.js +179 -0
- package/src/main/ui-automation/mouse/click.js +186 -0
- package/src/main/ui-automation/mouse/drag.js +88 -0
- package/src/main/ui-automation/mouse/index.js +30 -0
- package/src/main/ui-automation/mouse/movement.js +51 -0
- package/src/main/ui-automation/mouse/scroll.js +116 -0
- package/src/main/ui-automation/screenshot.js +183 -0
- package/src/main/ui-automation/window/index.js +23 -0
- package/src/main/ui-automation/window/manager.js +305 -0
- package/src/main/utils/time.js +62 -0
- package/src/main/visual-awareness.js +597 -0
- package/src/renderer/chat/chat.js +671 -0
- package/src/renderer/chat/index.html +725 -0
- package/src/renderer/chat/preload.js +112 -0
- package/src/renderer/overlay/index.html +648 -0
- package/src/renderer/overlay/overlay.js +782 -0
- package/src/renderer/overlay/preload.js +90 -0
- package/src/shared/grid-math.js +82 -0
- package/src/shared/inspect-types.js +230 -0
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
// ===== STATE =====
|
|
2
|
+
let currentMode = 'passive';
|
|
3
|
+
let currentProvider = 'copilot';
|
|
4
|
+
let currentModel = 'gpt-4o';
|
|
5
|
+
let totalTokens = 0;
|
|
6
|
+
let messages = [];
|
|
7
|
+
let contextItems = [];
|
|
8
|
+
let pendingActions = null;
|
|
9
|
+
|
|
10
|
+
// ===== ELEMENTS =====
|
|
11
|
+
const chatHistory = document.getElementById('chat-history');
|
|
12
|
+
const messageInput = document.getElementById('message-input');
|
|
13
|
+
const sendButton = document.getElementById('send-button');
|
|
14
|
+
const passiveBtn = document.getElementById('passive-btn');
|
|
15
|
+
const selectionBtn = document.getElementById('selection-btn');
|
|
16
|
+
const minimizeBtn = document.getElementById('minimize-btn');
|
|
17
|
+
const closeBtn = document.getElementById('close-btn');
|
|
18
|
+
const captureBtn = document.getElementById('capture-btn');
|
|
19
|
+
const contextPanel = document.getElementById('context-panel');
|
|
20
|
+
const contextHeader = document.getElementById('context-header');
|
|
21
|
+
const contextContent = document.getElementById('context-content');
|
|
22
|
+
const contextCount = document.getElementById('context-count');
|
|
23
|
+
const providerSelect = document.getElementById('provider-select');
|
|
24
|
+
const modelSelect = document.getElementById('model-select');
|
|
25
|
+
const authStatus = document.getElementById('auth-status');
|
|
26
|
+
const tokenCount = document.getElementById('token-count');
|
|
27
|
+
|
|
28
|
+
// ===== TOKEN ESTIMATION =====
|
|
29
|
+
// Rough estimate: ~4 chars per token for English text
|
|
30
|
+
function estimateTokens(text) {
|
|
31
|
+
return Math.ceil(text.length / 4);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function updateTokenCount(additionalTokens = 0) {
|
|
35
|
+
totalTokens += additionalTokens;
|
|
36
|
+
if (tokenCount) {
|
|
37
|
+
tokenCount.textContent = `${totalTokens.toLocaleString()} tokens`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resetTokenCount() {
|
|
42
|
+
totalTokens = 0;
|
|
43
|
+
updateTokenCount();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ===== AUTH STATUS =====
|
|
47
|
+
function updateAuthStatus(status, provider) {
|
|
48
|
+
if (!authStatus) return;
|
|
49
|
+
|
|
50
|
+
authStatus.className = 'status-badge';
|
|
51
|
+
|
|
52
|
+
switch (status) {
|
|
53
|
+
case 'connected':
|
|
54
|
+
authStatus.classList.add('connected');
|
|
55
|
+
authStatus.textContent = `${getProviderName(provider)} Connected`;
|
|
56
|
+
break;
|
|
57
|
+
case 'pending':
|
|
58
|
+
authStatus.classList.add('pending');
|
|
59
|
+
authStatus.textContent = 'Authenticating...';
|
|
60
|
+
break;
|
|
61
|
+
case 'error':
|
|
62
|
+
authStatus.classList.add('disconnected');
|
|
63
|
+
authStatus.textContent = 'Auth Error';
|
|
64
|
+
break;
|
|
65
|
+
default:
|
|
66
|
+
authStatus.classList.add('disconnected');
|
|
67
|
+
authStatus.textContent = 'Not Connected';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getProviderName(provider) {
|
|
72
|
+
const names = {
|
|
73
|
+
copilot: 'Copilot',
|
|
74
|
+
openai: 'OpenAI',
|
|
75
|
+
anthropic: 'Anthropic',
|
|
76
|
+
ollama: 'Ollama'
|
|
77
|
+
};
|
|
78
|
+
return names[provider] || provider;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ===== PROVIDER FUNCTIONS =====
|
|
82
|
+
function setProvider(provider) {
|
|
83
|
+
currentProvider = provider;
|
|
84
|
+
if (window.electronAPI.setProvider) {
|
|
85
|
+
window.electronAPI.setProvider(provider);
|
|
86
|
+
}
|
|
87
|
+
// Also send as a command for backward compatibility
|
|
88
|
+
window.electronAPI.sendMessage(`/provider ${provider}`);
|
|
89
|
+
addMessage(`Switched to ${getProviderName(provider)}`, 'system');
|
|
90
|
+
|
|
91
|
+
// Show/hide model selector based on provider
|
|
92
|
+
updateModelSelector(provider);
|
|
93
|
+
|
|
94
|
+
// Check auth status for new provider
|
|
95
|
+
checkProviderAuth(provider);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ===== MODEL FUNCTIONS =====
|
|
99
|
+
function setModel(model) {
|
|
100
|
+
currentModel = model;
|
|
101
|
+
// Send model change command
|
|
102
|
+
window.electronAPI.sendMessage(`/model ${model}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function updateModelSelector(provider) {
|
|
106
|
+
if (!modelSelect) return;
|
|
107
|
+
|
|
108
|
+
// Only show model selector for Copilot
|
|
109
|
+
modelSelect.style.display = provider === 'copilot' ? 'block' : 'none';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ===== MESSAGE FUNCTIONS =====
|
|
113
|
+
function addMessage(text, type = 'agent', timestamp = Date.now(), extra = {}) {
|
|
114
|
+
const emptyState = chatHistory.querySelector('.empty-state');
|
|
115
|
+
if (emptyState) emptyState.remove();
|
|
116
|
+
|
|
117
|
+
const messageEl = document.createElement('div');
|
|
118
|
+
messageEl.className = `message ${type}`;
|
|
119
|
+
if (extra.subtype) messageEl.classList.add(extra.subtype);
|
|
120
|
+
|
|
121
|
+
const textEl = document.createElement('div');
|
|
122
|
+
textEl.textContent = text;
|
|
123
|
+
messageEl.appendChild(textEl);
|
|
124
|
+
|
|
125
|
+
const timestampEl = document.createElement('div');
|
|
126
|
+
timestampEl.className = 'timestamp';
|
|
127
|
+
timestampEl.textContent = new Date(timestamp).toLocaleTimeString();
|
|
128
|
+
messageEl.appendChild(timestampEl);
|
|
129
|
+
|
|
130
|
+
chatHistory.appendChild(messageEl);
|
|
131
|
+
chatHistory.scrollTop = chatHistory.scrollHeight;
|
|
132
|
+
|
|
133
|
+
messages.push({ text, type, timestamp, ...extra });
|
|
134
|
+
|
|
135
|
+
// Track tokens for user and agent messages
|
|
136
|
+
if (type === 'user' || type === 'agent') {
|
|
137
|
+
updateTokenCount(estimateTokens(text));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function sendMessage() {
|
|
142
|
+
const text = messageInput.value.trim();
|
|
143
|
+
if (!text) return;
|
|
144
|
+
|
|
145
|
+
addMessage(text, 'user');
|
|
146
|
+
window.electronAPI.sendMessage(text);
|
|
147
|
+
|
|
148
|
+
messageInput.value = '';
|
|
149
|
+
messageInput.style.height = 'auto';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ===== MODE FUNCTIONS =====
|
|
153
|
+
function updateModeDisplay() {
|
|
154
|
+
passiveBtn.classList.toggle('active', currentMode === 'passive');
|
|
155
|
+
selectionBtn.classList.toggle('active', currentMode === 'selection');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function setMode(mode) {
|
|
159
|
+
currentMode = mode;
|
|
160
|
+
window.electronAPI.setMode(mode);
|
|
161
|
+
updateModeDisplay();
|
|
162
|
+
|
|
163
|
+
if (mode === 'selection') {
|
|
164
|
+
addMessage('Selection mode active. Click dots on overlay or scroll to zoom.', 'system');
|
|
165
|
+
} else {
|
|
166
|
+
addMessage('Passive mode. Overlay is click-through.', 'system');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ===== CONTEXT PANEL FUNCTIONS =====
|
|
171
|
+
function addContextItem(data) {
|
|
172
|
+
contextItems.push(data);
|
|
173
|
+
updateContextPanel();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function updateContextPanel() {
|
|
177
|
+
contextCount.textContent = contextItems.length;
|
|
178
|
+
contextContent.innerHTML = '';
|
|
179
|
+
|
|
180
|
+
contextItems.forEach((item) => {
|
|
181
|
+
const itemEl = document.createElement('div');
|
|
182
|
+
itemEl.className = 'context-item';
|
|
183
|
+
itemEl.innerHTML = `
|
|
184
|
+
<span class="dot-marker"></span>
|
|
185
|
+
<span>${item.label}</span>
|
|
186
|
+
<span class="coords">(${item.x}, ${item.y})</span>
|
|
187
|
+
`;
|
|
188
|
+
contextContent.appendChild(itemEl);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (contextItems.length > 0) {
|
|
192
|
+
contextPanel.classList.add('expanded');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function toggleContextPanel() {
|
|
197
|
+
contextPanel.classList.toggle('expanded');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ===== WINDOW CONTROLS =====
|
|
201
|
+
minimizeBtn.addEventListener('click', () => {
|
|
202
|
+
window.electronAPI.minimizeWindow();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
closeBtn.addEventListener('click', () => {
|
|
206
|
+
window.electronAPI.hideWindow();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// ===== CAPTURE FUNCTION =====
|
|
210
|
+
captureBtn.addEventListener('click', () => {
|
|
211
|
+
addMessage('Initiating screen capture...', 'system', Date.now(), { subtype: 'capture' });
|
|
212
|
+
window.electronAPI.captureScreen();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// ===== EVENT LISTENERS =====
|
|
216
|
+
sendButton.addEventListener('click', sendMessage);
|
|
217
|
+
|
|
218
|
+
messageInput.addEventListener('keydown', (e) => {
|
|
219
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
220
|
+
e.preventDefault();
|
|
221
|
+
sendMessage();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Auto-resize textarea
|
|
226
|
+
messageInput.addEventListener('input', () => {
|
|
227
|
+
messageInput.style.height = 'auto';
|
|
228
|
+
messageInput.style.height = Math.min(messageInput.scrollHeight, 120) + 'px';
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
passiveBtn.addEventListener('click', () => setMode('passive'));
|
|
232
|
+
selectionBtn.addEventListener('click', () => setMode('selection'));
|
|
233
|
+
contextHeader.addEventListener('click', toggleContextPanel);
|
|
234
|
+
|
|
235
|
+
// Provider selection
|
|
236
|
+
if (providerSelect) {
|
|
237
|
+
providerSelect.addEventListener('change', (e) => {
|
|
238
|
+
setProvider(e.target.value);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Model selection
|
|
243
|
+
if (modelSelect) {
|
|
244
|
+
modelSelect.addEventListener('change', (e) => {
|
|
245
|
+
setModel(e.target.value);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check provider auth status
|
|
250
|
+
function checkProviderAuth(provider) {
|
|
251
|
+
if (window.electronAPI.checkAuth) {
|
|
252
|
+
window.electronAPI.checkAuth(provider);
|
|
253
|
+
} else {
|
|
254
|
+
// Fallback: use /status command
|
|
255
|
+
window.electronAPI.sendMessage('/status');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ===== IPC LISTENERS =====
|
|
260
|
+
window.electronAPI.onDotSelected((data) => {
|
|
261
|
+
if (data.cancelled) {
|
|
262
|
+
addMessage('Selection cancelled', 'system');
|
|
263
|
+
setMode('passive');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
addMessage(`Selected: ${data.label} at (${data.x}, ${data.y})`, 'system');
|
|
268
|
+
addContextItem(data);
|
|
269
|
+
|
|
270
|
+
window.electronAPI.getState().then(state => {
|
|
271
|
+
currentMode = state.overlayMode;
|
|
272
|
+
updateModeDisplay();
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
window.electronAPI.onAgentResponse((data) => {
|
|
277
|
+
removeTypingIndicator();
|
|
278
|
+
const msgType = data.type === 'error' ? 'system' : 'agent';
|
|
279
|
+
|
|
280
|
+
// Check if response contains actions
|
|
281
|
+
if (data.hasActions && data.actionData && data.actionData.actions) {
|
|
282
|
+
console.log('[CHAT] Received agent response with actions:', data.actionData.actions.length);
|
|
283
|
+
|
|
284
|
+
// Show the AI's thought/explanation first (without the JSON)
|
|
285
|
+
const cleanText = data.text.replace(/```json[\s\S]*?```/g, '').trim();
|
|
286
|
+
if (cleanText) {
|
|
287
|
+
addMessage(cleanText, msgType, data.timestamp, {
|
|
288
|
+
provider: data.provider,
|
|
289
|
+
hasVisualContext: data.hasVisualContext
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Show action confirmation UI
|
|
294
|
+
showActionConfirmation(data.actionData);
|
|
295
|
+
} else {
|
|
296
|
+
// Normal response without actions
|
|
297
|
+
addMessage(data.text, msgType, data.timestamp, {
|
|
298
|
+
provider: data.provider,
|
|
299
|
+
hasVisualContext: data.hasVisualContext
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
if (window.electronAPI.onAgentTyping) {
|
|
305
|
+
window.electronAPI.onAgentTyping((data) => {
|
|
306
|
+
if (data.isTyping) {
|
|
307
|
+
showTypingIndicator();
|
|
308
|
+
} else {
|
|
309
|
+
removeTypingIndicator();
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (window.electronAPI.onScreenCaptured) {
|
|
315
|
+
window.electronAPI.onScreenCaptured((data) => {
|
|
316
|
+
if (data.error) {
|
|
317
|
+
addMessage(`Capture failed: ${data.error}`, 'system');
|
|
318
|
+
} else {
|
|
319
|
+
addMessage(`Screen captured: ${data.width}x${data.height}. AI can now see your screen.`, 'system', Date.now(), { subtype: 'capture' });
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (window.electronAPI.onVisualContextUpdate) {
|
|
325
|
+
window.electronAPI.onVisualContextUpdate((data) => {
|
|
326
|
+
updateVisualContextIndicator(data.count);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Auth status updates
|
|
331
|
+
if (window.electronAPI.onAuthStatus) {
|
|
332
|
+
window.electronAPI.onAuthStatus((data) => {
|
|
333
|
+
updateAuthStatus(data.status, data.provider);
|
|
334
|
+
if (data.provider && providerSelect) {
|
|
335
|
+
providerSelect.value = data.provider;
|
|
336
|
+
currentProvider = data.provider;
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Token usage updates from API responses
|
|
342
|
+
if (window.electronAPI.onTokenUsage) {
|
|
343
|
+
window.electronAPI.onTokenUsage((data) => {
|
|
344
|
+
if (data.inputTokens) {
|
|
345
|
+
totalTokens = data.totalTokens || totalTokens + data.inputTokens + (data.outputTokens || 0);
|
|
346
|
+
updateTokenCount();
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ===== TYPING INDICATOR =====
|
|
352
|
+
function showTypingIndicator() {
|
|
353
|
+
if (document.getElementById('typing-indicator')) return;
|
|
354
|
+
|
|
355
|
+
const typingEl = document.createElement('div');
|
|
356
|
+
typingEl.id = 'typing-indicator';
|
|
357
|
+
typingEl.className = 'message agent typing';
|
|
358
|
+
typingEl.innerHTML = `
|
|
359
|
+
<div class="typing-dots">
|
|
360
|
+
<span></span><span></span><span></span>
|
|
361
|
+
</div>
|
|
362
|
+
`;
|
|
363
|
+
chatHistory.appendChild(typingEl);
|
|
364
|
+
chatHistory.scrollTop = chatHistory.scrollHeight;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function removeTypingIndicator() {
|
|
368
|
+
const indicator = document.getElementById('typing-indicator');
|
|
369
|
+
if (indicator) indicator.remove();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ===== VISUAL CONTEXT INDICATOR =====
|
|
373
|
+
function updateVisualContextIndicator(count) {
|
|
374
|
+
let indicator = document.getElementById('visual-context-indicator');
|
|
375
|
+
if (!indicator) {
|
|
376
|
+
indicator = document.createElement('div');
|
|
377
|
+
indicator.id = 'visual-context-indicator';
|
|
378
|
+
indicator.style.cssText = 'position:absolute;top:8px;right:8px;background:var(--accent-green);color:white;padding:2px 8px;border-radius:10px;font-size:10px;';
|
|
379
|
+
document.getElementById('toolbar').appendChild(indicator);
|
|
380
|
+
}
|
|
381
|
+
indicator.textContent = count > 0 ? `📸 ${count}` : '';
|
|
382
|
+
indicator.style.display = count > 0 ? 'block' : 'none';
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ===== INITIALIZATION =====
|
|
386
|
+
window.electronAPI.getState().then(state => {
|
|
387
|
+
currentMode = state.overlayMode;
|
|
388
|
+
updateModeDisplay();
|
|
389
|
+
|
|
390
|
+
// Load current provider
|
|
391
|
+
if (state.aiProvider) {
|
|
392
|
+
currentProvider = state.aiProvider;
|
|
393
|
+
if (providerSelect) {
|
|
394
|
+
providerSelect.value = state.aiProvider;
|
|
395
|
+
}
|
|
396
|
+
console.log('Current AI provider:', state.aiProvider);
|
|
397
|
+
updateModelSelector(state.aiProvider);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Load current model
|
|
401
|
+
if (state.model && modelSelect) {
|
|
402
|
+
currentModel = state.model;
|
|
403
|
+
modelSelect.value = state.model;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Check auth status for current provider (async - response comes via onAuthStatus)
|
|
407
|
+
checkProviderAuth(currentProvider);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// Initialize auth status display as pending until check completes
|
|
411
|
+
updateAuthStatus('pending', currentProvider);
|
|
412
|
+
updateModelSelector(currentProvider);
|
|
413
|
+
|
|
414
|
+
// ===== AGENTIC ACTION UI =====
|
|
415
|
+
function showActionConfirmation(actionData) {
|
|
416
|
+
pendingActions = actionData;
|
|
417
|
+
|
|
418
|
+
const emptyState = chatHistory.querySelector('.empty-state');
|
|
419
|
+
if (emptyState) emptyState.remove();
|
|
420
|
+
|
|
421
|
+
const actionEl = document.createElement('div');
|
|
422
|
+
actionEl.id = 'action-confirmation';
|
|
423
|
+
actionEl.className = 'message agent action-card';
|
|
424
|
+
|
|
425
|
+
const actionsHtml = actionData.actions.map((action, idx) => {
|
|
426
|
+
let icon = '🖱️';
|
|
427
|
+
let desc = '';
|
|
428
|
+
|
|
429
|
+
switch (action.type) {
|
|
430
|
+
case 'click':
|
|
431
|
+
icon = '🖱️';
|
|
432
|
+
desc = `Click at (${action.x}, ${action.y})`;
|
|
433
|
+
if (action.coordinate) desc = `Click ${action.coordinate}`;
|
|
434
|
+
break;
|
|
435
|
+
case 'double_click':
|
|
436
|
+
icon = '🖱️🖱️';
|
|
437
|
+
desc = `Double-click at (${action.x}, ${action.y})`;
|
|
438
|
+
break;
|
|
439
|
+
case 'right_click':
|
|
440
|
+
icon = '🖱️';
|
|
441
|
+
desc = `Right-click at (${action.x}, ${action.y})`;
|
|
442
|
+
break;
|
|
443
|
+
case 'type':
|
|
444
|
+
icon = '⌨️';
|
|
445
|
+
desc = `Type: "${action.text.substring(0, 30)}${action.text.length > 30 ? '...' : ''}"`;
|
|
446
|
+
break;
|
|
447
|
+
case 'key':
|
|
448
|
+
icon = '⌨️';
|
|
449
|
+
desc = `Press: ${action.keys}`;
|
|
450
|
+
break;
|
|
451
|
+
case 'scroll':
|
|
452
|
+
icon = '📜';
|
|
453
|
+
desc = `Scroll ${action.direction || 'down'} ${action.amount || 3} lines`;
|
|
454
|
+
break;
|
|
455
|
+
case 'wait':
|
|
456
|
+
icon = '⏳';
|
|
457
|
+
desc = `Wait ${action.ms}ms`;
|
|
458
|
+
break;
|
|
459
|
+
case 'move_mouse':
|
|
460
|
+
icon = '➡️';
|
|
461
|
+
desc = `Move to (${action.x}, ${action.y})`;
|
|
462
|
+
break;
|
|
463
|
+
case 'drag':
|
|
464
|
+
icon = '✋';
|
|
465
|
+
desc = `Drag from (${action.fromX}, ${action.fromY}) to (${action.toX}, ${action.toY})`;
|
|
466
|
+
break;
|
|
467
|
+
default:
|
|
468
|
+
desc = JSON.stringify(action);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return `<div class="action-item"><span class="action-icon">${icon}</span><span class="action-desc">${idx + 1}. ${desc}</span></div>`;
|
|
472
|
+
}).join('');
|
|
473
|
+
|
|
474
|
+
actionEl.innerHTML = `
|
|
475
|
+
<div class="action-header">
|
|
476
|
+
<span class="action-title">🤖 AI wants to perform ${actionData.actions.length} action${actionData.actions.length > 1 ? 's' : ''}:</span>
|
|
477
|
+
</div>
|
|
478
|
+
${actionData.thought ? `<div class="action-thought">${actionData.thought}</div>` : ''}
|
|
479
|
+
<div class="action-list">${actionsHtml}</div>
|
|
480
|
+
<div class="action-buttons">
|
|
481
|
+
<button id="execute-actions-btn" class="action-btn execute">▶ Execute</button>
|
|
482
|
+
<button id="cancel-actions-btn" class="action-btn cancel">✕ Cancel</button>
|
|
483
|
+
</div>
|
|
484
|
+
`;
|
|
485
|
+
|
|
486
|
+
chatHistory.appendChild(actionEl);
|
|
487
|
+
chatHistory.scrollTop = chatHistory.scrollHeight;
|
|
488
|
+
|
|
489
|
+
// Attach event listeners
|
|
490
|
+
document.getElementById('execute-actions-btn').addEventListener('click', executeActions);
|
|
491
|
+
document.getElementById('cancel-actions-btn').addEventListener('click', cancelActions);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function executeActions() {
|
|
495
|
+
if (!pendingActions) return;
|
|
496
|
+
|
|
497
|
+
const confirmEl = document.getElementById('action-confirmation');
|
|
498
|
+
if (confirmEl) {
|
|
499
|
+
const buttons = confirmEl.querySelector('.action-buttons');
|
|
500
|
+
if (buttons) {
|
|
501
|
+
buttons.innerHTML = '<span class="executing">⏳ Executing...</span>';
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
window.electronAPI.executeActions(pendingActions);
|
|
506
|
+
pendingActions = null;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function cancelActions() {
|
|
510
|
+
const confirmEl = document.getElementById('action-confirmation');
|
|
511
|
+
if (confirmEl) {
|
|
512
|
+
confirmEl.remove();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
window.electronAPI.cancelActions();
|
|
516
|
+
pendingActions = null;
|
|
517
|
+
addMessage('Actions cancelled', 'system');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function showActionProgress(data) {
|
|
521
|
+
let progressEl = document.getElementById('action-progress');
|
|
522
|
+
if (!progressEl) {
|
|
523
|
+
progressEl = document.createElement('div');
|
|
524
|
+
progressEl.id = 'action-progress';
|
|
525
|
+
progressEl.className = 'message system action-progress';
|
|
526
|
+
chatHistory.appendChild(progressEl);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
progressEl.textContent = `⏳ ${data.message || `Executing action ${data.current} of ${data.total}...`}`;
|
|
530
|
+
chatHistory.scrollTop = chatHistory.scrollHeight;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function showActionComplete(data) {
|
|
534
|
+
const confirmEl = document.getElementById('action-confirmation');
|
|
535
|
+
if (confirmEl) confirmEl.remove();
|
|
536
|
+
|
|
537
|
+
const progressEl = document.getElementById('action-progress');
|
|
538
|
+
if (progressEl) progressEl.remove();
|
|
539
|
+
|
|
540
|
+
if (data.success) {
|
|
541
|
+
addMessage(`✅ ${data.actionsCount} action${data.actionsCount > 1 ? 's' : ''} completed successfully`, 'system');
|
|
542
|
+
} else {
|
|
543
|
+
addMessage(`❌ Action failed: ${data.error}`, 'system');
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Agentic action IPC listeners
|
|
548
|
+
if (window.electronAPI.onActionExecuting) {
|
|
549
|
+
window.electronAPI.onActionExecuting((data) => {
|
|
550
|
+
showActionConfirmation(data);
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (window.electronAPI.onActionProgress) {
|
|
555
|
+
window.electronAPI.onActionProgress((data) => {
|
|
556
|
+
showActionProgress(data);
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (window.electronAPI.onActionComplete) {
|
|
561
|
+
window.electronAPI.onActionComplete((data) => {
|
|
562
|
+
showActionComplete(data);
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Add typing indicator styles
|
|
567
|
+
const style = document.createElement('style');
|
|
568
|
+
style.textContent = `
|
|
569
|
+
.message.typing {
|
|
570
|
+
padding: 12px 16px;
|
|
571
|
+
}
|
|
572
|
+
.typing-dots {
|
|
573
|
+
display: flex;
|
|
574
|
+
gap: 4px;
|
|
575
|
+
align-items: center;
|
|
576
|
+
}
|
|
577
|
+
.typing-dots span {
|
|
578
|
+
width: 8px;
|
|
579
|
+
height: 8px;
|
|
580
|
+
background: var(--text-secondary);
|
|
581
|
+
border-radius: 50%;
|
|
582
|
+
animation: typing-bounce 1.4s ease-in-out infinite;
|
|
583
|
+
}
|
|
584
|
+
.typing-dots span:nth-child(2) { animation-delay: 0.2s; }
|
|
585
|
+
.typing-dots span:nth-child(3) { animation-delay: 0.4s; }
|
|
586
|
+
@keyframes typing-bounce {
|
|
587
|
+
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
|
|
588
|
+
30% { transform: translateY(-8px); opacity: 1; }
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/* Action card styles */
|
|
592
|
+
.action-card {
|
|
593
|
+
background: linear-gradient(135deg, #1a1a2e, #16213e);
|
|
594
|
+
border: 1px solid var(--accent-blue);
|
|
595
|
+
border-radius: 12px;
|
|
596
|
+
padding: 16px;
|
|
597
|
+
}
|
|
598
|
+
.action-header {
|
|
599
|
+
margin-bottom: 8px;
|
|
600
|
+
}
|
|
601
|
+
.action-title {
|
|
602
|
+
font-weight: 600;
|
|
603
|
+
color: var(--accent-blue);
|
|
604
|
+
}
|
|
605
|
+
.action-thought {
|
|
606
|
+
font-style: italic;
|
|
607
|
+
color: var(--text-secondary);
|
|
608
|
+
margin-bottom: 12px;
|
|
609
|
+
padding: 8px;
|
|
610
|
+
background: rgba(255,255,255,0.05);
|
|
611
|
+
border-radius: 6px;
|
|
612
|
+
}
|
|
613
|
+
.action-list {
|
|
614
|
+
margin-bottom: 12px;
|
|
615
|
+
}
|
|
616
|
+
.action-item {
|
|
617
|
+
display: flex;
|
|
618
|
+
align-items: center;
|
|
619
|
+
gap: 8px;
|
|
620
|
+
padding: 6px 8px;
|
|
621
|
+
background: rgba(255,255,255,0.05);
|
|
622
|
+
border-radius: 4px;
|
|
623
|
+
margin-bottom: 4px;
|
|
624
|
+
}
|
|
625
|
+
.action-icon {
|
|
626
|
+
font-size: 16px;
|
|
627
|
+
}
|
|
628
|
+
.action-desc {
|
|
629
|
+
font-family: 'Consolas', monospace;
|
|
630
|
+
font-size: 12px;
|
|
631
|
+
}
|
|
632
|
+
.action-buttons {
|
|
633
|
+
display: flex;
|
|
634
|
+
gap: 12px;
|
|
635
|
+
justify-content: flex-end;
|
|
636
|
+
}
|
|
637
|
+
.action-btn {
|
|
638
|
+
padding: 8px 16px;
|
|
639
|
+
border: none;
|
|
640
|
+
border-radius: 6px;
|
|
641
|
+
cursor: pointer;
|
|
642
|
+
font-weight: 600;
|
|
643
|
+
font-size: 13px;
|
|
644
|
+
transition: all 0.2s;
|
|
645
|
+
}
|
|
646
|
+
.action-btn.execute {
|
|
647
|
+
background: var(--accent-green);
|
|
648
|
+
color: white;
|
|
649
|
+
}
|
|
650
|
+
.action-btn.execute:hover {
|
|
651
|
+
background: #00c853;
|
|
652
|
+
transform: scale(1.02);
|
|
653
|
+
}
|
|
654
|
+
.action-btn.cancel {
|
|
655
|
+
background: rgba(255,255,255,0.1);
|
|
656
|
+
color: var(--text-secondary);
|
|
657
|
+
}
|
|
658
|
+
.action-btn.cancel:hover {
|
|
659
|
+
background: rgba(255,100,100,0.2);
|
|
660
|
+
color: #ff6b6b;
|
|
661
|
+
}
|
|
662
|
+
.executing {
|
|
663
|
+
color: var(--accent-blue);
|
|
664
|
+
font-style: italic;
|
|
665
|
+
}
|
|
666
|
+
.action-progress {
|
|
667
|
+
background: rgba(0,150,255,0.1);
|
|
668
|
+
border-left: 3px solid var(--accent-blue);
|
|
669
|
+
}
|
|
670
|
+
`;
|
|
671
|
+
document.head.appendChild(style);
|