@@ -3,13 +3,17 @@ import * as vscode from 'vscode'
33import * as child_process from 'child_process'
44import * as path from 'path'
55import * as net from 'net'
6- import { InitializeChatMessage } from '@shared/types/websocket-message'
6+ import {
7+ ConnectedBrowser ,
8+ InitializeChatMessage
9+ } from '@shared/types/websocket-message'
710import { CHATBOTS } from '@shared/constants/chatbots'
811import { DEFAULT_PORT , SECURITY_TOKENS } from '@shared/constants/websocket'
912import { dictionary } from '@shared/constants/dictionary'
1013import { Logger } from '@shared/utils/logger'
1114import { Preset } from '@shared/types/preset'
1215import { 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