@hanzo/browser-extension 1.7.2
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/.github/workflows/docs.yml +44 -0
- package/.github/workflows/release.yml +55 -0
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/dist/browser-extension/README.md +43 -0
- package/dist/browser-extension/ai-worker.js +30 -0
- package/dist/browser-extension/background-firefox.js +617 -0
- package/dist/browser-extension/background.js +2737 -0
- package/dist/browser-extension/browser-control.js +683 -0
- package/dist/browser-extension/cdp-bridge-server.js +444 -0
- package/dist/browser-extension/chrome/ai-worker.js +571 -0
- package/dist/browser-extension/chrome/background.js +2737 -0
- package/dist/browser-extension/chrome/callback.html +17 -0
- package/dist/browser-extension/chrome/content-script.js +1270 -0
- package/dist/browser-extension/chrome/icon128.png +0 -0
- package/dist/browser-extension/chrome/icon16.png +0 -0
- package/dist/browser-extension/chrome/icon32.png +0 -0
- package/dist/browser-extension/chrome/icon48.png +0 -0
- package/dist/browser-extension/chrome/manifest.json +57 -0
- package/dist/browser-extension/chrome/popup.css +519 -0
- package/dist/browser-extension/chrome/popup.html +242 -0
- package/dist/browser-extension/chrome/popup.js +267 -0
- package/dist/browser-extension/chrome/sidebar.css +1176 -0
- package/dist/browser-extension/chrome/sidebar.html +280 -0
- package/dist/browser-extension/chrome/sidebar.js +778 -0
- package/dist/browser-extension/cli.js +233 -0
- package/dist/browser-extension/content-script.js +1270 -0
- package/dist/browser-extension/firefox/ai-worker.js +571 -0
- package/dist/browser-extension/firefox/background.js +617 -0
- package/dist/browser-extension/firefox/callback.html +17 -0
- package/dist/browser-extension/firefox/content-script.js +1270 -0
- package/dist/browser-extension/firefox/icon128.png +0 -0
- package/dist/browser-extension/firefox/icon16.png +0 -0
- package/dist/browser-extension/firefox/icon32.png +0 -0
- package/dist/browser-extension/firefox/icon48.png +0 -0
- package/dist/browser-extension/firefox/manifest.json +66 -0
- package/dist/browser-extension/firefox/popup.css +519 -0
- package/dist/browser-extension/firefox/popup.html +242 -0
- package/dist/browser-extension/firefox/popup.js +267 -0
- package/dist/browser-extension/firefox/sidebar.css +1176 -0
- package/dist/browser-extension/firefox/sidebar.html +280 -0
- package/dist/browser-extension/firefox/sidebar.js +778 -0
- package/dist/browser-extension/hanzo-browser-extension-firefox-1.1.0.zip +0 -0
- package/dist/browser-extension/icon128.png +0 -0
- package/dist/browser-extension/icon16.png +0 -0
- package/dist/browser-extension/icon32.png +0 -0
- package/dist/browser-extension/icon48.png +0 -0
- package/dist/browser-extension/manifest.json +57 -0
- package/dist/browser-extension/package.json +41 -0
- package/dist/browser-extension/popup.css +468 -0
- package/dist/browser-extension/popup.html +191 -0
- package/dist/browser-extension/popup.js +267 -0
- package/dist/browser-extension/safari/Info.plist +21 -0
- package/dist/browser-extension/safari/ai-worker.js +571 -0
- package/dist/browser-extension/safari/background.js +2737 -0
- package/dist/browser-extension/safari/callback.html +17 -0
- package/dist/browser-extension/safari/content-script.js +1270 -0
- package/dist/browser-extension/safari/icon128.png +0 -0
- package/dist/browser-extension/safari/icon16.png +0 -0
- package/dist/browser-extension/safari/icon32.png +0 -0
- package/dist/browser-extension/safari/icon48.png +0 -0
- package/dist/browser-extension/safari/popup.css +519 -0
- package/dist/browser-extension/safari/popup.html +242 -0
- package/dist/browser-extension/safari/popup.js +267 -0
- package/dist/browser-extension/safari/sidebar.css +1176 -0
- package/dist/browser-extension/safari/sidebar.html +280 -0
- package/dist/browser-extension/safari/sidebar.js +778 -0
- package/dist/browser-extension/sidebar.css +718 -0
- package/dist/browser-extension/sidebar.html +184 -0
- package/dist/browser-extension/sidebar.js +778 -0
- package/dist/browser-extension/webgpu-ai.js +495 -0
- package/dist/cdp-bridge-server.js +411 -0
- package/dist/safari/Hanzo AI/Hanzo AI.xcodeproj/project.pbxproj +1047 -0
- package/dist/safari/Hanzo AI/Hanzo AI.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json +98 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/Contents.json +6 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json +21 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/LargeIcon.imageset/icon128.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Resources/Base.lproj/Main.html +20 -0
- package/dist/safari/Hanzo AI/Shared (App)/Resources/Icon.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Resources/Script.js +24 -0
- package/dist/safari/Hanzo AI/Shared (App)/Resources/Style.css +61 -0
- package/dist/safari/Hanzo AI/Shared (App)/ViewController.swift +81 -0
- package/dist/safari/Hanzo AI/Shared (Extension)/SafariWebExtensionHandler.swift +42 -0
- package/dist/safari/Hanzo AI/iOS (App)/AppDelegate.swift +24 -0
- package/dist/safari/Hanzo AI/iOS (App)/Base.lproj/LaunchScreen.storyboard +36 -0
- package/dist/safari/Hanzo AI/iOS (App)/Base.lproj/Main.storyboard +38 -0
- package/dist/safari/Hanzo AI/iOS (App)/Info.plist +27 -0
- package/dist/safari/Hanzo AI/iOS (App)/SceneDelegate.swift +18 -0
- package/dist/safari/Hanzo AI/iOS (Extension)/Info.plist +13 -0
- package/dist/safari/Hanzo AI/macOS (App)/AppDelegate.swift +21 -0
- package/dist/safari/Hanzo AI/macOS (App)/Base.lproj/Main.storyboard +124 -0
- package/dist/safari/Hanzo AI/macOS (App)/Info.plist +8 -0
- package/dist/safari/Hanzo AI/macOS (Extension)/Info.plist +13 -0
- package/docs/_config.yml +13 -0
- package/docs/_layouts/default.html +435 -0
- package/docs/index.md +54 -0
- package/install-extension.sh +75 -0
- package/manifest.json +29 -0
- package/package.json +32 -0
- package/src/ai-worker.js +571 -0
- package/src/callback.html +17 -0
- package/src/chat-widget.tsx +93 -0
- package/src/icon128.png +0 -0
- package/src/icon16.png +0 -0
- package/src/icon32.png +0 -0
- package/src/icon48.png +0 -0
- package/src/manifest-firefox.json +66 -0
- package/src/manifest.json +57 -0
- package/src/package.json +41 -0
- package/src/popup.css +519 -0
- package/src/popup.html +242 -0
- package/src/popup.js +192 -0
- package/src/sidebar.css +1176 -0
- package/src/sidebar.html +280 -0
- package/src/sidebar.js +884 -0
- package/src/webgpu.d.ts +131 -0
- package/test/claude-integration.test.ts +251 -0
- package/test/server-frameworks-edge-cases.test.ts +145 -0
- package/test/server-frameworks.test.ts +395 -0
- package/test-cdp-connection.js +140 -0
- package/tsconfig.json +19 -0
package/src/sidebar.js
ADDED
|
@@ -0,0 +1,884 @@
|
|
|
1
|
+
// Hanzo AI Browser Extension Sidebar — Chat + Tools + Settings
|
|
2
|
+
|
|
3
|
+
class HanzoSidebar {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.agents = new Map();
|
|
6
|
+
this.currentTab = 'chat';
|
|
7
|
+
this.messages = [];
|
|
8
|
+
this.streaming = false;
|
|
9
|
+
this.streamController = null;
|
|
10
|
+
this.authenticated = false;
|
|
11
|
+
this.ragContext = null;
|
|
12
|
+
|
|
13
|
+
this.initializeUI();
|
|
14
|
+
this.setupEventListeners();
|
|
15
|
+
this.checkAuth();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
initializeUI() {
|
|
19
|
+
this.el = {
|
|
20
|
+
// Auth
|
|
21
|
+
authSection: document.getElementById('auth-section'),
|
|
22
|
+
authBtn: document.getElementById('auth-btn'),
|
|
23
|
+
tabBar: document.getElementById('tab-bar'),
|
|
24
|
+
userBadge: document.getElementById('user-badge'),
|
|
25
|
+
headerAvatar: document.getElementById('header-avatar'),
|
|
26
|
+
|
|
27
|
+
// Chat
|
|
28
|
+
tabChat: document.getElementById('tab-chat'),
|
|
29
|
+
chatMessages: document.getElementById('chat-messages'),
|
|
30
|
+
chatInput: document.getElementById('chat-input'),
|
|
31
|
+
sendBtn: document.getElementById('send-btn'),
|
|
32
|
+
modelSelect: document.getElementById('model-select'),
|
|
33
|
+
ragEnabled: document.getElementById('rag-enabled'),
|
|
34
|
+
tabContextEnabled: document.getElementById('tab-context-enabled'),
|
|
35
|
+
ragStatus: document.getElementById('rag-status'),
|
|
36
|
+
|
|
37
|
+
// Tools
|
|
38
|
+
tabTools: document.getElementById('tab-tools'),
|
|
39
|
+
mcpStatus: document.getElementById('mcp-status'),
|
|
40
|
+
mcpTools: document.getElementById('mcp-tools'),
|
|
41
|
+
mcpToolList: document.getElementById('mcp-tool-list'),
|
|
42
|
+
agentCount: document.getElementById('agent-count'),
|
|
43
|
+
agentList: document.getElementById('agent-list'),
|
|
44
|
+
tabFs: document.getElementById('tab-fs'),
|
|
45
|
+
refreshTabs: document.getElementById('refresh-tabs'),
|
|
46
|
+
gpuStatus: document.getElementById('gpu-status'),
|
|
47
|
+
gpuModel: document.getElementById('gpu-model'),
|
|
48
|
+
launchAgent: document.getElementById('launch-agent'),
|
|
49
|
+
|
|
50
|
+
// Settings
|
|
51
|
+
tabSettings: document.getElementById('tab-settings'),
|
|
52
|
+
userAvatar: document.getElementById('user-avatar'),
|
|
53
|
+
userName: document.getElementById('user-name'),
|
|
54
|
+
userEmail: document.getElementById('user-email'),
|
|
55
|
+
logoutBtn: document.getElementById('logout-btn'),
|
|
56
|
+
saveSettings: document.getElementById('save-settings'),
|
|
57
|
+
defaultModel: document.getElementById('default-model'),
|
|
58
|
+
ragUseZap: document.getElementById('rag-use-zap'),
|
|
59
|
+
ragIncludeTabContext: document.getElementById('rag-include-tab-context'),
|
|
60
|
+
ragTopK: document.getElementById('rag-top-k'),
|
|
61
|
+
ragKb: document.getElementById('rag-kb'),
|
|
62
|
+
ragEndpoint: document.getElementById('rag-endpoint'),
|
|
63
|
+
ragApiKey: document.getElementById('rag-api-key'),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setupEventListeners() {
|
|
68
|
+
// Auth
|
|
69
|
+
this.el.authBtn.addEventListener('click', () => this.login());
|
|
70
|
+
|
|
71
|
+
// Tabs
|
|
72
|
+
document.querySelectorAll('.tab').forEach(tab => {
|
|
73
|
+
tab.addEventListener('click', () => this.switchTab(tab.dataset.tab));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Chat
|
|
77
|
+
this.el.sendBtn.addEventListener('click', () => this.sendMessage());
|
|
78
|
+
this.el.chatInput.addEventListener('keydown', (e) => {
|
|
79
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
this.sendMessage();
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
this.el.chatInput.addEventListener('input', () => this.autoResize());
|
|
85
|
+
this.el.ragEnabled?.addEventListener('change', () => {
|
|
86
|
+
this.setRagStatus(this.el.ragEnabled.checked ? 'RAG enabled' : 'RAG disabled');
|
|
87
|
+
});
|
|
88
|
+
this.el.tabContextEnabled?.addEventListener('change', () => {
|
|
89
|
+
if (!this.el.ragEnabled?.checked) return;
|
|
90
|
+
this.setRagStatus(this.el.tabContextEnabled.checked ? 'Tab context on' : 'Tab context off');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Tools
|
|
94
|
+
this.el.refreshTabs.addEventListener('click', () => this.refreshTabFilesystem());
|
|
95
|
+
this.el.launchAgent.addEventListener('click', () => this.showAgentLauncher());
|
|
96
|
+
|
|
97
|
+
// Settings
|
|
98
|
+
this.el.logoutBtn.addEventListener('click', () => this.logout());
|
|
99
|
+
this.el.saveSettings.addEventListener('click', () => this.saveSettings());
|
|
100
|
+
|
|
101
|
+
// Background messages
|
|
102
|
+
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
103
|
+
this.handleMessage(request);
|
|
104
|
+
return true;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ===========================================================================
|
|
109
|
+
// Auth
|
|
110
|
+
// ===========================================================================
|
|
111
|
+
|
|
112
|
+
async checkAuth() {
|
|
113
|
+
// Always show tabs and initialize tools — auth only gates chat
|
|
114
|
+
this.el.tabBar.classList.remove('hidden');
|
|
115
|
+
this.el.authSection.classList.add('hidden');
|
|
116
|
+
this.switchTab('tools'); // Default to tools (always works)
|
|
117
|
+
|
|
118
|
+
// Initialize features that work without auth
|
|
119
|
+
this.connectToMCP();
|
|
120
|
+
this.refreshTabFilesystem();
|
|
121
|
+
this.checkWebGPU();
|
|
122
|
+
this.loadSettings();
|
|
123
|
+
this.startMonitoring();
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const response = await chrome.runtime.sendMessage({ action: 'auth.status' });
|
|
127
|
+
if (response?.success && response.authenticated) {
|
|
128
|
+
this.setUser(response.user);
|
|
129
|
+
this.switchTab('chat');
|
|
130
|
+
} else {
|
|
131
|
+
this.showChatLoginPrompt();
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error('Auth check failed:', error);
|
|
135
|
+
this.showChatLoginPrompt();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async login() {
|
|
140
|
+
this.el.authBtn.disabled = true;
|
|
141
|
+
this.el.authBtn.textContent = 'Signing in...';
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const response = await chrome.runtime.sendMessage({ action: 'auth.login' });
|
|
145
|
+
if (response?.success) {
|
|
146
|
+
this.setUser(response.user);
|
|
147
|
+
this.hideChatLoginPrompt();
|
|
148
|
+
} else {
|
|
149
|
+
this.showError(response?.error || 'Sign in failed');
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
this.showError('Sign in failed');
|
|
153
|
+
} finally {
|
|
154
|
+
this.el.authBtn.disabled = false;
|
|
155
|
+
this.el.authBtn.textContent = 'Sign in with Hanzo';
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async logout() {
|
|
160
|
+
await chrome.runtime.sendMessage({ action: 'auth.logout' });
|
|
161
|
+
this.el.userBadge.classList.add('hidden');
|
|
162
|
+
this.el.userName.textContent = '';
|
|
163
|
+
this.el.userEmail.textContent = '';
|
|
164
|
+
this.showChatLoginPrompt();
|
|
165
|
+
this.switchTab('tools');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
setUser(user) {
|
|
169
|
+
this.authenticated = true;
|
|
170
|
+
if (user) {
|
|
171
|
+
const avatar = user.picture || user.avatar || '';
|
|
172
|
+
if (avatar) {
|
|
173
|
+
this.el.headerAvatar.src = avatar;
|
|
174
|
+
this.el.userBadge.classList.remove('hidden');
|
|
175
|
+
this.el.userAvatar.src = avatar;
|
|
176
|
+
}
|
|
177
|
+
this.el.userName.textContent = user.name || user.displayName || 'User';
|
|
178
|
+
this.el.userEmail.textContent = user.email || '';
|
|
179
|
+
}
|
|
180
|
+
this.loadModels();
|
|
181
|
+
this.loadConversation();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
showChatLoginPrompt() {
|
|
185
|
+
this.authenticated = false;
|
|
186
|
+
// Show login prompt in chat area instead of blocking entire sidebar
|
|
187
|
+
const chatLogin = document.getElementById('chat-login-prompt');
|
|
188
|
+
if (chatLogin) chatLogin.classList.remove('hidden');
|
|
189
|
+
const chatComposer = document.getElementById('chat-composer');
|
|
190
|
+
if (chatComposer) chatComposer.classList.add('hidden');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
hideChatLoginPrompt() {
|
|
194
|
+
this.authenticated = true;
|
|
195
|
+
const chatLogin = document.getElementById('chat-login-prompt');
|
|
196
|
+
if (chatLogin) chatLogin.classList.add('hidden');
|
|
197
|
+
const chatComposer = document.getElementById('chat-composer');
|
|
198
|
+
if (chatComposer) chatComposer.classList.remove('hidden');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ===========================================================================
|
|
202
|
+
// Tab Navigation
|
|
203
|
+
// ===========================================================================
|
|
204
|
+
|
|
205
|
+
switchTab(name) {
|
|
206
|
+
this.currentTab = name;
|
|
207
|
+
|
|
208
|
+
// Update tab buttons
|
|
209
|
+
document.querySelectorAll('.tab').forEach(t => {
|
|
210
|
+
t.classList.toggle('active', t.dataset.tab === name);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Show/hide content
|
|
214
|
+
document.querySelectorAll('.tab-content').forEach(t => t.classList.add('hidden'));
|
|
215
|
+
const target = document.getElementById(`tab-${name}`);
|
|
216
|
+
if (target) target.classList.remove('hidden');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ===========================================================================
|
|
220
|
+
// Chat
|
|
221
|
+
// ===========================================================================
|
|
222
|
+
|
|
223
|
+
async loadModels() {
|
|
224
|
+
try {
|
|
225
|
+
const response = await chrome.runtime.sendMessage({ action: 'chat.listModels' });
|
|
226
|
+
if (response?.success && response.models?.length) {
|
|
227
|
+
const select = this.el.modelSelect;
|
|
228
|
+
select.innerHTML = '';
|
|
229
|
+
for (const model of response.models) {
|
|
230
|
+
const opt = document.createElement('option');
|
|
231
|
+
opt.value = model.id;
|
|
232
|
+
opt.textContent = model.name || model.id;
|
|
233
|
+
select.appendChild(opt);
|
|
234
|
+
}
|
|
235
|
+
// Also populate settings default model
|
|
236
|
+
this.el.defaultModel.innerHTML = select.innerHTML;
|
|
237
|
+
}
|
|
238
|
+
} catch {
|
|
239
|
+
// Keep default options
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async sendMessage() {
|
|
244
|
+
const text = this.el.chatInput.value.trim();
|
|
245
|
+
if (!text || this.streaming) return;
|
|
246
|
+
if (!this.authenticated) {
|
|
247
|
+
this.showError('Please sign in to use chat');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Get token
|
|
252
|
+
const tokenResp = await chrome.runtime.sendMessage({ action: 'auth.getToken' });
|
|
253
|
+
if (!tokenResp?.success || !tokenResp.token) {
|
|
254
|
+
this.showError('Please sign in again');
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const userMessage = { role: 'user', content: text };
|
|
259
|
+
const model = this.el.modelSelect.value;
|
|
260
|
+
let requestMessages = [...this.messages, userMessage];
|
|
261
|
+
|
|
262
|
+
this.ragContext = null;
|
|
263
|
+
if (this.el.ragEnabled?.checked) {
|
|
264
|
+
this.setRagStatus('Retrieving context...');
|
|
265
|
+
const ragResp = await this.requestRagContext(text);
|
|
266
|
+
if (ragResp?.success && Array.isArray(ragResp.snippets) && ragResp.snippets.length) {
|
|
267
|
+
this.ragContext = ragResp;
|
|
268
|
+
requestMessages = [
|
|
269
|
+
{ role: 'system', content: this.buildRagSystemPrompt(ragResp) },
|
|
270
|
+
...requestMessages,
|
|
271
|
+
];
|
|
272
|
+
this.setRagStatus(`RAG: ${ragResp.snippets.length} snippet(s) from ${ragResp.source || 'context'}`);
|
|
273
|
+
} else if (ragResp?.error) {
|
|
274
|
+
this.setRagStatus(`RAG unavailable (${ragResp.error})`);
|
|
275
|
+
} else {
|
|
276
|
+
this.setRagStatus('No context found');
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
this.setRagStatus('RAG disabled');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Add user message to transcript
|
|
283
|
+
this.messages.push(userMessage);
|
|
284
|
+
this.appendMessage('user', text);
|
|
285
|
+
|
|
286
|
+
// Clear input
|
|
287
|
+
this.el.chatInput.value = '';
|
|
288
|
+
this.autoResize();
|
|
289
|
+
|
|
290
|
+
// Show typing
|
|
291
|
+
this.streaming = true;
|
|
292
|
+
this.el.sendBtn.disabled = true;
|
|
293
|
+
const typingEl = this.showTyping();
|
|
294
|
+
|
|
295
|
+
// Create assistant message element
|
|
296
|
+
const assistantEl = this.appendMessage('assistant', '');
|
|
297
|
+
let fullContent = '';
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
// Stream via direct fetch (sidebar has access via CSP)
|
|
301
|
+
const response = await fetch('https://api.hanzo.ai/v1/chat/completions', {
|
|
302
|
+
method: 'POST',
|
|
303
|
+
headers: {
|
|
304
|
+
'Content-Type': 'application/json',
|
|
305
|
+
'Authorization': `Bearer ${tokenResp.token}`,
|
|
306
|
+
},
|
|
307
|
+
body: JSON.stringify({
|
|
308
|
+
model,
|
|
309
|
+
messages: requestMessages,
|
|
310
|
+
stream: true,
|
|
311
|
+
}),
|
|
312
|
+
signal: (this.streamController = new AbortController()).signal,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
if (!response.ok) {
|
|
316
|
+
const errText = await response.text();
|
|
317
|
+
throw new Error(`API error ${response.status}: ${errText}`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
typingEl.remove();
|
|
321
|
+
const reader = response.body.getReader();
|
|
322
|
+
const decoder = new TextDecoder();
|
|
323
|
+
let buffer = '';
|
|
324
|
+
|
|
325
|
+
while (true) {
|
|
326
|
+
const { done, value } = await reader.read();
|
|
327
|
+
if (done) break;
|
|
328
|
+
|
|
329
|
+
buffer += decoder.decode(value, { stream: true });
|
|
330
|
+
const lines = buffer.split('\n');
|
|
331
|
+
buffer = lines.pop() || '';
|
|
332
|
+
|
|
333
|
+
for (const line of lines) {
|
|
334
|
+
const trimmed = line.trim();
|
|
335
|
+
if (!trimmed || trimmed.startsWith(':')) continue;
|
|
336
|
+
if (trimmed.startsWith('data: ')) {
|
|
337
|
+
const data = trimmed.slice(6);
|
|
338
|
+
if (data === '[DONE]') break;
|
|
339
|
+
try {
|
|
340
|
+
const parsed = JSON.parse(data);
|
|
341
|
+
const delta = parsed.choices?.[0]?.delta?.content;
|
|
342
|
+
if (delta) {
|
|
343
|
+
fullContent += delta;
|
|
344
|
+
assistantEl.innerHTML = this.renderMarkdown(fullContent);
|
|
345
|
+
this.scrollToBottom();
|
|
346
|
+
}
|
|
347
|
+
} catch {}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (this.ragContext?.snippets?.length) {
|
|
353
|
+
fullContent += this.buildRagCitationBlock(this.ragContext);
|
|
354
|
+
assistantEl.innerHTML = this.renderMarkdown(fullContent);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
this.messages.push({ role: 'assistant', content: fullContent });
|
|
358
|
+
this.saveConversation();
|
|
359
|
+
} catch (error) {
|
|
360
|
+
typingEl.remove();
|
|
361
|
+
if (error?.name !== 'AbortError') {
|
|
362
|
+
assistantEl.remove();
|
|
363
|
+
this.appendError(error?.message || 'Chat failed');
|
|
364
|
+
}
|
|
365
|
+
} finally {
|
|
366
|
+
this.streaming = false;
|
|
367
|
+
this.streamController = null;
|
|
368
|
+
this.el.sendBtn.disabled = false;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async getActiveTabContext() {
|
|
373
|
+
try {
|
|
374
|
+
const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
375
|
+
if (!activeTab) return null;
|
|
376
|
+
return {
|
|
377
|
+
url: activeTab.url || '',
|
|
378
|
+
title: activeTab.title || '',
|
|
379
|
+
};
|
|
380
|
+
} catch {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async requestRagContext(query) {
|
|
386
|
+
try {
|
|
387
|
+
const pageContext = this.el.tabContextEnabled?.checked ? await this.getActiveTabContext() : null;
|
|
388
|
+
const topK = Math.max(1, Math.min(parseInt(this.el.ragTopK?.value || '5', 10) || 5, 20));
|
|
389
|
+
|
|
390
|
+
return await chrome.runtime.sendMessage({
|
|
391
|
+
action: 'rag.query',
|
|
392
|
+
query,
|
|
393
|
+
topK,
|
|
394
|
+
includeTabContext: !!this.el.tabContextEnabled?.checked,
|
|
395
|
+
useZapMemory: !!this.el.ragUseZap?.checked,
|
|
396
|
+
knowledgeBase: this.el.ragKb?.value || '',
|
|
397
|
+
endpoint: this.el.ragEndpoint?.value || '',
|
|
398
|
+
apiKey: this.el.ragApiKey?.value || '',
|
|
399
|
+
pageContext: pageContext || undefined,
|
|
400
|
+
});
|
|
401
|
+
} catch (error) {
|
|
402
|
+
return { success: false, error: error?.message || 'Failed to query RAG' };
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
buildRagSystemPrompt(ragResponse) {
|
|
407
|
+
const snippets = (ragResponse?.snippets || []).slice(0, 8);
|
|
408
|
+
const lines = snippets.map((snippet, index) => {
|
|
409
|
+
const title = snippet.title || snippet.source || `Snippet ${index + 1}`;
|
|
410
|
+
const score = typeof snippet.score === 'number' ? ` (score ${snippet.score.toFixed(3)})` : '';
|
|
411
|
+
const sourceLine = snippet.url ? `${title}${score} — ${snippet.url}` : `${title}${score}`;
|
|
412
|
+
return `[${index + 1}] ${sourceLine}\n${snippet.content}`;
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
return [
|
|
416
|
+
'You are assisting inside Hanzo Browser Extension.',
|
|
417
|
+
'Use the retrieved context below when relevant, and state uncertainty if context is insufficient.',
|
|
418
|
+
'Retrieved Context:',
|
|
419
|
+
...lines,
|
|
420
|
+
].join('\n\n');
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
buildRagCitationBlock(ragResponse) {
|
|
424
|
+
const snippets = (ragResponse?.snippets || []).slice(0, 5);
|
|
425
|
+
if (!snippets.length) return '';
|
|
426
|
+
|
|
427
|
+
const rows = snippets.map((snippet, index) => {
|
|
428
|
+
const title = snippet.title || snippet.source || `Snippet ${index + 1}`;
|
|
429
|
+
if (snippet.url) {
|
|
430
|
+
return `- ${title}: ${snippet.url}`;
|
|
431
|
+
}
|
|
432
|
+
return `- ${title}`;
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
return `\n\n---\nContext Sources:\n${rows.join('\n')}`;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
setRagStatus(text) {
|
|
439
|
+
if (!this.el.ragStatus) return;
|
|
440
|
+
this.el.ragStatus.textContent = text || 'Ready';
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
appendMessage(role, content) {
|
|
444
|
+
// Remove welcome message
|
|
445
|
+
const welcome = this.el.chatMessages.querySelector('.chat-welcome');
|
|
446
|
+
if (welcome) welcome.remove();
|
|
447
|
+
|
|
448
|
+
const div = document.createElement('div');
|
|
449
|
+
div.className = `msg msg-${role}`;
|
|
450
|
+
div.innerHTML = role === 'assistant' ? this.renderMarkdown(content) : this.escapeHtml(content);
|
|
451
|
+
this.el.chatMessages.appendChild(div);
|
|
452
|
+
this.scrollToBottom();
|
|
453
|
+
return div;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
appendError(message) {
|
|
457
|
+
const div = document.createElement('div');
|
|
458
|
+
div.className = 'msg-error';
|
|
459
|
+
div.textContent = message;
|
|
460
|
+
this.el.chatMessages.appendChild(div);
|
|
461
|
+
this.scrollToBottom();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
showTyping() {
|
|
465
|
+
const div = document.createElement('div');
|
|
466
|
+
div.className = 'typing-indicator';
|
|
467
|
+
div.innerHTML = '<span></span><span></span><span></span>';
|
|
468
|
+
this.el.chatMessages.appendChild(div);
|
|
469
|
+
this.scrollToBottom();
|
|
470
|
+
return div;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
scrollToBottom() {
|
|
474
|
+
this.el.chatMessages.scrollTop = this.el.chatMessages.scrollHeight;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
autoResize() {
|
|
478
|
+
const el = this.el.chatInput;
|
|
479
|
+
el.style.height = 'auto';
|
|
480
|
+
el.style.height = Math.min(el.scrollHeight, 120) + 'px';
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Minimal markdown: code blocks, inline code, bold, italic, line breaks
|
|
484
|
+
renderMarkdown(text) {
|
|
485
|
+
if (!text) return '';
|
|
486
|
+
let html = this.escapeHtml(text);
|
|
487
|
+
|
|
488
|
+
// Code blocks: ```lang\n...\n```
|
|
489
|
+
html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => {
|
|
490
|
+
return `<pre><code>${code.trim()}</code></pre>`;
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// Inline code: `...`
|
|
494
|
+
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
495
|
+
|
|
496
|
+
// Bold: **...**
|
|
497
|
+
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
498
|
+
|
|
499
|
+
// Italic: *...*
|
|
500
|
+
html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
501
|
+
|
|
502
|
+
// Line breaks
|
|
503
|
+
html = html.replace(/\n/g, '<br>');
|
|
504
|
+
|
|
505
|
+
return html;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
escapeHtml(text) {
|
|
509
|
+
const div = document.createElement('div');
|
|
510
|
+
div.textContent = text;
|
|
511
|
+
return div.innerHTML;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async loadConversation() {
|
|
515
|
+
return new Promise((resolve) => {
|
|
516
|
+
chrome.storage.local.get(['hanzo_chat_messages'], (result) => {
|
|
517
|
+
if (result.hanzo_chat_messages?.length) {
|
|
518
|
+
this.messages = result.hanzo_chat_messages;
|
|
519
|
+
// Render stored messages
|
|
520
|
+
const welcome = this.el.chatMessages.querySelector('.chat-welcome');
|
|
521
|
+
if (welcome) welcome.remove();
|
|
522
|
+
for (const msg of this.messages) {
|
|
523
|
+
this.appendMessage(msg.role, msg.content);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
resolve();
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
saveConversation() {
|
|
532
|
+
// Keep last 50 messages
|
|
533
|
+
const toStore = this.messages.slice(-50);
|
|
534
|
+
chrome.storage.local.set({ hanzo_chat_messages: toStore });
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// ===========================================================================
|
|
538
|
+
// Tools Tab
|
|
539
|
+
// ===========================================================================
|
|
540
|
+
|
|
541
|
+
async connectToMCP() {
|
|
542
|
+
try {
|
|
543
|
+
const zapResponse = await chrome.runtime.sendMessage({ action: 'zap.status' });
|
|
544
|
+
if (zapResponse?.success) {
|
|
545
|
+
const zap = zapResponse.zap;
|
|
546
|
+
const toolCount = zap.mcps?.reduce((sum, m) => sum + (m.tools?.length || 0), 0) || 0;
|
|
547
|
+
this.updateMCPStatus(zap.connected, toolCount, zap.mcps?.length || 0);
|
|
548
|
+
if (zap.connected) {
|
|
549
|
+
const toolsResp = await chrome.runtime.sendMessage({ action: 'zap.listTools' });
|
|
550
|
+
this.updateMCPToolList(toolsResp?.success ? toolsResp.tools : []);
|
|
551
|
+
} else {
|
|
552
|
+
this.updateMCPToolList([]);
|
|
553
|
+
}
|
|
554
|
+
} else {
|
|
555
|
+
this.updateMCPStatus(false);
|
|
556
|
+
this.updateMCPToolList([]);
|
|
557
|
+
}
|
|
558
|
+
} catch {
|
|
559
|
+
this.updateMCPStatus(false);
|
|
560
|
+
this.updateMCPToolList([]);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
updateMCPStatus(connected, toolCount = 0, mcpCount = 0) {
|
|
565
|
+
this.el.mcpStatus.innerHTML = connected ? `
|
|
566
|
+
<span class="status-indicator connected"></span>
|
|
567
|
+
${mcpCount > 0 ? `ZAP: ${mcpCount} MCP(s)` : 'Connected'}
|
|
568
|
+
` : `
|
|
569
|
+
<span class="status-indicator disconnected"></span>
|
|
570
|
+
Disconnected
|
|
571
|
+
`;
|
|
572
|
+
this.el.mcpTools.textContent = toolCount;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
updateMCPToolList(tools) {
|
|
576
|
+
if (!this.el.mcpToolList) return;
|
|
577
|
+
if (!tools || !tools.length) {
|
|
578
|
+
this.el.mcpToolList.textContent = 'No tools discovered yet.';
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const names = Array.from(new Set(tools.map((tool) => tool.name))).sort();
|
|
583
|
+
const preview = names.slice(0, 20);
|
|
584
|
+
const suffix = names.length > preview.length ? `\n... +${names.length - preview.length} more` : '';
|
|
585
|
+
this.el.mcpToolList.textContent = `${preview.join('\n')}${suffix}`;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async refreshTabFilesystem() {
|
|
589
|
+
try {
|
|
590
|
+
const tabs = await chrome.tabs.query({});
|
|
591
|
+
this.el.tabFs.innerHTML = tabs.map((tab, i) => `
|
|
592
|
+
<div class="tab-item ${tab.active ? 'active' : ''}" data-tab-id="${tab.id}">
|
|
593
|
+
<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor">
|
|
594
|
+
<path d="M2 2v12h12V2H2zm1 1h10v10H3V3z"/>
|
|
595
|
+
</svg>
|
|
596
|
+
<span title="${this.escapeHtml(tab.url || '')}">/tabs/${i}/${this.sanitizePath(tab.title || 'untitled')}</span>
|
|
597
|
+
</div>
|
|
598
|
+
`).join('');
|
|
599
|
+
|
|
600
|
+
this.el.tabFs.querySelectorAll('.tab-item').forEach(item => {
|
|
601
|
+
item.addEventListener('click', () => {
|
|
602
|
+
chrome.tabs.update(parseInt(item.dataset.tabId), { active: true });
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
} catch {}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
sanitizePath(title) {
|
|
609
|
+
return title.replace(/[^a-zA-Z0-9-_]/g, '_').toLowerCase().substring(0, 30);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async checkWebGPU() {
|
|
613
|
+
try {
|
|
614
|
+
const response = await chrome.runtime.sendMessage({ action: 'checkWebGPU' });
|
|
615
|
+
if (response?.available) {
|
|
616
|
+
this.el.gpuStatus.innerHTML = `
|
|
617
|
+
<span class="status-indicator connected"></span>
|
|
618
|
+
Available
|
|
619
|
+
`;
|
|
620
|
+
this.el.gpuModel.textContent = response.adapter || 'Unknown';
|
|
621
|
+
} else {
|
|
622
|
+
this.el.gpuStatus.innerHTML = `
|
|
623
|
+
<span class="status-indicator disconnected"></span>
|
|
624
|
+
Not Available
|
|
625
|
+
`;
|
|
626
|
+
}
|
|
627
|
+
} catch {}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
updateAgentList() {
|
|
631
|
+
const arr = Array.from(this.agents.values());
|
|
632
|
+
this.el.agentCount.textContent = arr.length;
|
|
633
|
+
if (arr.length === 0) {
|
|
634
|
+
this.el.agentList.innerHTML = '<div class="empty-state">No active agents</div>';
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
this.el.agentList.innerHTML = arr.map(a => `
|
|
638
|
+
<div class="agent-item">
|
|
639
|
+
<div class="agent-icon">
|
|
640
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
641
|
+
<circle cx="12" cy="12" r="10" stroke-width="2"/>
|
|
642
|
+
<path d="M12 6v6l4 2" stroke-width="2"/>
|
|
643
|
+
</svg>
|
|
644
|
+
</div>
|
|
645
|
+
<div class="agent-info">
|
|
646
|
+
<div class="agent-name">${this.escapeHtml(a.name)}</div>
|
|
647
|
+
<div class="agent-status">${a.status} • Tab ${a.tabId}</div>
|
|
648
|
+
</div>
|
|
649
|
+
<div class="agent-actions">
|
|
650
|
+
<button class="icon-btn small" data-stop="${a.id}" title="Stop">
|
|
651
|
+
<svg viewBox="0 0 16 16" fill="currentColor">
|
|
652
|
+
<rect x="4" y="4" width="8" height="8" stroke-width="2"/>
|
|
653
|
+
</svg>
|
|
654
|
+
</button>
|
|
655
|
+
</div>
|
|
656
|
+
</div>
|
|
657
|
+
`).join('');
|
|
658
|
+
|
|
659
|
+
this.el.agentList.querySelectorAll('[data-stop]').forEach(btn => {
|
|
660
|
+
btn.addEventListener('click', () => {
|
|
661
|
+
const id = btn.dataset.stop;
|
|
662
|
+
chrome.runtime.sendMessage({ action: 'stopAgent', agentId: id });
|
|
663
|
+
this.agents.delete(id);
|
|
664
|
+
this.updateAgentList();
|
|
665
|
+
});
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
showAgentLauncher() {
|
|
670
|
+
// Create agent launcher modal overlay
|
|
671
|
+
const overlay = document.createElement('div');
|
|
672
|
+
overlay.className = 'modal-overlay';
|
|
673
|
+
overlay.innerHTML = `
|
|
674
|
+
<div class="modal-content">
|
|
675
|
+
<div class="modal-header">
|
|
676
|
+
<h3>Launch Agent</h3>
|
|
677
|
+
<button class="icon-btn close-modal">×</button>
|
|
678
|
+
</div>
|
|
679
|
+
<div class="modal-body">
|
|
680
|
+
<label class="setting-item">
|
|
681
|
+
<span>Target Tab</span>
|
|
682
|
+
<select id="agent-tab-select">
|
|
683
|
+
<option value="active">Active Tab</option>
|
|
684
|
+
</select>
|
|
685
|
+
</label>
|
|
686
|
+
<label class="setting-item">
|
|
687
|
+
<span>Agent Type</span>
|
|
688
|
+
<select id="agent-type-select">
|
|
689
|
+
<option value="browser-control">Browser Automation</option>
|
|
690
|
+
<option value="page-analyzer">Page Analyzer</option>
|
|
691
|
+
<option value="form-filler">Form Filler</option>
|
|
692
|
+
<option value="data-extractor">Data Extractor</option>
|
|
693
|
+
</select>
|
|
694
|
+
</label>
|
|
695
|
+
<label class="setting-item">
|
|
696
|
+
<span>Instructions</span>
|
|
697
|
+
<textarea id="agent-instructions" rows="3" placeholder="Describe what the agent should do..."></textarea>
|
|
698
|
+
</label>
|
|
699
|
+
</div>
|
|
700
|
+
<div class="modal-footer">
|
|
701
|
+
<button class="secondary-btn cancel-modal">Cancel</button>
|
|
702
|
+
<button class="primary-btn launch-modal">Launch</button>
|
|
703
|
+
</div>
|
|
704
|
+
</div>
|
|
705
|
+
`;
|
|
706
|
+
document.body.appendChild(overlay);
|
|
707
|
+
|
|
708
|
+
// Populate tabs
|
|
709
|
+
chrome.tabs.query({}, (tabs) => {
|
|
710
|
+
const select = overlay.querySelector('#agent-tab-select');
|
|
711
|
+
tabs.forEach(tab => {
|
|
712
|
+
if (tab.id) {
|
|
713
|
+
const opt = document.createElement('option');
|
|
714
|
+
opt.value = tab.id;
|
|
715
|
+
opt.textContent = `${tab.title?.substring(0, 40) || 'Untitled'} ${tab.active ? '(active)' : ''}`;
|
|
716
|
+
select.appendChild(opt);
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
// Close handlers
|
|
722
|
+
overlay.querySelector('.close-modal').addEventListener('click', () => overlay.remove());
|
|
723
|
+
overlay.querySelector('.cancel-modal').addEventListener('click', () => overlay.remove());
|
|
724
|
+
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
|
725
|
+
|
|
726
|
+
// Launch handler
|
|
727
|
+
overlay.querySelector('.launch-modal').addEventListener('click', async () => {
|
|
728
|
+
const tabSelect = overlay.querySelector('#agent-tab-select');
|
|
729
|
+
const typeSelect = overlay.querySelector('#agent-type-select');
|
|
730
|
+
const instructions = overlay.querySelector('#agent-instructions');
|
|
731
|
+
|
|
732
|
+
let tabId;
|
|
733
|
+
if (tabSelect.value === 'active') {
|
|
734
|
+
const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
735
|
+
tabId = activeTab?.id;
|
|
736
|
+
} else {
|
|
737
|
+
tabId = parseInt(tabSelect.value);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (!tabId) {
|
|
741
|
+
this.showError('No tab selected');
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const agentType = typeSelect.value;
|
|
746
|
+
const agentInstructions = instructions.value?.trim() || '';
|
|
747
|
+
overlay.remove();
|
|
748
|
+
|
|
749
|
+
try {
|
|
750
|
+
const response = await chrome.runtime.sendMessage({
|
|
751
|
+
action: 'launchAIWorker',
|
|
752
|
+
tabId,
|
|
753
|
+
model: agentType,
|
|
754
|
+
instructions: agentInstructions,
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
if (response?.success) {
|
|
758
|
+
const agentId = response.agentId || `agent-${Date.now().toString(36)}`;
|
|
759
|
+
this.agents.set(agentId, {
|
|
760
|
+
id: agentId,
|
|
761
|
+
name: agentType,
|
|
762
|
+
tabId,
|
|
763
|
+
status: 'running',
|
|
764
|
+
});
|
|
765
|
+
this.updateAgentList();
|
|
766
|
+
this.showNotification(`Agent launched on tab ${tabId}`);
|
|
767
|
+
} else {
|
|
768
|
+
this.showError(response?.error || 'Failed to launch agent');
|
|
769
|
+
}
|
|
770
|
+
} catch (error) {
|
|
771
|
+
this.showError('Failed to launch agent: ' + error.message);
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// ===========================================================================
|
|
777
|
+
// Settings Tab
|
|
778
|
+
// ===========================================================================
|
|
779
|
+
|
|
780
|
+
loadSettings() {
|
|
781
|
+
chrome.storage.local.get([
|
|
782
|
+
'mcpPort',
|
|
783
|
+
'cdpPort',
|
|
784
|
+
'zapPorts',
|
|
785
|
+
'hanzo_default_model',
|
|
786
|
+
'hanzo_rag_endpoint',
|
|
787
|
+
'hanzo_rag_api_key',
|
|
788
|
+
'hanzo_rag_kb',
|
|
789
|
+
'hanzo_rag_top_k',
|
|
790
|
+
'hanzo_rag_include_tab_context',
|
|
791
|
+
'hanzo_rag_use_zap',
|
|
792
|
+
'hanzo_chat_rag_enabled',
|
|
793
|
+
'hanzo_chat_tab_context_enabled',
|
|
794
|
+
], (result) => {
|
|
795
|
+
const mcpPort = document.getElementById('mcp-port-setting');
|
|
796
|
+
const cdpPort = document.getElementById('cdp-port-setting');
|
|
797
|
+
const zapPorts = document.getElementById('zap-ports-setting');
|
|
798
|
+
|
|
799
|
+
if (mcpPort) mcpPort.value = result.mcpPort || 3001;
|
|
800
|
+
if (cdpPort) cdpPort.value = result.cdpPort || 9223;
|
|
801
|
+
if (zapPorts) zapPorts.value = (result.zapPorts || [9999,9998,9997,9996,9995]).join(',');
|
|
802
|
+
if (result.hanzo_default_model && this.el.defaultModel) {
|
|
803
|
+
this.el.defaultModel.value = result.hanzo_default_model;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (this.el.ragEndpoint) this.el.ragEndpoint.value = result.hanzo_rag_endpoint || '';
|
|
807
|
+
if (this.el.ragApiKey) this.el.ragApiKey.value = result.hanzo_rag_api_key || '';
|
|
808
|
+
if (this.el.ragKb) this.el.ragKb.value = result.hanzo_rag_kb || '';
|
|
809
|
+
if (this.el.ragTopK) this.el.ragTopK.value = String(result.hanzo_rag_top_k || 5);
|
|
810
|
+
if (this.el.ragIncludeTabContext) this.el.ragIncludeTabContext.checked = result.hanzo_rag_include_tab_context !== false;
|
|
811
|
+
if (this.el.ragUseZap) this.el.ragUseZap.checked = result.hanzo_rag_use_zap !== false;
|
|
812
|
+
if (this.el.ragEnabled) this.el.ragEnabled.checked = result.hanzo_chat_rag_enabled !== false;
|
|
813
|
+
if (this.el.tabContextEnabled) this.el.tabContextEnabled.checked = result.hanzo_chat_tab_context_enabled !== false;
|
|
814
|
+
this.setRagStatus(this.el.ragEnabled?.checked ? 'Ready' : 'RAG disabled');
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
async saveSettings() {
|
|
819
|
+
const mcpPort = parseInt(document.getElementById('mcp-port-setting')?.value) || 3001;
|
|
820
|
+
const cdpPort = parseInt(document.getElementById('cdp-port-setting')?.value) || 9223;
|
|
821
|
+
const zapPorts = (document.getElementById('zap-ports-setting')?.value || '')
|
|
822
|
+
.split(',').map(p => parseInt(p.trim())).filter(p => p > 0 && p < 65536);
|
|
823
|
+
|
|
824
|
+
await chrome.storage.local.set({
|
|
825
|
+
mcpPort,
|
|
826
|
+
cdpPort,
|
|
827
|
+
zapPorts: zapPorts.length ? zapPorts : [9999,9998,9997,9996,9995],
|
|
828
|
+
hanzo_default_model: this.el.defaultModel?.value || 'gpt-4o',
|
|
829
|
+
'safe-mode': document.getElementById('safe-mode')?.checked,
|
|
830
|
+
'enable-webgpu': document.getElementById('enable-webgpu')?.checked,
|
|
831
|
+
maxAgents: parseInt(document.getElementById('max-agents')?.value) || 3,
|
|
832
|
+
hanzo_rag_endpoint: this.el.ragEndpoint?.value?.trim() || '',
|
|
833
|
+
hanzo_rag_api_key: this.el.ragApiKey?.value?.trim() || '',
|
|
834
|
+
hanzo_rag_kb: this.el.ragKb?.value?.trim() || '',
|
|
835
|
+
hanzo_rag_top_k: Math.max(1, Math.min(parseInt(this.el.ragTopK?.value || '5', 10) || 5, 20)),
|
|
836
|
+
hanzo_rag_include_tab_context: !!this.el.ragIncludeTabContext?.checked,
|
|
837
|
+
hanzo_rag_use_zap: !!this.el.ragUseZap?.checked,
|
|
838
|
+
hanzo_chat_rag_enabled: !!this.el.ragEnabled?.checked,
|
|
839
|
+
hanzo_chat_tab_context_enabled: !!this.el.tabContextEnabled?.checked,
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
this.showNotification('Settings saved');
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// ===========================================================================
|
|
846
|
+
// Utility
|
|
847
|
+
// ===========================================================================
|
|
848
|
+
|
|
849
|
+
startMonitoring() {
|
|
850
|
+
setInterval(() => this.connectToMCP(), 10000);
|
|
851
|
+
chrome.tabs.onUpdated.addListener(() => this.refreshTabFilesystem());
|
|
852
|
+
chrome.tabs.onRemoved.addListener(() => this.refreshTabFilesystem());
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
handleMessage(request) {
|
|
856
|
+
switch (request.action) {
|
|
857
|
+
case 'agentUpdate':
|
|
858
|
+
if (request.agentId && this.agents.has(request.agentId)) {
|
|
859
|
+
this.agents.get(request.agentId).status = request.status;
|
|
860
|
+
this.updateAgentList();
|
|
861
|
+
}
|
|
862
|
+
break;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
showNotification(message) {
|
|
867
|
+
const el = document.createElement('div');
|
|
868
|
+
el.className = 'notification';
|
|
869
|
+
el.textContent = message;
|
|
870
|
+
document.body.appendChild(el);
|
|
871
|
+
setTimeout(() => el.remove(), 3000);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
showError(message) {
|
|
875
|
+
const el = document.createElement('div');
|
|
876
|
+
el.className = 'notification error';
|
|
877
|
+
el.textContent = message;
|
|
878
|
+
document.body.appendChild(el);
|
|
879
|
+
setTimeout(() => el.remove(), 5000);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Initialize
|
|
884
|
+
const hanzoSidebar = new HanzoSidebar();
|