Skip to content

Commit 7f1ed32

Browse files
WebMemory: fix the modal popup for importing web activity data (#1438)
- Handle exception thrown when screenshots cannot be captured on certain tabs - Enable search on entity view page - Fix the modal popup for importing web activity data - Broadcast message when web socket connection status changes
1 parent a7b65c5 commit 7f1ed32

12 files changed

+339
-22
lines changed

ts/packages/agents/browser/src/agent/browserConnector.mts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ export class BrowserConnector {
119119
if (
120120
message.includes(
121121
"MAX_CAPTURE_VISIBLE_TAB_CALLS_PER_SECOND",
122-
)
122+
) ||
123+
message.includes("Tabs cannot be edited right now")
123124
) {
124125
return "";
125126
}

ts/packages/agents/browser/src/extension/serviceWorker/websocket.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,33 @@ const debugWebSocketError = registerDebug("typeagent:browser:ws:error");
1515
let webSocket: WebSocket | undefined;
1616
let settings: Record<string, any>;
1717

18+
/**
19+
* Broadcasts WebSocket connection status changes to all extension pages
20+
*/
21+
function broadcastConnectionStatus(connected: boolean): void {
22+
chrome.tabs.query({}, (tabs) => {
23+
tabs.forEach((tab) => {
24+
if (tab.url?.startsWith("chrome-extension://")) {
25+
chrome.tabs
26+
.sendMessage(tab.id!, {
27+
type: "connectionStatusChanged",
28+
connected: connected,
29+
timestamp: Date.now(),
30+
})
31+
.catch(() => {});
32+
}
33+
});
34+
});
35+
36+
chrome.runtime
37+
.sendMessage({
38+
type: "connectionStatusChanged",
39+
connected: connected,
40+
timestamp: Date.now(),
41+
})
42+
.catch(() => {});
43+
}
44+
1845
/**
1946
* Creates a new WebSocket connection
2047
* @returns Promise resolving to the WebSocket or undefined
@@ -69,12 +96,14 @@ export async function ensureWebsocketConnected(): Promise<
6996
webSocket = await createWebSocket();
7097
if (!webSocket) {
7198
showBadgeError();
99+
broadcastConnectionStatus(false);
72100
resolve(undefined);
73101
return;
74102
}
75103

76104
webSocket.binaryType = "blob";
77105
keepWebSocketAlive(webSocket);
106+
broadcastConnectionStatus(true);
78107

79108
const browserControlChannel = createGenericChannel((message: any) => {
80109
if (webSocket && webSocket.readyState === WebSocket.OPEN) {
@@ -163,6 +192,7 @@ export async function ensureWebsocketConnected(): Promise<
163192
debugWebSocket("websocket connection closed");
164193
webSocket = undefined;
165194
showBadgeError();
195+
broadcastConnectionStatus(false);
166196
if (event.reason !== "duplicate") {
167197
reconnectWebSocket();
168198
}
@@ -201,6 +231,7 @@ export function reconnectWebSocket(): void {
201231
debugWebSocket("Clearing reconnect retry interval");
202232
clearInterval(connectionCheckIntervalId);
203233
showBadgeHealthy();
234+
broadcastConnectionStatus(true);
204235
} else {
205236
debugWebSocket("Retrying connection");
206237
await ensureWebsocketConnected();

ts/packages/agents/browser/src/extension/views/entityGraphView.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
type="text"
3939
id="entitySearchInput"
4040
class="entity-search-input"
41-
placeholder="Search entities..."
41+
placeholder="Enter entity name..."
4242
/>
4343
<button id="entitySearchButton" class="entity-search-btn">
4444
<i class="bi bi-search"></i>

ts/packages/agents/browser/src/extension/views/entityGraphView.ts

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -229,22 +229,51 @@ class EntityGraphView {
229229
*/
230230
private setupSearchHandlers(): void {
231231
const searchInput = document.getElementById(
232-
"entitySearch",
232+
"entitySearchInput",
233233
) as HTMLInputElement;
234234
const searchButton = document.getElementById(
235-
"searchButton",
235+
"entitySearchButton",
236236
) as HTMLButtonElement;
237237

238+
console.log("Setting up search handlers:", {
239+
searchInput: !!searchInput,
240+
searchButton: !!searchButton,
241+
});
242+
238243
if (searchInput && searchButton) {
239-
searchButton.addEventListener("click", () => {
240-
this.searchEntity(searchInput.value);
241-
});
244+
const performSearch = () => {
245+
const query = searchInput.value.trim();
246+
if (query) {
247+
console.log("Performing search with value:", query);
248+
this.searchEntity(query);
249+
// Clear the input after successful search initiation
250+
searchInput.value = "";
251+
} else {
252+
this.showMessage(
253+
"Please enter an entity name to search",
254+
"warning",
255+
);
256+
}
257+
};
258+
259+
searchButton.addEventListener("click", performSearch);
242260

243261
searchInput.addEventListener("keypress", (e) => {
244262
if (e.key === "Enter") {
245-
this.searchEntity(searchInput.value);
263+
e.preventDefault(); // Prevent form submission
264+
performSearch();
246265
}
247266
});
267+
268+
// Optional: Add focus behavior for better UX
269+
searchInput.addEventListener("focus", () => {
270+
searchInput.select(); // Select all text when focused
271+
});
272+
} else {
273+
console.warn("Search elements not found:", {
274+
searchInputFound: !!searchInput,
275+
searchButtonFound: !!searchButton,
276+
});
248277
}
249278
}
250279

@@ -324,10 +353,20 @@ class EntityGraphView {
324353
if (!query.trim()) return;
325354

326355
try {
327-
// Search in real data
328-
await this.searchRealEntity(query);
356+
// Navigate directly to the entity (same as URL parameter logic)
357+
console.log(`Searching for entity: ${query}`);
358+
this.showMessage(`Loading entity "${query}"...`, "info");
359+
360+
await this.navigateToEntity(query.trim());
361+
362+
// Show success message
363+
this.showMessage(`Loaded entity: "${query}"`, "success");
329364
} catch (error) {
330365
console.error("Failed to search entity:", error);
366+
this.showMessage(
367+
`Failed to load entity "${query}". ${error instanceof Error ? error.message : "Please try again."}`,
368+
"error",
369+
);
331370
}
332371
}
333372

ts/packages/agents/browser/src/extension/views/extensionServiceBase.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,69 @@ export interface EntityMatch {
8787
* Abstract base class for extension services
8888
*/
8989
export abstract class ExtensionServiceBase {
90+
// ===================================================================
91+
// CONNECTION STATUS EVENT SYSTEM
92+
// ===================================================================
93+
94+
private connectionStatusCallbacks: ((connected: boolean) => void)[] = [];
95+
private connectionStatusListenerSetup = false;
96+
97+
/**
98+
* Register a callback for connection status changes
99+
*/
100+
public onConnectionStatusChange(
101+
callback: (connected: boolean) => void,
102+
): void {
103+
this.connectionStatusCallbacks.push(callback);
104+
105+
if (!this.connectionStatusListenerSetup) {
106+
this.setupConnectionStatusListener();
107+
this.connectionStatusListenerSetup = true;
108+
}
109+
}
110+
111+
/**
112+
* Remove connection status callback
113+
*/
114+
public removeConnectionStatusListener(
115+
callback: (connected: boolean) => void,
116+
): void {
117+
const index = this.connectionStatusCallbacks.indexOf(callback);
118+
if (index > -1) {
119+
this.connectionStatusCallbacks.splice(index, 1);
120+
}
121+
}
122+
123+
/**
124+
* Setup message listener for connection status changes
125+
*/
126+
private setupConnectionStatusListener(): void {
127+
const messageListener = (
128+
message: any,
129+
sender: any,
130+
sendResponse: any,
131+
) => {
132+
if (message.type === "connectionStatusChanged") {
133+
this.connectionStatusCallbacks.forEach((callback) => {
134+
try {
135+
callback(message.connected);
136+
} catch (error) {
137+
console.error(
138+
"Connection status callback error:",
139+
error,
140+
);
141+
}
142+
});
143+
}
144+
};
145+
146+
try {
147+
chrome.runtime.onMessage.addListener(messageListener);
148+
} catch (error) {
149+
console.error("Failed to setup connection status listener:", error);
150+
}
151+
}
152+
90153
// ===================================================================
91154
// SHARED METHOD IMPLEMENTATIONS
92155
// ===================================================================

ts/packages/agents/browser/src/extension/views/knowledgeDiscoveryPanel.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import { DiscoveryServices } from "./knowledgeUtilities";
4+
import { DiscoveryServices, extensionService } from "./knowledgeUtilities";
55

66
export class KnowledgeDiscoveryPanel {
77
private container: HTMLElement;
88
private services: DiscoveryServices;
99
private discoverData: any = null;
1010
private isConnected: boolean = true;
11+
private connectionStatusCallback?: (connected: boolean) => void;
1112

1213
constructor(container: HTMLElement, services: DiscoveryServices) {
1314
this.container = container;
@@ -21,6 +22,7 @@ export class KnowledgeDiscoveryPanel {
2122
emptyState.style.display = "none";
2223
}
2324

25+
this.setupConnectionStatusListener();
2426
await this.loadDiscoverData();
2527
this.renderContent();
2628
}
@@ -308,4 +310,14 @@ export class KnowledgeDiscoveryPanel {
308310
this.showConnectionError();
309311
}
310312
}
313+
314+
private setupConnectionStatusListener(): void {
315+
this.connectionStatusCallback = (connected: boolean) => {
316+
this.setConnectionStatus(connected);
317+
};
318+
319+
extensionService.onConnectionStatusChange(
320+
this.connectionStatusCallback,
321+
);
322+
}
311323
}

ts/packages/agents/browser/src/extension/views/knowledgeLibrary.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@
3636
data-bs-toggle="dropdown"
3737
aria-expanded="false"
3838
>
39-
<i class="bi bi-download"></i>
39+
<i class="bi bi-upload"></i>
4040
</button>
4141
<ul class="dropdown-menu">
4242
<li>
4343
<a class="dropdown-item" href="#" id="importWebActivityBtn">
44-
<i class="bi bi-download me-2"></i>
45-
Import Web Activity
44+
<i class="bi bi-bookmarks me-2"></i>
45+
Import Bookmarks or History
4646
</a>
4747
</li>
4848
<li>
@@ -92,7 +92,7 @@ <h1 class="page-title">
9292
</h1>
9393
<p class="page-subtitle">
9494
Ask in natural language: "latest github bookmarks", "car reviews
95-
last week", "most visited news sites"
95+
last week"
9696
</p>
9797
</div>
9898

ts/packages/agents/browser/src/extension/views/knowledgeLibrary.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ interface UserPreferences {
5757
class WebsiteLibraryPanelFullPage {
5858
private isConnected: boolean = false;
5959
private isInitialized: boolean = false;
60+
private connectionStatusCallback?: (connected: boolean) => void;
6061
private navigation: FullPageNavigation = {
6162
currentPage: "search",
6263
};
@@ -119,6 +120,7 @@ class WebsiteLibraryPanelFullPage {
119120
this.setupImportFunctionality();
120121

121122
await this.checkConnectionStatus();
123+
this.setupConnectionStatusListener();
122124
await this.loadLibraryStats();
123125
await this.navigateToPage("search");
124126
} catch (error) {
@@ -241,6 +243,29 @@ class WebsiteLibraryPanelFullPage {
241243
}
242244
}
243245

246+
private setupConnectionStatusListener(): void {
247+
this.connectionStatusCallback = (connected: boolean) => {
248+
console.log(
249+
`Connection status changed: ${connected ? "Connected" : "Disconnected"}`,
250+
);
251+
this.isConnected = connected;
252+
this.updateConnectionStatus();
253+
this.updatePanelConnectionStatus();
254+
};
255+
256+
extensionService.onConnectionStatusChange(
257+
this.connectionStatusCallback,
258+
);
259+
}
260+
261+
public cleanup(): void {
262+
if (this.connectionStatusCallback) {
263+
extensionService.removeConnectionStatusListener(
264+
this.connectionStatusCallback,
265+
);
266+
}
267+
}
268+
244269
private updatePanelConnectionStatus() {
245270
if (this.searchPanel) {
246271
this.searchPanel.setConnectionStatus(this.isConnected);
@@ -452,7 +477,7 @@ class WebsiteLibraryPanelFullPage {
452477

453478
const result =
454479
await this.importManager.startWebActivityImport(options);
455-
this.importUI.showImportComplete(result);
480+
// Don't show completion immediately - let progress updates handle it
456481
} catch (error) {
457482
this.importUI.showImportError({
458483
type: "processing",
@@ -481,7 +506,7 @@ class WebsiteLibraryPanelFullPage {
481506
});
482507

483508
const result = await this.importManager.startFolderImport(options);
484-
this.importUI.showImportComplete(result);
509+
// Don't show completion immediately - let progress updates handle it
485510
} catch (error) {
486511
this.importUI.showImportError({
487512
type: "processing",
@@ -771,3 +796,10 @@ if (document.readyState === "loading") {
771796
} else {
772797
initializeLibraryPanel();
773798
}
799+
800+
// Add cleanup on window unload
801+
window.addEventListener("beforeunload", () => {
802+
if (libraryPanelInstance) {
803+
libraryPanelInstance.cleanup();
804+
}
805+
});

0 commit comments

Comments
 (0)