vibesurf 0.1.20__py3-none-any.whl → 0.1.21__py3-none-any.whl

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.

Potentially problematic release.


This version of vibesurf might be problematic. Click here for more details.

Files changed (40) hide show
  1. vibe_surf/_version.py +2 -2
  2. vibe_surf/backend/api/task.py +1 -1
  3. vibe_surf/backend/api/voices.py +481 -0
  4. vibe_surf/backend/database/migrations/v004_add_voice_profiles.sql +35 -0
  5. vibe_surf/backend/database/models.py +38 -1
  6. vibe_surf/backend/database/queries.py +189 -1
  7. vibe_surf/backend/main.py +2 -0
  8. vibe_surf/backend/shared_state.py +1 -1
  9. vibe_surf/backend/voice_model_config.py +25 -0
  10. vibe_surf/browser/agen_browser_profile.py +2 -0
  11. vibe_surf/browser/agent_browser_session.py +3 -3
  12. vibe_surf/chrome_extension/background.js +224 -9
  13. vibe_surf/chrome_extension/content.js +147 -0
  14. vibe_surf/chrome_extension/manifest.json +11 -2
  15. vibe_surf/chrome_extension/permission-iframe.html +38 -0
  16. vibe_surf/chrome_extension/permission-request.html +104 -0
  17. vibe_surf/chrome_extension/scripts/api-client.js +61 -0
  18. vibe_surf/chrome_extension/scripts/main.js +8 -2
  19. vibe_surf/chrome_extension/scripts/permission-iframe-request.js +188 -0
  20. vibe_surf/chrome_extension/scripts/permission-request.js +118 -0
  21. vibe_surf/chrome_extension/scripts/settings-manager.js +690 -3
  22. vibe_surf/chrome_extension/scripts/ui-manager.js +730 -119
  23. vibe_surf/chrome_extension/scripts/user-settings-storage.js +422 -0
  24. vibe_surf/chrome_extension/scripts/voice-recorder.js +514 -0
  25. vibe_surf/chrome_extension/sidepanel.html +106 -29
  26. vibe_surf/chrome_extension/styles/components.css +35 -0
  27. vibe_surf/chrome_extension/styles/input.css +164 -1
  28. vibe_surf/chrome_extension/styles/layout.css +1 -1
  29. vibe_surf/chrome_extension/styles/settings-environment.css +138 -0
  30. vibe_surf/chrome_extension/styles/settings-forms.css +7 -7
  31. vibe_surf/chrome_extension/styles/variables.css +51 -0
  32. vibe_surf/tools/voice_asr.py +79 -8
  33. {vibesurf-0.1.20.dist-info → vibesurf-0.1.21.dist-info}/METADATA +8 -12
  34. {vibesurf-0.1.20.dist-info → vibesurf-0.1.21.dist-info}/RECORD +38 -31
  35. vibe_surf/chrome_extension/icons/convert-svg.js +0 -33
  36. vibe_surf/chrome_extension/icons/logo-preview.html +0 -187
  37. {vibesurf-0.1.20.dist-info → vibesurf-0.1.21.dist-info}/WHEEL +0 -0
  38. {vibesurf-0.1.20.dist-info → vibesurf-0.1.21.dist-info}/entry_points.txt +0 -0
  39. {vibesurf-0.1.20.dist-info → vibesurf-0.1.21.dist-info}/licenses/LICENSE +0 -0
  40. {vibesurf-0.1.20.dist-info → vibesurf-0.1.21.dist-info}/top_level.txt +0 -0
@@ -17,6 +17,13 @@ class VibeSurfUIManager {
17
17
  this.historyManager = null;
18
18
  this.fileManager = null;
19
19
  this.modalManager = null;
20
+ this.voiceRecorder = null;
21
+
22
+ // Initialize user settings storage
23
+ this.userSettingsStorage = null;
24
+
25
+ // Track if we're currently restoring selections to prevent override
26
+ this.isRestoringSelections = false;
20
27
 
21
28
  this.bindElements();
22
29
  this.initializeTabSelector(); // Initialize tab selector before binding events
@@ -51,6 +58,7 @@ class VibeSurfUIManager {
51
58
  agentModeSelect: document.getElementById('agent-mode-select'),
52
59
  taskInput: document.getElementById('task-input'),
53
60
  sendBtn: document.getElementById('send-btn'),
61
+ voiceRecordBtn: document.getElementById('voice-record-btn'),
54
62
 
55
63
  // Tab selector elements
56
64
  tabSelectorDropdown: document.getElementById('tab-selector-dropdown'),
@@ -77,6 +85,13 @@ class VibeSurfUIManager {
77
85
  // Initialize modal manager first (others may depend on it)
78
86
  this.modalManager = new VibeSurfModalManager();
79
87
 
88
+ // Initialize user settings storage
89
+ this.userSettingsStorage = new VibeSurfUserSettingsStorage();
90
+
91
+ // Initialize voice recorder
92
+ this.voiceRecorder = new VibeSurfVoiceRecorder(this.apiClient);
93
+ this.setupVoiceRecorderCallbacks();
94
+
80
95
  // Initialize other managers
81
96
  this.settingsManager = new VibeSurfSettingsManager(this.apiClient);
82
97
  this.historyManager = new VibeSurfHistoryManager(this.apiClient);
@@ -85,7 +100,7 @@ class VibeSurfUIManager {
85
100
  // Set up inter-manager communication
86
101
  this.setupManagerEvents();
87
102
 
88
- console.log('[UIManager] All specialized managers initialized');
103
+
89
104
  } catch (error) {
90
105
  console.error('[UIManager] Failed to initialize managers:', error);
91
106
  this.showNotification('Failed to initialize UI components', 'error');
@@ -95,7 +110,14 @@ class VibeSurfUIManager {
95
110
  setupManagerEvents() {
96
111
  // Settings Manager Events
97
112
  this.settingsManager.on('profilesUpdated', () => {
98
- this.updateLLMProfileSelect();
113
+ // Only update the profile select if we're not in the middle of restoring selections
114
+ // This prevents the profilesUpdated event from overriding user selections during initialization
115
+ if (!this.isRestoringSelections) {
116
+ this.updateLLMProfileSelect();
117
+ }
118
+
119
+ // Also update voice button state when profiles are updated (ASR profiles might have changed)
120
+ this.updateVoiceButtonState();
99
121
  });
100
122
 
101
123
  this.settingsManager.on('notification', (data) => {
@@ -213,6 +235,113 @@ class VibeSurfUIManager {
213
235
  });
214
236
  }
215
237
 
238
+ // Voice Recorder Callbacks
239
+ setupVoiceRecorderCallbacks() {
240
+ this.voiceRecorder.setCallbacks({
241
+ onRecordingStart: () => {
242
+ console.log('[UIManager] Voice recording started');
243
+ this.updateVoiceRecordingUI(true);
244
+ },
245
+ onRecordingStop: (audioBlob, duration) => {
246
+ console.log(`[UIManager] Voice recording stopped after ${duration}ms`);
247
+ this.updateVoiceRecordingUI(false);
248
+ },
249
+ onTranscriptionComplete: (transcribedText, result) => {
250
+ console.log(`[UIManager] Transcription completed: "${transcribedText}"`);
251
+ this.handleTranscriptionComplete(transcribedText);
252
+ },
253
+ onTranscriptionError: (errorMessage, errorType) => {
254
+ console.error(`[UIManager] Transcription error: ${errorMessage}`);
255
+ this.handleTranscriptionError(errorMessage, errorType);
256
+ },
257
+ onDurationUpdate: (formattedDuration, durationMs) => {
258
+ this.updateRecordingDuration(formattedDuration, durationMs);
259
+ }
260
+ });
261
+ }
262
+
263
+ // Update voice recording UI state
264
+ updateVoiceRecordingUI(isRecording) {
265
+ if (this.elements.voiceRecordBtn) {
266
+ if (isRecording) {
267
+ this.elements.voiceRecordBtn.classList.add('recording');
268
+ this.elements.voiceRecordBtn.setAttribute('title', 'Stop Recording');
269
+ this.elements.voiceRecordBtn.setAttribute('data-tooltip', 'Recording... Click to stop');
270
+ } else {
271
+ this.elements.voiceRecordBtn.classList.remove('recording');
272
+ this.elements.voiceRecordBtn.setAttribute('title', 'Voice Input');
273
+ this.elements.voiceRecordBtn.setAttribute('data-tooltip', 'Click to start voice recording');
274
+
275
+ // Clear duration display
276
+ this.updateRecordingDuration('0:00', 0);
277
+ }
278
+ }
279
+ }
280
+
281
+ // Update recording duration display
282
+ updateRecordingDuration(formattedDuration, durationMs) {
283
+ if (this.elements.voiceRecordBtn) {
284
+ // Update the tooltip with duration
285
+ this.elements.voiceRecordBtn.setAttribute('data-tooltip', `Recording... ${formattedDuration} - Click to stop`);
286
+
287
+ // Create or update duration display element
288
+ let durationElement = this.elements.voiceRecordBtn.querySelector('.recording-duration');
289
+ if (!durationElement) {
290
+ durationElement = document.createElement('div');
291
+ durationElement.className = 'recording-duration';
292
+ this.elements.voiceRecordBtn.appendChild(durationElement);
293
+ }
294
+
295
+ durationElement.textContent = formattedDuration;
296
+ }
297
+ }
298
+
299
+ // Handle transcription completion
300
+ handleTranscriptionComplete(transcribedText) {
301
+ if (!this.elements.taskInput) {
302
+ console.error('[UIManager] Task input element not found');
303
+ return;
304
+ }
305
+
306
+ // Insert transcribed text into the input field
307
+ const currentValue = this.elements.taskInput.value;
308
+ const cursorPosition = this.elements.taskInput.selectionStart;
309
+
310
+ // Insert at cursor position or append to end
311
+ const beforeCursor = currentValue.substring(0, cursorPosition);
312
+ const afterCursor = currentValue.substring(cursorPosition);
313
+ const newValue = beforeCursor + transcribedText + afterCursor;
314
+
315
+ this.elements.taskInput.value = newValue;
316
+
317
+ // Trigger input change event for validation and auto-resize
318
+ this.handleTaskInputChange({ target: this.elements.taskInput });
319
+
320
+ // Set cursor position after the inserted text
321
+ const newCursorPosition = cursorPosition + transcribedText.length;
322
+ this.elements.taskInput.setSelectionRange(newCursorPosition, newCursorPosition);
323
+ this.elements.taskInput.focus();
324
+
325
+ this.showNotification('Voice transcription completed', 'success');
326
+ }
327
+
328
+ // Handle transcription errors
329
+ handleTranscriptionError(errorMessage, errorType) {
330
+ let userMessage = 'Voice transcription failed';
331
+
332
+ if (errorType === 'recording') {
333
+ userMessage = `Recording failed: ${errorMessage}`;
334
+ } else if (errorType === 'transcription') {
335
+ if (errorMessage.includes('No active ASR profiles')) {
336
+ userMessage = 'No voice recognition profiles configured. Please set up an ASR profile in Settings > Voice.';
337
+ } else {
338
+ userMessage = `Transcription failed: ${errorMessage}`;
339
+ }
340
+ }
341
+
342
+ this.showNotification(userMessage, 'error');
343
+ }
344
+
216
345
  bindEvents() {
217
346
  // Header buttons
218
347
  this.elements.newSessionBtn?.addEventListener('click', this.handleNewSession.bind(this));
@@ -229,6 +358,7 @@ class VibeSurfUIManager {
229
358
 
230
359
  // Input handling
231
360
  this.elements.sendBtn?.addEventListener('click', this.handleSendTask.bind(this));
361
+ this.elements.voiceRecordBtn?.addEventListener('click', this.handleVoiceRecord.bind(this));
232
362
 
233
363
  // Task input handling
234
364
  this.elements.taskInput?.addEventListener('keydown', this.handleTaskInputKeydown.bind(this));
@@ -294,7 +424,7 @@ class VibeSurfUIManager {
294
424
  }
295
425
 
296
426
  handleTaskSubmitted(data) {
297
- console.log('[UIManager] Task submitted successfully, showing control panel');
427
+
298
428
  this.updateControlPanel('running');
299
429
  this.clearTaskInput();
300
430
  }
@@ -319,21 +449,21 @@ class VibeSurfUIManager {
319
449
  }
320
450
 
321
451
  handleTaskCompleted(data) {
322
- console.log('[UIManager] Task completed with status:', data.status);
452
+
323
453
 
324
454
  const message = data.status === 'done' ? 'Task completed successfully!' : 'Task completed with errors';
325
455
  const type = data.status === 'done' ? 'success' : 'error';
326
456
 
327
457
  // Check if we need to respect minimum visibility period
328
458
  if (this.controlPanelMinVisibilityActive) {
329
- console.log('[UIManager] Task completed during minimum visibility period, delaying control panel hide');
459
+
330
460
  const remainingTime = 1000;
331
461
  setTimeout(() => {
332
- console.log('[UIManager] Minimum visibility period respected, now hiding control panel');
462
+
333
463
  this.updateControlPanel('ready');
334
464
  }, remainingTime);
335
465
  } else {
336
- console.log('[UIManager] Task completed, hiding control panel immediately');
466
+
337
467
  this.updateControlPanel('ready');
338
468
  }
339
469
 
@@ -363,17 +493,17 @@ class VibeSurfUIManager {
363
493
 
364
494
  handleTaskError(data) {
365
495
  console.error('[UIManager] Task error:', data.error);
366
- console.log('[UIManager] Task error data structure:', JSON.stringify(data, null, 2));
496
+
367
497
 
368
498
  // Check if this is an LLM connection failure
369
499
  if (data.error && typeof data.error === 'object' && data.error.error === 'llm_connection_failed') {
370
- console.log('[UIManager] Detected LLM connection failure from object error');
500
+
371
501
  // Show LLM connection failed modal instead of generic notification
372
502
  this.showLLMConnectionFailedModal(data.error);
373
503
  this.updateControlPanel('ready'); // Reset UI since task failed to start
374
504
  return;
375
505
  } else if (data.error && typeof data.error === 'string' && data.error.includes('llm_connection_failed')) {
376
- console.log('[UIManager] Detected LLM connection failure from string error');
506
+
377
507
  // Handle case where error is a string containing the error type
378
508
  this.showLLMConnectionFailedModal({
379
509
  message: data.error,
@@ -392,10 +522,10 @@ class VibeSurfUIManager {
392
522
  setTimeout(() => {
393
523
  this.checkTaskStatus().then(status => {
394
524
  if (!status.isRunning) {
395
- console.log('[UIManager] Task confirmed stopped after error, hiding controls');
525
+
396
526
  this.updateControlPanel('ready');
397
527
  } else {
398
- console.log('[UIManager] Task still running after error, keeping controls visible');
528
+
399
529
  }
400
530
  }).catch(err => {
401
531
  console.warn('[UIManager] Could not verify task status after error:', err);
@@ -470,6 +600,23 @@ class VibeSurfUIManager {
470
600
  this.elements.agentModeSelect.disabled = isRunning && !isPaused;
471
601
  }
472
602
 
603
+ // Update voice record button state - disable during task execution unless paused
604
+ if (this.elements.voiceRecordBtn) {
605
+ const shouldDisableVoice = isRunning && !isPaused;
606
+ this.elements.voiceRecordBtn.disabled = shouldDisableVoice;
607
+ if (shouldDisableVoice) {
608
+ this.elements.voiceRecordBtn.classList.add('task-running-disabled');
609
+ this.elements.voiceRecordBtn.setAttribute('title', 'Voice input disabled while task is running');
610
+ } else {
611
+ this.elements.voiceRecordBtn.classList.remove('task-running-disabled');
612
+ if (this.elements.voiceRecordBtn.classList.contains('recording')) {
613
+ this.elements.voiceRecordBtn.setAttribute('title', 'Recording... Click to stop');
614
+ } else {
615
+ this.elements.voiceRecordBtn.setAttribute('title', 'Click to start voice recording');
616
+ }
617
+ }
618
+ }
619
+
473
620
  // Update file manager state - keep disabled during pause (as per requirement)
474
621
  this.fileManager.setEnabled(!isRunning);
475
622
 
@@ -496,7 +643,6 @@ class VibeSurfUIManager {
496
643
  }
497
644
 
498
645
  forceUpdateUIForPausedState() {
499
- console.log('[UIManager] Force updating UI for paused state');
500
646
 
501
647
  // Enable input during pause
502
648
  if (this.elements.taskInput) {
@@ -509,6 +655,13 @@ class VibeSurfUIManager {
509
655
  this.elements.sendBtn.disabled = !hasText;
510
656
  }
511
657
 
658
+ // Enable voice record button during pause
659
+ if (this.elements.voiceRecordBtn) {
660
+ this.elements.voiceRecordBtn.disabled = false;
661
+ this.elements.voiceRecordBtn.classList.remove('task-running-disabled');
662
+ this.elements.voiceRecordBtn.setAttribute('title', 'Click to start voice recording');
663
+ }
664
+
512
665
  // Keep LLM profile disabled during pause (user doesn't need to change it)
513
666
  if (this.elements.llmProfileSelect) {
514
667
  this.elements.llmProfileSelect.disabled = true;
@@ -522,7 +675,7 @@ class VibeSurfUIManager {
522
675
  // Keep file manager disabled during pause
523
676
  this.fileManager.setEnabled(false);
524
677
 
525
- // Keep header buttons disabled during pause (only input and send should be available)
678
+ // Keep header buttons disabled during pause (only input, send, and voice should be available)
526
679
  const headerButtons = [
527
680
  this.elements.newSessionBtn,
528
681
  this.elements.historyBtn,
@@ -533,13 +686,13 @@ class VibeSurfUIManager {
533
686
  if (button) {
534
687
  button.disabled = true;
535
688
  button.classList.add('task-running-disabled');
536
- button.setAttribute('title', 'Disabled during pause - only input and send are available');
689
+ button.setAttribute('title', 'Disabled during pause - only input, send, and voice input are available');
537
690
  }
538
691
  });
539
692
  }
540
693
 
541
694
  forceUpdateUIForRunningState() {
542
- console.log('[UIManager] Force updating UI for running state');
695
+
543
696
 
544
697
  // Disable input during running
545
698
  if (this.elements.taskInput) {
@@ -559,6 +712,13 @@ class VibeSurfUIManager {
559
712
  this.elements.agentModeSelect.disabled = true;
560
713
  }
561
714
 
715
+ // Disable voice record button during running
716
+ if (this.elements.voiceRecordBtn) {
717
+ this.elements.voiceRecordBtn.disabled = true;
718
+ this.elements.voiceRecordBtn.classList.add('task-running-disabled');
719
+ this.elements.voiceRecordBtn.setAttribute('title', 'Voice input disabled while task is running');
720
+ }
721
+
562
722
  // Update file manager state
563
723
  this.fileManager.setEnabled(false);
564
724
 
@@ -658,6 +818,26 @@ class VibeSurfUIManager {
658
818
  this.settingsManager.showSettings();
659
819
  }
660
820
 
821
+ async handleShowLLMSettings() {
822
+ // Enhanced task running check
823
+ const statusCheck = await this.checkTaskStatus();
824
+ if (statusCheck.isRunning) {
825
+ const canProceed = await this.showTaskRunningWarning('access settings');
826
+ if (!canProceed) return;
827
+ }
828
+
829
+ // Show settings and navigate directly to LLM profiles tab
830
+ this.settingsManager.showSettings();
831
+
832
+ // Switch to LLM profiles tab after settings are shown
833
+ setTimeout(() => {
834
+ const llmTab = document.querySelector('.settings-tab[data-tab="llm-profiles"]');
835
+ if (llmTab) {
836
+ llmTab.click();
837
+ }
838
+ }, 100);
839
+ }
840
+
661
841
  async handleCopySession() {
662
842
  const sessionId = this.sessionManager.getCurrentSessionId();
663
843
 
@@ -675,6 +855,111 @@ class VibeSurfUIManager {
675
855
  }
676
856
  }
677
857
 
858
+ // Voice Recording UI Handler
859
+ async handleVoiceRecord() {
860
+ // Check if voice recording is supported
861
+ if (!this.voiceRecorder.isSupported()) {
862
+ this.showNotification('Voice recording is not supported in your browser', 'error');
863
+ return;
864
+ }
865
+
866
+ // Check if ASR profiles are available before allowing recording
867
+ const isVoiceAvailable = await this.voiceRecorder.isVoiceRecordingAvailable();
868
+ if (!isVoiceAvailable) {
869
+ console.log('[UIManager] No ASR profiles available, showing configuration modal');
870
+ this.showVoiceProfileRequiredModal('configure');
871
+ return;
872
+ }
873
+
874
+ // Enhanced task status check - disable recording during task execution unless paused
875
+ const taskStatus = this.sessionManager.getTaskStatus();
876
+ const isTaskRunning = this.state.isTaskRunning;
877
+
878
+ if (isTaskRunning && taskStatus !== 'paused') {
879
+ this.showNotification('Cannot record voice while task is running. Stop the current task or wait for it to complete.', 'warning');
880
+ return;
881
+ }
882
+
883
+ // Additional check: if task is paused, allow voice input but show info message
884
+ if (isTaskRunning && taskStatus === 'paused') {
885
+ console.log('[UIManager] Task is paused, allowing voice input');
886
+ }
887
+
888
+ // Check if voice button is disabled due to missing ASR profiles
889
+ if (this.elements.voiceRecordBtn && this.elements.voiceRecordBtn.classList.contains('voice-disabled')) {
890
+ console.log('[UIManager] Voice button is disabled due to missing ASR profiles, showing modal');
891
+ this.showVoiceProfileRequiredModal('configure');
892
+ return;
893
+ }
894
+
895
+ try {
896
+ if (this.voiceRecorder.isCurrentlyRecording()) {
897
+ // Stop recording
898
+ console.log('[UIManager] Stopping voice recording');
899
+ await this.voiceRecorder.stopRecording();
900
+ } else {
901
+ // Start recording
902
+ console.log('[UIManager] Starting voice recording');
903
+
904
+ // Request microphone permission first
905
+ try {
906
+ // For Chrome extensions, ensure we have proper user gesture context
907
+ console.log('[UIManager] Requesting microphone permission with user gesture context');
908
+
909
+ // Add a small delay to ensure user gesture is properly registered
910
+ await new Promise(resolve => setTimeout(resolve, 100));
911
+
912
+ const hasPermission = await this.voiceRecorder.requestMicrophonePermission();
913
+ if (!hasPermission) {
914
+ this.showNotification('Microphone permission denied. Please allow microphone access to use voice input.', 'error');
915
+ return;
916
+ }
917
+ } catch (permissionError) {
918
+ console.error('[UIManager] Microphone permission error:', permissionError);
919
+
920
+ // Handle different types of permission errors with detailed guidance
921
+ let errorMessage = 'Microphone permission denied';
922
+ let detailedHelp = '';
923
+
924
+ if (permissionError.name === 'MicrophonePermissionError') {
925
+ errorMessage = permissionError.message;
926
+ if (permissionError.userAction) {
927
+ detailedHelp = permissionError.userAction;
928
+ }
929
+ } else if (permissionError.name === 'NotAllowedError') {
930
+ errorMessage = 'Microphone access was denied by your browser.';
931
+ detailedHelp = 'Please check your browser permissions and try again. You may need to allow microphone access in your browser settings.';
932
+ } else {
933
+ errorMessage = `Microphone access error: ${permissionError.message}`;
934
+ detailedHelp = 'Please ensure your browser allows microphone access and that a microphone is connected.';
935
+ }
936
+
937
+ // Show detailed error message
938
+ const fullMessage = detailedHelp ? `${errorMessage}\n\n${detailedHelp}` : errorMessage;
939
+ this.showNotification(fullMessage, 'error');
940
+
941
+ // Also log detailed error for debugging
942
+ console.error('[UIManager] Detailed microphone permission error:', {
943
+ name: permissionError.name,
944
+ message: permissionError.message,
945
+ userAction: permissionError.userAction,
946
+ originalError: permissionError.originalError
947
+ });
948
+
949
+ this.updateVoiceRecordingUI(false);
950
+ return;
951
+ }
952
+
953
+ // Start recording
954
+ await this.voiceRecorder.startRecording();
955
+ }
956
+ } catch (error) {
957
+ console.error('[UIManager] Voice recording error:', error);
958
+ this.showNotification(`Voice recording failed: ${error.message}`, 'error');
959
+ this.updateVoiceRecordingUI(false);
960
+ }
961
+ }
962
+
678
963
  async handleSendTask() {
679
964
  const taskDescription = this.elements.taskInput?.value.trim();
680
965
  const taskStatus = this.sessionManager.getTaskStatus();
@@ -689,7 +974,7 @@ class VibeSurfUIManager {
689
974
  try {
690
975
  if (isPaused) {
691
976
  // Handle adding new task to paused execution
692
- console.log('[UIManager] Adding new task to paused execution:', taskDescription);
977
+
693
978
  await this.sessionManager.addNewTaskToPaused(taskDescription);
694
979
 
695
980
  // Clear the input after successful addition
@@ -697,7 +982,7 @@ class VibeSurfUIManager {
697
982
  this.showNotification('Additional information added to the task', 'success');
698
983
 
699
984
  // Automatically resume the task after adding new information
700
- console.log('[UIManager] Auto-resuming task after adding new information');
985
+
701
986
  await this.sessionManager.resumeTask('Auto-resume after adding new task information');
702
987
 
703
988
  return;
@@ -742,17 +1027,17 @@ class VibeSurfUIManager {
742
1027
  const filePath = this.fileManager.getUploadedFilesForTask();
743
1028
  if (filePath) {
744
1029
  taskData.upload_files_path = filePath;
745
- console.log('[UIManager] Set upload_files_path to:', filePath);
1030
+
746
1031
  }
747
1032
 
748
1033
  // Add selected tabs information if any
749
1034
  const selectedTabsData = this.getSelectedTabsForTask();
750
1035
  if (selectedTabsData) {
751
1036
  taskData.selected_tabs = selectedTabsData;
752
- console.log('[UIManager] Set selected_tabs to:', selectedTabsData);
1037
+
753
1038
  }
754
1039
 
755
- console.log('[UIManager] Complete task data being submitted:', JSON.stringify(taskData, null, 2));
1040
+
756
1041
  await this.sessionManager.submitTask(taskData);
757
1042
 
758
1043
  // Clear uploaded files after successful task submission
@@ -879,14 +1164,64 @@ class VibeSurfUIManager {
879
1164
  return false; // Allow default behavior
880
1165
  }
881
1166
 
882
- handleLlmProfileChange(event) {
1167
+ async handleLlmProfileChange(event) {
1168
+ const selectedProfile = event.target.value;
1169
+
1170
+
1171
+
1172
+ try {
1173
+ // Save the selected LLM profile to user settings storage
1174
+ if (this.userSettingsStorage) {
1175
+
1176
+ await this.userSettingsStorage.setSelectedLlmProfile(selectedProfile);
1177
+
1178
+
1179
+ // Verify the save
1180
+ const verified = await this.userSettingsStorage.getSelectedLlmProfile();
1181
+
1182
+
1183
+ // Also save to localStorage as backup for browser restart scenarios
1184
+ localStorage.setItem('vibesurf-llm-profile-backup', selectedProfile);
1185
+
1186
+ } else {
1187
+ console.warn('[UIManager] userSettingsStorage not available for LLM profile save');
1188
+ }
1189
+ } catch (error) {
1190
+ console.error('[UIManager] Failed to save LLM profile selection:', error);
1191
+ }
1192
+
883
1193
  // Re-validate send button state when LLM profile changes
884
1194
  if (this.elements.taskInput) {
885
1195
  this.handleTaskInputChange({ target: this.elements.taskInput });
886
1196
  }
887
1197
  }
888
1198
 
889
- handleAgentModeChange(event) {
1199
+ async handleAgentModeChange(event) {
1200
+ const selectedMode = event.target.value;
1201
+
1202
+
1203
+
1204
+ try {
1205
+ // Save the selected agent mode to user settings storage
1206
+ if (this.userSettingsStorage) {
1207
+
1208
+ await this.userSettingsStorage.setSelectedAgentMode(selectedMode);
1209
+
1210
+
1211
+ // Verify the save
1212
+ const verified = await this.userSettingsStorage.getSelectedAgentMode();
1213
+
1214
+
1215
+ // Also save to localStorage as backup for browser restart scenarios
1216
+ localStorage.setItem('vibesurf-agent-mode-backup', selectedMode);
1217
+
1218
+ } else {
1219
+ console.warn('[UIManager] userSettingsStorage not available for agent mode save');
1220
+ }
1221
+ } catch (error) {
1222
+ console.error('[UIManager] Failed to save agent mode selection:', error);
1223
+ }
1224
+
890
1225
  // Re-validate send button state when agent mode changes
891
1226
  if (this.elements.taskInput) {
892
1227
  this.handleTaskInputChange({ target: this.elements.taskInput });
@@ -894,7 +1229,7 @@ class VibeSurfUIManager {
894
1229
  }
895
1230
 
896
1231
  handleTaskInputChange(event) {
897
- console.log('[UIManager] handleTaskInputChange called with value:', event.target.value);
1232
+
898
1233
 
899
1234
  const hasText = event.target.value.trim().length > 0;
900
1235
  const textarea = event.target;
@@ -938,10 +1273,10 @@ class VibeSurfUIManager {
938
1273
 
939
1274
  // UI Display Methods
940
1275
  updateSessionDisplay(sessionId) {
941
- console.log('[UIManager] Updating session display with ID:', sessionId);
1276
+
942
1277
  if (this.elements.sessionId) {
943
1278
  this.elements.sessionId.textContent = sessionId || '-';
944
- console.log('[UIManager] Session ID display updated to:', sessionId);
1279
+
945
1280
  } else {
946
1281
  console.error('[UIManager] Session ID element not found');
947
1282
  }
@@ -968,13 +1303,13 @@ class VibeSurfUIManager {
968
1303
 
969
1304
  switch (status) {
970
1305
  case 'ready':
971
- console.log('[UIManager] Setting control panel to ready (hidden)');
1306
+
972
1307
  panel.classList.add('hidden');
973
1308
  panel.classList.remove('error-state');
974
1309
  break;
975
1310
 
976
1311
  case 'running':
977
- console.log('[UIManager] Setting control panel to running (showing cancel button)');
1312
+
978
1313
  panel.classList.remove('hidden');
979
1314
  panel.classList.remove('error-state');
980
1315
  cancelBtn?.classList.remove('hidden');
@@ -986,7 +1321,7 @@ class VibeSurfUIManager {
986
1321
  break;
987
1322
 
988
1323
  case 'paused':
989
- console.log('[UIManager] Setting control panel to paused (showing resume/terminate buttons)');
1324
+
990
1325
  panel.classList.remove('hidden');
991
1326
  panel.classList.remove('error-state');
992
1327
  cancelBtn?.classList.add('hidden');
@@ -995,7 +1330,7 @@ class VibeSurfUIManager {
995
1330
  break;
996
1331
 
997
1332
  case 'error':
998
- console.log('[UIManager] Setting control panel to error (keeping cancel/terminate buttons visible)');
1333
+
999
1334
  panel.classList.remove('hidden');
1000
1335
  panel.classList.add('error-state');
1001
1336
  cancelBtn?.classList.remove('hidden');
@@ -1017,7 +1352,7 @@ class VibeSurfUIManager {
1017
1352
  // Clear the flag after minimum visibility period (2 seconds)
1018
1353
  setTimeout(() => {
1019
1354
  this.controlPanelMinVisibilityActive = false;
1020
- console.log('[UIManager] Minimum control panel visibility period ended');
1355
+
1021
1356
  }, 2000);
1022
1357
  }
1023
1358
 
@@ -1218,7 +1553,7 @@ class VibeSurfUIManager {
1218
1553
  }
1219
1554
 
1220
1555
  handleSuggestionTaskClick(taskDescription) {
1221
- console.log('[UIManager] Suggestion task clicked:', taskDescription);
1556
+
1222
1557
 
1223
1558
  // First check if task is running
1224
1559
  if (this.state.isTaskRunning) {
@@ -1240,7 +1575,7 @@ class VibeSurfUIManager {
1240
1575
  return;
1241
1576
  }
1242
1577
 
1243
- console.log('[UIManager] Setting task description and submitting...');
1578
+
1244
1579
  this.elements.taskInput.value = taskDescription;
1245
1580
  this.elements.taskInput.focus();
1246
1581
 
@@ -1249,7 +1584,7 @@ class VibeSurfUIManager {
1249
1584
 
1250
1585
  // Auto-submit the task after a short delay
1251
1586
  setTimeout(() => {
1252
- console.log('[UIManager] Auto-submitting suggestion task...');
1587
+
1253
1588
  this.handleSendTask();
1254
1589
  }, 100);
1255
1590
  }
@@ -1370,9 +1705,9 @@ class VibeSurfUIManager {
1370
1705
  }
1371
1706
 
1372
1707
  // Check clipboard API availability
1373
- console.log('[UIManager] navigator.clipboard available:', !!navigator.clipboard);
1374
- console.log('[UIManager] writeText method available:', !!(navigator.clipboard && navigator.clipboard.writeText));
1375
- console.log('[UIManager] Document has focus:', document.hasFocus());
1708
+
1709
+
1710
+
1376
1711
 
1377
1712
  // Try multiple clipboard methods
1378
1713
  let copySuccess = false;
@@ -1380,7 +1715,7 @@ class VibeSurfUIManager {
1380
1715
 
1381
1716
  // Method 1: Chrome extension messaging approach
1382
1717
  try {
1383
- console.log('[UIManager] Trying Chrome extension messaging approach...');
1718
+
1384
1719
  await new Promise((resolve, reject) => {
1385
1720
  chrome.runtime.sendMessage({
1386
1721
  type: 'COPY_TO_CLIPBOARD',
@@ -1396,7 +1731,7 @@ class VibeSurfUIManager {
1396
1731
  });
1397
1732
  });
1398
1733
  copySuccess = true;
1399
- console.log('[UIManager] Copied using Chrome extension messaging');
1734
+
1400
1735
  } catch (extensionError) {
1401
1736
  console.warn('[UIManager] Chrome extension messaging failed:', extensionError);
1402
1737
  lastError = extensionError;
@@ -1405,10 +1740,10 @@ class VibeSurfUIManager {
1405
1740
  // Method 2: Modern clipboard API (if extension method failed)
1406
1741
  if (!copySuccess && navigator.clipboard && navigator.clipboard.writeText) {
1407
1742
  try {
1408
- console.log('[UIManager] Trying modern clipboard API...');
1743
+
1409
1744
  await navigator.clipboard.writeText(messageText);
1410
1745
  copySuccess = true;
1411
- console.log('[UIManager] Copied using modern clipboard API');
1746
+
1412
1747
  } catch (clipboardError) {
1413
1748
  console.warn('[UIManager] Modern clipboard API failed:', clipboardError);
1414
1749
  lastError = clipboardError;
@@ -1418,7 +1753,7 @@ class VibeSurfUIManager {
1418
1753
  // Method 3: Fallback using execCommand
1419
1754
  if (!copySuccess) {
1420
1755
  try {
1421
- console.log('[UIManager] Trying execCommand fallback...');
1756
+
1422
1757
  const textArea = document.createElement('textarea');
1423
1758
  textArea.value = messageText;
1424
1759
  textArea.style.position = 'fixed';
@@ -1435,7 +1770,7 @@ class VibeSurfUIManager {
1435
1770
 
1436
1771
  if (success) {
1437
1772
  copySuccess = true;
1438
- console.log('[UIManager] Copied using execCommand fallback');
1773
+
1439
1774
  } else {
1440
1775
  console.warn('[UIManager] execCommand returned false');
1441
1776
  }
@@ -1448,7 +1783,7 @@ class VibeSurfUIManager {
1448
1783
  if (copySuccess) {
1449
1784
  // Show visual feedback
1450
1785
  this.showCopyFeedback();
1451
- console.log('[UIManager] Copy operation completed successfully');
1786
+
1452
1787
  } else {
1453
1788
  throw new Error(`All clipboard methods failed. Last error: ${lastError?.message || 'Unknown error'}`);
1454
1789
  }
@@ -1668,9 +2003,12 @@ class VibeSurfUIManager {
1668
2003
  }
1669
2004
  }
1670
2005
 
1671
- updateLLMProfileSelect() {
2006
+ async updateLLMProfileSelect() {
1672
2007
  if (!this.elements.llmProfileSelect) return;
1673
2008
 
2009
+ // Preserve current user selection if any (to avoid overriding during profile updates)
2010
+ const currentSelection = this.elements.llmProfileSelect.value;
2011
+
1674
2012
  const profiles = this.settingsManager.getLLMProfiles();
1675
2013
  const select = this.elements.llmProfileSelect;
1676
2014
  select.innerHTML = '';
@@ -1691,16 +2029,50 @@ class VibeSurfUIManager {
1691
2029
  emptyOption.disabled = true;
1692
2030
  select.appendChild(emptyOption);
1693
2031
 
2032
+ // Determine selection priority: current selection > saved selection > default profile
2033
+ let targetSelection = currentSelection; // Preserve current selection first
2034
+
2035
+ // If no current selection, get saved selection
2036
+ if (!targetSelection) {
2037
+ try {
2038
+ if (this.userSettingsStorage) {
2039
+ targetSelection = await this.userSettingsStorage.getSelectedLlmProfile();
2040
+ }
2041
+ } catch (error) {
2042
+ console.error('[UIManager] Failed to get saved LLM profile selection:', error);
2043
+ }
2044
+ }
2045
+
1694
2046
  // Add actual profiles
2047
+ let hasSelectedProfile = false;
2048
+ let hasTargetProfile = false;
2049
+
1695
2050
  profiles.forEach(profile => {
1696
2051
  const option = document.createElement('option');
1697
2052
  option.value = profile.profile_name;
1698
2053
  option.textContent = profile.profile_name;
1699
- if (profile.is_default) {
1700
- option.selected = true;
2054
+
2055
+ // Check if this profile matches our target selection
2056
+ if (targetSelection && profile.profile_name === targetSelection) {
2057
+ hasTargetProfile = true;
1701
2058
  }
2059
+
1702
2060
  select.appendChild(option);
1703
2061
  });
2062
+
2063
+ // Apply selection based on priority
2064
+ if (hasTargetProfile) {
2065
+ select.value = targetSelection;
2066
+ hasSelectedProfile = true;
2067
+ } else {
2068
+ // Fall back to default profile if target not available
2069
+ const defaultProfile = profiles.find(p => p.is_default);
2070
+ if (defaultProfile) {
2071
+ select.value = defaultProfile.profile_name;
2072
+ hasSelectedProfile = true;
2073
+ }
2074
+ }
2075
+
1704
2076
  }
1705
2077
 
1706
2078
  // Update send button state if taskInput exists
@@ -1721,7 +2093,7 @@ class VibeSurfUIManager {
1721
2093
  confirmText: 'Open Settings',
1722
2094
  cancelText: 'Cancel',
1723
2095
  onConfirm: () => {
1724
- this.handleShowSettings();
2096
+ this.handleShowLLMSettings();
1725
2097
  }
1726
2098
  }
1727
2099
  : {
@@ -1731,15 +2103,61 @@ class VibeSurfUIManager {
1731
2103
  this.elements.llmProfileSelect?.focus();
1732
2104
  },
1733
2105
  onCancel: () => {
1734
- this.handleShowSettings();
2106
+ this.handleShowLLMSettings();
2107
+ }
2108
+ };
2109
+
2110
+ this.modalManager.showWarningModal(title, message, options);
2111
+ }
2112
+
2113
+ showVoiceProfileRequiredModal(action) {
2114
+ const isConfigureAction = action === 'configure';
2115
+ const title = isConfigureAction ? 'Voice Profile Required' : 'Please Select Voice Profile';
2116
+ const message = isConfigureAction
2117
+ ? 'No voice recognition (ASR) profiles are configured. You need to configure at least one voice profile before using voice input.'
2118
+ : 'Please configure a voice recognition profile to use voice input functionality.';
2119
+
2120
+ const options = isConfigureAction
2121
+ ? {
2122
+ confirmText: 'Open Voice Settings',
2123
+ cancelText: 'Cancel',
2124
+ onConfirm: () => {
2125
+ this.handleShowVoiceSettings();
2126
+ }
2127
+ }
2128
+ : {
2129
+ confirmText: 'Open Voice Settings',
2130
+ cancelText: 'Cancel',
2131
+ onConfirm: () => {
2132
+ this.handleShowVoiceSettings();
1735
2133
  }
1736
2134
  };
1737
2135
 
1738
2136
  this.modalManager.showWarningModal(title, message, options);
1739
2137
  }
1740
2138
 
2139
+ async handleShowVoiceSettings() {
2140
+ // Enhanced task running check
2141
+ const statusCheck = await this.checkTaskStatus();
2142
+ if (statusCheck.isRunning) {
2143
+ const canProceed = await this.showTaskRunningWarning('access voice settings');
2144
+ if (!canProceed) return;
2145
+ }
2146
+
2147
+ // Show settings and navigate directly to Voice profiles tab
2148
+ this.settingsManager.showSettings();
2149
+
2150
+ // Switch to Voice profiles tab after settings are shown
2151
+ setTimeout(() => {
2152
+ const voiceTab = document.querySelector('.settings-tab[data-tab="voice-profiles"]');
2153
+ if (voiceTab) {
2154
+ voiceTab.click();
2155
+ }
2156
+ }, 100);
2157
+ }
2158
+
1741
2159
  showLLMConnectionFailedModal(errorData) {
1742
- console.log('[UIManager] showLLMConnectionFailedModal called with:', errorData);
2160
+
1743
2161
 
1744
2162
  const llmProfile = errorData.llm_profile || 'unknown';
1745
2163
  const errorMessage = errorData.message || 'Cannot connect to LLM API';
@@ -1756,7 +2174,7 @@ class VibeSurfUIManager {
1756
2174
  }
1757
2175
  };
1758
2176
 
1759
- console.log('[UIManager] Showing LLM connection failed modal');
2177
+
1760
2178
  this.modalManager.showWarningModal(title, message, options);
1761
2179
  }
1762
2180
 
@@ -1825,20 +2243,132 @@ class VibeSurfUIManager {
1825
2243
  });
1826
2244
  }
1827
2245
 
2246
+ // Restore LLM profile selection from user settings storage
2247
+ async restoreLlmProfileSelection() {
2248
+ try {
2249
+ if (this.userSettingsStorage && this.elements.llmProfileSelect) {
2250
+ // Check current options available
2251
+ const availableOptions = Array.from(this.elements.llmProfileSelect.options).map(opt => opt.value);
2252
+
2253
+ const savedLlmProfile = await this.userSettingsStorage.getSelectedLlmProfile();
2254
+
2255
+ if (savedLlmProfile && savedLlmProfile.trim() !== '') {
2256
+ // Check if the saved profile exists in the current options
2257
+ const option = this.elements.llmProfileSelect.querySelector(`option[value="${savedLlmProfile}"]`);
2258
+
2259
+ if (option) {
2260
+ this.elements.llmProfileSelect.value = savedLlmProfile;
2261
+ } else {
2262
+ console.warn('[UIManager] Saved LLM profile not found in current options:', savedLlmProfile);
2263
+ }
2264
+ } else {
2265
+ // Check localStorage backup for browser restart scenarios
2266
+ const backupProfile = localStorage.getItem('vibesurf-llm-profile-backup');
2267
+
2268
+ if (backupProfile) {
2269
+ const option = this.elements.llmProfileSelect.querySelector(`option[value="${backupProfile}"]`);
2270
+ if (option) {
2271
+ this.elements.llmProfileSelect.value = backupProfile;
2272
+ // Also save it back to Chrome storage
2273
+ await this.userSettingsStorage.setSelectedLlmProfile(backupProfile);
2274
+ } else {
2275
+ console.warn('[UIManager] Backup profile not found in current options:', backupProfile);
2276
+ }
2277
+ }
2278
+ }
2279
+ } else {
2280
+ console.warn('[UIManager] Required components not available - userSettingsStorage:', !!this.userSettingsStorage, 'llmProfileSelect:', !!this.elements.llmProfileSelect);
2281
+ }
2282
+ } catch (error) {
2283
+ console.error('[UIManager] Failed to restore LLM profile selection:', error);
2284
+ }
2285
+ }
2286
+
2287
+ // Check and update voice button state based on ASR profile availability
2288
+ async updateVoiceButtonState() {
2289
+ if (!this.elements.voiceRecordBtn) return;
2290
+
2291
+ try {
2292
+ const isVoiceAvailable = await this.voiceRecorder.isVoiceRecordingAvailable();
2293
+
2294
+ if (!isVoiceAvailable) {
2295
+ // Add visual indication but keep button enabled for click handling
2296
+ this.elements.voiceRecordBtn.classList.add('voice-disabled');
2297
+ this.elements.voiceRecordBtn.setAttribute('title', 'Voice input disabled - No ASR profiles configured. Click to configure.');
2298
+ this.elements.voiceRecordBtn.setAttribute('data-tooltip', 'Voice input disabled - No ASR profiles configured. Click to configure.');
2299
+ } else {
2300
+ // Remove visual indication and restore normal tooltip
2301
+ this.elements.voiceRecordBtn.classList.remove('voice-disabled');
2302
+ if (!this.elements.voiceRecordBtn.classList.contains('recording')) {
2303
+ this.elements.voiceRecordBtn.setAttribute('title', 'Click to start voice recording');
2304
+ this.elements.voiceRecordBtn.setAttribute('data-tooltip', 'Click to start voice recording');
2305
+ }
2306
+ }
2307
+ } catch (error) {
2308
+ console.error('[UIManager] Error updating voice button state:', error);
2309
+ // Fallback: add visual indication but keep button enabled
2310
+ this.elements.voiceRecordBtn.classList.add('voice-disabled');
2311
+ this.elements.voiceRecordBtn.setAttribute('title', 'Voice input temporarily unavailable. Click for more info.');
2312
+ this.elements.voiceRecordBtn.setAttribute('data-tooltip', 'Voice input temporarily unavailable. Click for more info.');
2313
+ }
2314
+ }
2315
+
1828
2316
  // Initialization
1829
2317
  async initialize() {
1830
2318
  try {
2319
+ // Check for microphone permission parameter first (like Doubao AI)
2320
+ const urlParams = new URLSearchParams(window.location.search);
2321
+ const enterParam = urlParams.get('enter');
2322
+
2323
+ if (enterParam === 'mic-permission') {
2324
+ console.log('[UIManager] Detected microphone permission request parameter');
2325
+ this.showMicrophonePermissionRequest();
2326
+ return; // Skip normal initialization for permission request
2327
+ }
2328
+
1831
2329
  this.showLoading('Initializing VibeSurf...');
1832
2330
 
2331
+ // Initialize user settings storage first
2332
+ if (this.userSettingsStorage) {
2333
+
2334
+ await this.userSettingsStorage.initialize();
2335
+
2336
+ } else {
2337
+ console.error('[UIManager] userSettingsStorage not available during initialization');
2338
+ }
2339
+
1833
2340
  // Load settings data through settings manager
2341
+
1834
2342
  await this.settingsManager.loadSettingsData();
1835
2343
 
2344
+
2345
+ // Now restore user selections AFTER profiles are loaded but before any other initialization
2346
+
2347
+ this.isRestoringSelections = true;
2348
+
2349
+ try {
2350
+ // Restore LLM profile selection first
2351
+ await this.restoreLlmProfileSelection();
2352
+
2353
+ // Restore agent mode selection
2354
+ await this.restoreAgentModeSelection();
2355
+ } finally {
2356
+ this.isRestoringSelections = false;
2357
+ }
2358
+
2359
+ // Check and update voice button state based on ASR profile availability
2360
+ await this.updateVoiceButtonState();
2361
+
2362
+
1836
2363
  // Create initial session if none exists
2364
+
1837
2365
  if (!this.sessionManager.getCurrentSession()) {
2366
+
1838
2367
  await this.sessionManager.createSession();
1839
2368
  }
1840
2369
 
1841
2370
  this.hideLoading();
2371
+
1842
2372
  } catch (error) {
1843
2373
  this.hideLoading();
1844
2374
  console.error('[UIManager] Initialization failed:', error);
@@ -1846,11 +2376,130 @@ class VibeSurfUIManager {
1846
2376
  }
1847
2377
  }
1848
2378
 
2379
+ // Show microphone permission request (like Doubao AI) - simplified approach
2380
+ showMicrophonePermissionRequest() {
2381
+ console.log('[UIManager] Showing simplified microphone permission request');
2382
+ console.log('[UIManager] Current URL:', window.location.href);
2383
+ console.log('[UIManager] URL params:', window.location.search);
2384
+
2385
+ // Simple approach: just try to get permission directly
2386
+ this.requestMicrophonePermissionDirectly();
2387
+ }
2388
+
2389
+ // Direct microphone permission request (simplified like Doubao AI)
2390
+ async requestMicrophonePermissionDirectly() {
2391
+ console.log('[UIManager] Requesting microphone permission directly');
2392
+
2393
+ try {
2394
+ // Immediately try to get microphone permission
2395
+ console.log('[UIManager] Calling getUserMedia...');
2396
+ console.log('[UIManager] User agent:', navigator.userAgent);
2397
+ console.log('[UIManager] Location protocol:', window.location.protocol);
2398
+ console.log('[UIManager] Location href:', window.location.href);
2399
+
2400
+ const stream = await navigator.mediaDevices.getUserMedia({
2401
+ audio: true,
2402
+ video: false
2403
+ });
2404
+
2405
+ console.log('[UIManager] Microphone permission granted!');
2406
+
2407
+ // Stop the stream immediately (we just need permission)
2408
+ stream.getTracks().forEach(track => track.stop());
2409
+
2410
+ // Send success message back to original tab
2411
+ console.log('[UIManager] Sending success message...');
2412
+ chrome.runtime.sendMessage({
2413
+ type: 'MICROPHONE_PERMISSION_RESULT',
2414
+ granted: true
2415
+ }, (response) => {
2416
+ console.log('[UIManager] Success message response:', response);
2417
+ });
2418
+
2419
+ // Close this tab after a short delay
2420
+ setTimeout(() => {
2421
+ console.log('[UIManager] Closing tab...');
2422
+ window.close();
2423
+ }, 1000);
2424
+
2425
+ } catch (error) {
2426
+ console.error('[UIManager] Microphone permission denied:', error);
2427
+ console.log('[UIManager] Error name:', error.name);
2428
+ console.log('[UIManager] Error message:', error.message);
2429
+ console.log('[UIManager] Error stack:', error.stack);
2430
+
2431
+ // Send error message back to original tab
2432
+ console.log('[UIManager] Sending error message...');
2433
+ chrome.runtime.sendMessage({
2434
+ type: 'MICROPHONE_PERMISSION_RESULT',
2435
+ granted: false,
2436
+ error: error.message,
2437
+ errorName: error.name
2438
+ }, (response) => {
2439
+ console.log('[UIManager] Error message response:', response);
2440
+ });
2441
+
2442
+ // Close this tab after a short delay
2443
+ setTimeout(() => {
2444
+ console.log('[UIManager] Closing tab after error...');
2445
+ window.close();
2446
+ }, 2000);
2447
+ }
2448
+ }
2449
+ // Restore agent mode selection from user settings storage
2450
+ async restoreAgentModeSelection() {
2451
+ try {
2452
+
2453
+ if (this.userSettingsStorage && this.elements.agentModeSelect) {
2454
+ // Check current options available
2455
+ const availableOptions = Array.from(this.elements.agentModeSelect.options).map(opt => opt.value);
2456
+
2457
+ const savedAgentMode = await this.userSettingsStorage.getSelectedAgentMode();
2458
+
2459
+ if (savedAgentMode && savedAgentMode.trim() !== '') {
2460
+ // Restore any saved agent mode, including 'thinking'
2461
+ this.elements.agentModeSelect.value = savedAgentMode;
2462
+ // Ensure the option is actually selected
2463
+ const option = this.elements.agentModeSelect.querySelector(`option[value="${savedAgentMode}"]`);
2464
+ if (option) {
2465
+ option.selected = true;
2466
+ console.log('[UIManager] Restored agent mode selection:', savedAgentMode);
2467
+ } else {
2468
+ console.warn('[UIManager] Agent mode option not found:', savedAgentMode);
2469
+ }
2470
+ } else {
2471
+ // Check localStorage backup for browser restart scenarios
2472
+ const backupMode = localStorage.getItem('vibesurf-agent-mode-backup');
2473
+
2474
+ if (backupMode) {
2475
+ const option = this.elements.agentModeSelect.querySelector(`option[value="${backupMode}"]`);
2476
+ if (option) {
2477
+ this.elements.agentModeSelect.value = backupMode;
2478
+ // Also save it back to Chrome storage
2479
+ await this.userSettingsStorage.setSelectedAgentMode(backupMode);
2480
+ } else {
2481
+ console.warn('[UIManager] Backup agent mode not found in current options:', backupMode);
2482
+ }
2483
+ }
2484
+ }
2485
+ } else {
2486
+ console.warn('[UIManager] Required components not available - userSettingsStorage:', !!this.userSettingsStorage, 'agentModeSelect:', !!this.elements.agentModeSelect);
2487
+ }
2488
+ } catch (error) {
2489
+ console.error('[UIManager] Failed to restore agent mode selection:', error);
2490
+ }
2491
+ }
2492
+
1849
2493
  // Cleanup
1850
2494
  destroy() {
1851
2495
  // Stop task status monitoring
1852
2496
  this.stopTaskStatusMonitoring();
1853
2497
 
2498
+ // Cleanup voice recorder
2499
+ if (this.voiceRecorder) {
2500
+ this.voiceRecorder.cleanup();
2501
+ }
2502
+
1854
2503
  // Cleanup managers
1855
2504
  if (this.settingsManager) {
1856
2505
  // Cleanup settings manager if it has destroy method
@@ -1886,7 +2535,7 @@ class VibeSurfUIManager {
1886
2535
 
1887
2536
  // Tab Selector Methods
1888
2537
  initializeTabSelector() {
1889
- console.log('[UIManager] Initializing tab selector...');
2538
+
1890
2539
 
1891
2540
  // Initialize tab selector state
1892
2541
  this.tabSelectorState = {
@@ -1896,20 +2545,15 @@ class VibeSurfUIManager {
1896
2545
  atPosition: -1 // Position where @ was typed
1897
2546
  };
1898
2547
 
1899
- console.log('[UIManager] Tab selector state initialized:', this.tabSelectorState);
2548
+
1900
2549
 
1901
2550
  // Bind tab selector events
1902
2551
  this.bindTabSelectorEvents();
1903
2552
  }
1904
2553
 
1905
2554
  bindTabSelectorEvents() {
1906
- console.log('[UIManager] Binding tab selector events...');
1907
- console.log('[UIManager] Available elements for binding:', {
1908
- tabSelectorCancel: !!this.elements.tabSelectorCancel,
1909
- tabSelectorConfirm: !!this.elements.tabSelectorConfirm,
1910
- selectAllTabs: !!this.elements.selectAllTabs,
1911
- tabSelectorDropdown: !!this.elements.tabSelectorDropdown
1912
- });
2555
+
2556
+
1913
2557
 
1914
2558
  // Select all radio button
1915
2559
  this.elements.selectAllTabs?.addEventListener('change', this.handleSelectAllTabs.bind(this));
@@ -1924,7 +2568,7 @@ class VibeSurfUIManager {
1924
2568
  }
1925
2569
  });
1926
2570
 
1927
- console.log('[UIManager] Tab selector events bound successfully');
2571
+
1928
2572
  }
1929
2573
 
1930
2574
  handleTabSelectorInput(event) {
@@ -1937,16 +2581,11 @@ class VibeSurfUIManager {
1937
2581
  const inputValue = event.target.value;
1938
2582
  const cursorPosition = event.target.selectionStart;
1939
2583
 
1940
- console.log('[UIManager] Tab selector input check:', {
1941
- inputValue,
1942
- cursorPosition,
1943
- charAtCursor: inputValue[cursorPosition - 1],
1944
- isAtSymbol: inputValue[cursorPosition - 1] === '@'
1945
- });
2584
+
1946
2585
 
1947
2586
  // Check if @ was just typed
1948
2587
  if (inputValue[cursorPosition - 1] === '@') {
1949
- console.log('[UIManager] @ detected, showing tab selector');
2588
+
1950
2589
  this.tabSelectorState.atPosition = cursorPosition - 1;
1951
2590
  this.showTabSelector();
1952
2591
  } else if (this.tabSelectorState.isVisible) {
@@ -1954,7 +2593,7 @@ class VibeSurfUIManager {
1954
2593
  if (this.tabSelectorState.atPosition >= 0 &&
1955
2594
  (this.tabSelectorState.atPosition >= inputValue.length ||
1956
2595
  inputValue[this.tabSelectorState.atPosition] !== '@')) {
1957
- console.log('[UIManager] @ deleted, hiding tab selector');
2596
+
1958
2597
  this.hideTabSelector();
1959
2598
  return;
1960
2599
  }
@@ -1962,19 +2601,15 @@ class VibeSurfUIManager {
1962
2601
  // Hide tab selector if user continues typing after @
1963
2602
  const textAfterAt = inputValue.substring(this.tabSelectorState.atPosition + 1, cursorPosition);
1964
2603
  if (textAfterAt.length > 0 && !textAfterAt.match(/^[\s]*$/)) {
1965
- console.log('[UIManager] Hiding tab selector due to continued typing');
2604
+
1966
2605
  this.hideTabSelector();
1967
2606
  }
1968
2607
  }
1969
2608
  }
1970
2609
 
1971
2610
  async showTabSelector() {
1972
- console.log('[UIManager] showTabSelector called');
1973
- console.log('[UIManager] Tab selector elements:', {
1974
- dropdown: !!this.elements.tabSelectorDropdown,
1975
- taskInput: !!this.elements.taskInput,
1976
- tabOptionsList: !!this.elements.tabOptionsList
1977
- });
2611
+
2612
+
1978
2613
 
1979
2614
  if (!this.elements.tabSelectorDropdown || !this.elements.taskInput) {
1980
2615
  console.error('[UIManager] Tab selector elements not found', {
@@ -1985,15 +2620,15 @@ class VibeSurfUIManager {
1985
2620
  }
1986
2621
 
1987
2622
  try {
1988
- console.log('[UIManager] Fetching tab data...');
2623
+
1989
2624
  // Fetch tab data from backend
1990
2625
  await this.populateTabSelector();
1991
2626
 
1992
- console.log('[UIManager] Positioning dropdown...');
2627
+
1993
2628
  // Position the dropdown relative to the input
1994
2629
  this.positionTabSelector();
1995
2630
 
1996
- console.log('[UIManager] Showing dropdown...');
2631
+
1997
2632
  // Show the dropdown with explicit visibility
1998
2633
  this.elements.tabSelectorDropdown.classList.remove('hidden');
1999
2634
  this.elements.tabSelectorDropdown.style.display = 'block';
@@ -2001,16 +2636,10 @@ class VibeSurfUIManager {
2001
2636
  this.elements.tabSelectorDropdown.style.opacity = '1';
2002
2637
  this.tabSelectorState.isVisible = true;
2003
2638
 
2004
- console.log('[UIManager] Tab selector shown successfully');
2005
- console.log('[UIManager] Classes:', this.elements.tabSelectorDropdown.className);
2006
- console.log('[UIManager] Computed styles:', {
2007
- display: getComputedStyle(this.elements.tabSelectorDropdown).display,
2008
- visibility: getComputedStyle(this.elements.tabSelectorDropdown).visibility,
2009
- opacity: getComputedStyle(this.elements.tabSelectorDropdown).opacity,
2010
- zIndex: getComputedStyle(this.elements.tabSelectorDropdown).zIndex,
2011
- position: getComputedStyle(this.elements.tabSelectorDropdown).position
2012
- });
2013
- console.log('[UIManager] Dropdown content HTML:', this.elements.tabSelectorDropdown.innerHTML);
2639
+
2640
+
2641
+
2642
+
2014
2643
  } catch (error) {
2015
2644
  console.error('[UIManager] Failed to show tab selector:', error);
2016
2645
  this.showNotification('Failed to load browser tabs', 'error');
@@ -2026,7 +2655,7 @@ class VibeSurfUIManager {
2026
2655
  this.tabSelectorState.selectedTabs = [];
2027
2656
  this.tabSelectorState.atPosition = -1;
2028
2657
 
2029
- console.log('[UIManager] Tab selector hidden');
2658
+
2030
2659
  }
2031
2660
 
2032
2661
  positionTabSelector() {
@@ -2035,10 +2664,7 @@ class VibeSurfUIManager {
2035
2664
  const inputRect = this.elements.taskInput.getBoundingClientRect();
2036
2665
  const dropdown = this.elements.tabSelectorDropdown;
2037
2666
 
2038
- console.log('[UIManager] Positioning dropdown:', {
2039
- inputRect,
2040
- dropdownElement: dropdown
2041
- });
2667
+
2042
2668
 
2043
2669
  // Calculate 90% width of input
2044
2670
  const dropdownWidth = inputRect.width * 0.9;
@@ -2052,47 +2678,32 @@ class VibeSurfUIManager {
2052
2678
  dropdown.style.maxHeight = '300px';
2053
2679
  dropdown.style.overflowY = 'auto';
2054
2680
 
2055
- console.log('[UIManager] Dropdown positioned with styles:', {
2056
- position: dropdown.style.position,
2057
- bottom: dropdown.style.bottom,
2058
- left: dropdown.style.left,
2059
- width: dropdown.style.width,
2060
- zIndex: dropdown.style.zIndex
2061
- });
2681
+
2062
2682
  }
2063
2683
 
2064
2684
  async populateTabSelector() {
2065
2685
  try {
2066
- console.log('[UIManager] Getting tab data from backend...');
2686
+
2067
2687
  // Get all tabs and active tab from backend
2068
2688
  const [allTabsResponse, activeTabResponse] = await Promise.all([
2069
2689
  this.apiClient.getAllBrowserTabs(),
2070
2690
  this.apiClient.getActiveBrowserTab()
2071
2691
  ]);
2072
2692
 
2073
- console.log('[UIManager] Raw API responses:', {
2074
- allTabsResponse: JSON.stringify(allTabsResponse, null, 2),
2075
- activeTabResponse: JSON.stringify(activeTabResponse, null, 2)
2076
- });
2693
+
2077
2694
 
2078
2695
  const allTabs = allTabsResponse.tabs || allTabsResponse || {};
2079
2696
  const activeTab = activeTabResponse.tab || activeTabResponse || {};
2080
2697
  const activeTabId = Object.keys(activeTab)[0];
2081
2698
 
2082
- console.log('[UIManager] Processed tab data:', {
2083
- allTabsCount: Object.keys(allTabs).length,
2084
- activeTabId,
2085
- allTabIds: Object.keys(allTabs),
2086
- allTabsData: allTabs,
2087
- activeTabData: activeTab
2088
- });
2699
+
2089
2700
 
2090
2701
  this.tabSelectorState.allTabs = allTabs;
2091
2702
 
2092
2703
  // Clear existing options
2093
2704
  if (this.elements.tabOptionsList) {
2094
2705
  this.elements.tabOptionsList.innerHTML = '';
2095
- console.log('[UIManager] Cleared existing tab options');
2706
+
2096
2707
  } else {
2097
2708
  console.error('[UIManager] tabOptionsList element not found!');
2098
2709
  return;
@@ -2109,7 +2720,7 @@ class VibeSurfUIManager {
2109
2720
 
2110
2721
  Object.entries(testTabs).forEach(([tabId, tabInfo]) => {
2111
2722
  const isActive = tabId === 'test-1';
2112
- console.log('[UIManager] Creating test tab option:', { tabId, title: tabInfo.title, isActive });
2723
+
2113
2724
  const option = this.createTabOption(tabId, tabInfo, isActive);
2114
2725
  this.elements.tabOptionsList.appendChild(option);
2115
2726
  });
@@ -2119,7 +2730,7 @@ class VibeSurfUIManager {
2119
2730
  // Add real tab options
2120
2731
  Object.entries(allTabs).forEach(([tabId, tabInfo]) => {
2121
2732
  const isActive = tabId === activeTabId;
2122
- console.log('[UIManager] Creating tab option:', { tabId, title: tabInfo.title, isActive });
2733
+
2123
2734
  const option = this.createTabOption(tabId, tabInfo, isActive);
2124
2735
  this.elements.tabOptionsList.appendChild(option);
2125
2736
  });
@@ -2130,7 +2741,7 @@ class VibeSurfUIManager {
2130
2741
  this.elements.selectAllTabs.checked = false;
2131
2742
  }
2132
2743
 
2133
- console.log('[UIManager] Tab selector populated with', Object.keys(this.tabSelectorState.allTabs).length, 'tabs');
2744
+
2134
2745
  } catch (error) {
2135
2746
  console.error('[UIManager] Failed to populate tab selector:', error);
2136
2747
  throw error;
@@ -2170,7 +2781,7 @@ class VibeSurfUIManager {
2170
2781
  // For radio buttons, replace the selected tabs array with just this tab
2171
2782
  this.tabSelectorState.selectedTabs = [tabId];
2172
2783
 
2173
- console.log('[UIManager] Selected tab:', tabId);
2784
+
2174
2785
 
2175
2786
  // Auto-confirm selection immediately
2176
2787
  this.confirmTabSelection();
@@ -2183,7 +2794,7 @@ class VibeSurfUIManager {
2183
2794
  const allTabIds = Object.keys(this.tabSelectorState.allTabs);
2184
2795
  this.tabSelectorState.selectedTabs = allTabIds;
2185
2796
 
2186
- console.log('[UIManager] Select all tabs:', allTabIds);
2797
+
2187
2798
 
2188
2799
  // Auto-confirm selection immediately
2189
2800
  this.confirmTabSelection();