Skip to content

Commit f2f9fee

Browse files
committed
Add multi-browser support with user agent tracking and browser selection UI
1 parent 0b089da commit f2f9fee

7 files changed

Lines changed: 215 additions & 70 deletions

File tree

‎apps/browser/src/background/websocket.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ export const connect_websocket = async (): Promise<void> => {
3535

3636
const manifest = browser.runtime.getManifest()
3737
const version = manifest.version
38+
const user_agent = navigator.userAgent
3839

3940
websocket = new WebSocket(
40-
`ws://localhost:${DEFAULT_PORT}?token=${SECURITY_TOKENS.BROWSERS}&version=${version}`
41+
`ws://localhost:${DEFAULT_PORT}?token=${SECURITY_TOKENS.BROWSERS}&version=${version}&user_agent=${encodeURIComponent(user_agent)}`
4142
)
4243

4344
websocket.onopen = () => {

‎apps/browser/src/manifest.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"name": "Code Web Chat Connector",
55
"short_name": "CWC",
66
"description": "Autofill your coding prompts in chatbots.",
7-
"version": "1.94.0",
7+
"version": "1.95.0",
88
"homepage_url": "https://github.com/robertpiosik/CodeWebChat",
99
"icons": {
1010
"16": "icons/icon-16.png",

‎apps/editor/src/constants/state-keys.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
// Note: Use kebab-case for new keys
21
export const LAST_APPLIED_CHANGES_STATE_KEY = 'last-applied-changes-state'
32
export const LAST_APPLIED_CHANGES_EDITOR_STATE_STATE_KEY =
43
'last-applied-changes-editor-state'
54
export const LAST_APPLIED_CLIPBOARD_CONTENT_STATE_KEY =
65
'last-applied-clipboard-content'
76
export const QUICK_SAVES_STATE_KEY = 'quick-saves'
8-
export const LAST_CONTEXT_SAVE_LOCATION_STATE_KEY = 'lastSaveLocation'
7+
export const LAST_CONTEXT_SAVE_LOCATION_STATE_KEY = 'last-save-location'
98
export const RANGES_STATE_KEY = 'ranges'
109
export const LAST_RANGE_SAVE_LOCATION_STATE_KEY = 'last-range-save-location'
1110
export const LAST_APPLY_CONTEXT_OPTION_STATE_KEY = 'last-apply-context-option'
@@ -73,6 +72,8 @@ export const get_recently_used_presets_or_groups_key = (
7372
web_prompt_type: string
7473
) => `recently-used-presets-or-groups-${web_prompt_type}`
7574

75+
export const LAST_SELECTED_BROWSER_ID_STATE_KEY = 'last-selected-browser-id'
76+
7677
export type DuplicateWorkspaceContext = {
7778
checked_files: string[]
7879
checked_files_timestamps?: Record<string, number>

‎apps/editor/src/services/websocket-manager.ts‎

Lines changed: 113 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ import * as vscode from 'vscode'
33
import * as child_process from 'child_process'
44
import * as path from 'path'
55
import * as net from 'net'
6-
import { InitializeChatMessage } from '@shared/types/websocket-message'
6+
import {
7+
ConnectedBrowser,
8+
InitializeChatMessage
9+
} from '@shared/types/websocket-message'
710
import { CHATBOTS } from '@shared/constants/chatbots'
811
import { DEFAULT_PORT, SECURITY_TOKENS } from '@shared/constants/websocket'
912
import { dictionary } from '@shared/constants/dictionary'
1013
import { Logger } from '@shared/utils/logger'
1114
import { Preset } from '@shared/types/preset'
1215
import { ConfigPresetFormat } from '@/views/panel/backend/utils/preset-format-converters'
16+
import { LAST_SELECTED_BROWSER_ID_STATE_KEY } from '@/constants/state-keys'
1317

1418
/**
1519
* Bridges the current workspace window and websocket server that runs in a separate process.
@@ -21,6 +25,7 @@ export class WebSocketManager {
2125
private client: WebSocket.WebSocket | null = null
2226
private _on_connection_status_change: vscode.EventEmitter<boolean> =
2327
new vscode.EventEmitter<boolean>()
28+
private connected_browsers: ConnectedBrowser[] = []
2429
private reconnect_timer: NodeJS.Timeout | null = null
2530
private has_connected_browsers: boolean = false
2631
private client_id: number | null = null
@@ -156,7 +161,8 @@ export class WebSocketManager {
156161
if (message.action == 'client-id-assignment') {
157162
this.client_id = message.client_id
158163
} else if (message.action == 'browser-connection-status') {
159-
this.has_connected_browsers = message.has_connected_browsers
164+
this.connected_browsers = message.connected_browsers
165+
this.has_connected_browsers = this.connected_browsers.length > 0
160166
this._on_connection_status_change.fire(this.has_connected_browsers)
161167
} else if (message.action == 'apply-chat-response') {
162168
vscode.commands.executeCommand('codeWebChat.applyChatResponse', {
@@ -244,6 +250,96 @@ export class WebSocketManager {
244250
return this.has_connected_browsers
245251
}
246252

253+
private _get_browser_name(user_agent: string): string {
254+
if (user_agent.includes('Edg/')) {
255+
return 'Edge'
256+
} else if (user_agent.includes('Chrome/')) {
257+
return 'Chrome'
258+
} else if (user_agent.includes('Firefox/')) {
259+
return 'Firefox'
260+
} else if (user_agent.includes('Safari/')) {
261+
return 'Safari'
262+
} else {
263+
return 'Browser'
264+
}
265+
}
266+
267+
private async _select_browser(): Promise<number | undefined> {
268+
if (this.connected_browsers.length == 0) {
269+
return undefined
270+
}
271+
272+
if (this.connected_browsers.length == 1) {
273+
const id = this.connected_browsers[0].id
274+
await this.context.workspaceState.update(
275+
LAST_SELECTED_BROWSER_ID_STATE_KEY,
276+
id
277+
)
278+
return id
279+
}
280+
281+
const last_selected_browser_id = this.context.workspaceState.get<number>(
282+
LAST_SELECTED_BROWSER_ID_STATE_KEY
283+
)
284+
285+
const items = this.connected_browsers.map((b) => ({
286+
label: this._get_browser_name(b.user_agent),
287+
detail: b.user_agent,
288+
id: b.id,
289+
picked: b.id == last_selected_browser_id
290+
}))
291+
292+
return new Promise<number | undefined>((resolve) => {
293+
const quick_pick = vscode.window.createQuickPick<
294+
vscode.QuickPickItem & { id: number }
295+
>()
296+
quick_pick.items = items
297+
quick_pick.placeholder = 'Select a browser to use'
298+
quick_pick.title = 'Connected Browsers'
299+
300+
const close_button = {
301+
iconPath: new vscode.ThemeIcon('close'),
302+
tooltip: 'Close'
303+
}
304+
305+
quick_pick.buttons = [close_button]
306+
307+
const disposables: vscode.Disposable[] = []
308+
309+
disposables.push(
310+
quick_pick.onDidTriggerButton((button) => {
311+
if (button === close_button) {
312+
quick_pick.hide()
313+
}
314+
})
315+
)
316+
317+
disposables.push(
318+
quick_pick.onDidAccept(async () => {
319+
const selected = quick_pick.selectedItems[0]
320+
if (selected) {
321+
await this.context.workspaceState.update(
322+
LAST_SELECTED_BROWSER_ID_STATE_KEY,
323+
selected.id
324+
)
325+
resolve(selected.id)
326+
}
327+
quick_pick.hide()
328+
})
329+
)
330+
331+
disposables.push(
332+
quick_pick.onDidHide(() => {
333+
resolve(undefined)
334+
disposables.forEach((d) => d.dispose())
335+
quick_pick.dispose()
336+
})
337+
)
338+
339+
quick_pick.show()
340+
})
341+
}
342+
247343
public async initialize_chats(params: {
248344
chats: Array<{
249345
text: string
@@ -253,7 +349,7 @@ export class WebSocketManager {
253349
prompt_type: any
254350
}>
255351
presets_config_key: string
256-
}): Promise<void> {
352+
}): Promise<boolean> {
257353
if (!this.has_connected_browsers) {
258354
throw new Error('Does not have connected browsers.')
259355
}
@@ -264,6 +360,11 @@ export class WebSocketManager {
264360
const gemini_user_id = config.get<number | null>('geminiUserId')
265361
const ai_studio_user_id = config.get<number | null>('aiStudioUserId')
266362

363+
const target_browser_id = await this._select_browser()
364+
if (target_browser_id === undefined) {
365+
return false // User cancelled or no browser
366+
}
367+
267368
for (const chat of params.chats) {
268369
const preset = web_chat_presets.find((p) => p.name == chat.preset_name)
269370
if (!preset) {
@@ -320,6 +421,7 @@ export class WebSocketManager {
320421
url,
321422
model: preset.model,
322423
temperature: preset.temperature,
424+
target_browser_id,
323425
top_p: preset.topP,
324426
thinking_budget: preset.thinkingBudget,
325427
reasoning_effort: preset.reasoningEffort,
@@ -339,6 +441,8 @@ export class WebSocketManager {
339441

340442
this.client?.send(JSON.stringify(message))
341443
}
444+
445+
return true
342446
}
343447

344448
public async preview_preset(params: {
@@ -351,6 +455,11 @@ export class WebSocketManager {
351455
throw new Error('Does not have connected browsers.')
352456
}
353457

458+
const target_browser_id = await this._select_browser()
459+
if (target_browser_id === undefined) {
460+
return // User cancelled
461+
}
462+
354463
const config = vscode.workspace.getConfiguration('codeWebChat')
355464
const gemini_user_number = config.get<number | null>('geminiUserNumber')
356465
const ai_studio_user_id = config.get<number | null>('aiStudioUserId')
@@ -409,6 +518,7 @@ export class WebSocketManager {
409518
url,
410519
model: params.preset.model,
411520
temperature: params.preset.temperature,
521+
target_browser_id,
412522
top_p: params.preset.top_p,
413523
thinking_budget: params.preset.thinking_budget,
414524
reasoning_effort: params.preset.reasoning_effort,

0 commit comments

Comments
 (0)