Skip to content

Commit f90ba65

Browse files
Fix UI bugs, re-enable built-in tools, v1.0.3
1 parent 0e15fd5 commit f90ba65

2 files changed

Lines changed: 58 additions & 18 deletions

File tree

src/main/copilot-service.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -193,16 +193,16 @@ You're a helpful AI integrated into a web browser with full browser automation c
193193
- Type text into search boxes and forms
194194
- Scroll pages up/down
195195
- Find text on pages
196-
- Take screenshots and get DETAILED VISUAL DESCRIPTIONS of page state (video playing, buttons visible, text content, etc.)
196+
- Take screenshots - the image is saved to disk and you can use the built-in view tool to see page contents visually
197197
- Navigate back/forward in history
198198
- Wait for page content or specific elements to load
199199
- Ask clarifying questions to the user rather than ending the turn.
200200
</capabilities>
201201
202202
<instructions>
203203
- Be concise and direct.
204-
- IMPORTANT: You are a browser automation agent. DO NOT use any tools other than the provided \`browser_*\` tools.
205-
- **VISUAL AWARENESS**: Use \`browser_take_screenshot\` to get a detailed structured description of what's visible on the page. This tells you:
204+
- IMPORTANT: You are a browser automation agent. DO NOT use any tools other than the provided \`browser_*\` tools and the built-in \`view\` tool for viewing screenshots.
205+
- **VISUAL AWARENESS**: Use \`browser_take_screenshot\` to capture the page. It saves the image to a file and returns the path. Then use the built-in \`view\` tool with that path to see what's on the page visually.
206206
- Whether a video is playing, paused, buffering, or showing an ad
207207
- All visible buttons and their labels
208208
- Video titles, channels, and URLs on YouTube
@@ -244,7 +244,8 @@ If you've opened multiple tabs trying to find something please close the old unu
244244
</best_practices>
245245
246246
<never_do>
247-
Never use any tools or take any actions outside of the provided browser tools. You ARE a BROWSER AUTOMATION AGENT.
247+
- Never use any tools other than browser_* tools and the view tool. You ARE a BROWSER AUTOMATION AGENT.
248+
- Never use shell, edit_file, read_file (except view for screenshots), or any file manipulation tools.
248249
</never_do>
249250
`,
250251
},
@@ -659,7 +660,7 @@ Never use any tools or take any actions outside of the provided browser tools. Y
659660
},
660661
}),
661662
defineToolFn('browser_take_screenshot', {
662-
description: 'Take a screenshot of the current page. Saves the image to disk and returns the file path. Use the built-in view tool to see the screenshot contents.',
663+
description: 'Take a screenshot of the current page. Saves the image to disk and returns the file path. Use the built-in view tool with the returned path to see page contents.',
663664
skipPermission: true,
664665
parameters: {
665666
type: 'object',
@@ -672,11 +673,10 @@ Never use any tools or take any actions outside of the provided browser tools. Y
672673
// Capture screenshot for user display
673674
const dataUrl = await withTimeout(callbacks.takeScreenshot(), 10000);
674675
if (dataUrl) {
675-
// Send the image data to UI so user can see it in the chat
676676
reportResult('browser_take_screenshot', dataUrl);
677677
}
678678

679-
// Save screenshot to file so the built-in view tool can read it
679+
// Save to file for view tool access
680680
let filePath: string | null = null;
681681
if (callbacks.saveScreenshotToFile) {
682682
filePath = await callbacks.saveScreenshotToFile();
@@ -686,7 +686,7 @@ Never use any tools or take any actions outside of the provided browser tools. Y
686686
return `Screenshot saved to: ${filePath}\nUse the view tool to see the screenshot contents.`;
687687
}
688688

689-
// Fallback to text description if file save failed
689+
// Fallback to text description if save failed
690690
const visualDescription = await withTimeout(callbacks.getVisualDescription(), 10000);
691691
return visualDescription;
692692
} catch (error: any) {

src/renderer/app.js

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ const state = {
1010
currentTheme: 'system',
1111
copilotReady: false,
1212
isStreaming: false,
13+
isSending: false, // Lock to prevent double-send
1314
currentModel: 'gpt-4.1',
1415
includePageContent: false,
1516
currentSessionId: null,
1617
messages: [], // Store messages in memory
18+
streamGeneration: 0, // Counter to ignore stale stream results after new chat
19+
sessionReady: Promise.resolve(), // Resolves when backend session is ready
1720
};
1821

1922
// DOM Elements
@@ -410,9 +413,13 @@ function setupIpcListeners() {
410413
});
411414

412415
window.electronAPI.onStreamEnd((result) => {
416+
const expectedGeneration = state.streamGeneration;
413417
state.isStreaming = false;
414418
updateSendButton();
415419

420+
// Ignore stale stream results from before a chat was cleared
421+
if (expectedGeneration !== state.streamGeneration) return;
422+
416423
// Check if we have a streaming message BEFORE removing the indicator/attribute
417424
const streamingMessage = elements.chatMessages.querySelector('[data-streaming="true"]');
418425

@@ -467,8 +474,10 @@ function setupIpcListeners() {
467474
});
468475

469476
window.electronAPI.onStreamError((error) => {
477+
const expectedGen = state.streamGeneration;
470478
state.isStreaming = false;
471479
updateSendButton();
480+
if (expectedGen !== state.streamGeneration) return;
472481
removeThinkingIndicator();
473482
addErrorMessage(error);
474483
});
@@ -668,6 +677,12 @@ async function sendMessage() {
668677

669678
if (!message) return;
670679

680+
// Prevent double-send while processing
681+
if (state.isSending) return;
682+
state.isSending = true;
683+
elements.sendBtn.disabled = true;
684+
elements.chatInput.disabled = true;
685+
671686
let fullMessage = message;
672687

673688
// Include page content if requested
@@ -689,12 +704,17 @@ async function sendMessage() {
689704
const welcomeMsg = elements.chatMessages.querySelector('.welcome-message');
690705
if (welcomeMsg) {
691706
welcomeMsg.remove();
692-
// Start new session
693-
await startNewSession();
707+
// Only create session if one doesn't exist yet (startNewChat already created one)
708+
if (!state.currentSessionId) {
709+
await startNewSession();
710+
}
694711
} else if (!state.currentSessionId) {
695712
await startNewSession();
696713
}
697714

715+
// Wait for any in-progress session reset to complete
716+
await state.sessionReady;
717+
698718
// Add user message
699719
const userMessage = { id: Date.now().toString(), role: 'user', content: message, timestamp: Date.now() };
700720
state.messages.push(userMessage);
@@ -713,6 +733,11 @@ async function sendMessage() {
713733
state.isStreaming = true;
714734
updateSendButton();
715735

736+
// Unlock send
737+
state.isSending = false;
738+
elements.chatInput.disabled = false;
739+
elements.chatInput.focus();
740+
716741
// Send to Copilot
717742
window.electronAPI.startStream(fullMessage, state.currentModel);
718743
}
@@ -721,12 +746,16 @@ async function startNewSession() {
721746
state.currentSessionId = Date.now().toString();
722747
state.messages = [];
723748

724-
// Reset the Copilot session context on the backend
725-
try {
726-
await window.electronAPI.resetSession();
727-
} catch (err) {
728-
console.warn('Failed to reset Copilot session:', err);
729-
}
749+
// Track session readiness so sendMessage can wait for it
750+
const readyPromise = (async () => {
751+
try {
752+
await window.electronAPI.resetSession();
753+
} catch (err) {
754+
console.warn('Failed to reset Copilot session:', err);
755+
}
756+
})();
757+
state.sessionReady = readyPromise;
758+
await readyPromise;
730759
}
731760

732761
function saveCurrentSession() {
@@ -1221,9 +1250,20 @@ function addErrorMessage(error) {
12211250
scrollToBottom();
12221251
}
12231252

1224-
function startNewChat() {
1253+
async function startNewChat() {
1254+
// Abort any active stream first
1255+
if (state.isStreaming) {
1256+
window.electronAPI.abortStream();
1257+
state.isStreaming = false;
1258+
updateSendButton();
1259+
removeThinkingIndicator();
1260+
}
1261+
1262+
// Bump generation counter so stale stream results are ignored
1263+
state.streamGeneration++;
1264+
12251265
clearChat();
1226-
startNewSession();
1266+
await startNewSession();
12271267
}
12281268

12291269
// Sidebar Resize Logic

0 commit comments

Comments
 (0)