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.
- vibe_surf/_version.py +2 -2
- vibe_surf/backend/api/task.py +1 -1
- vibe_surf/backend/api/voices.py +481 -0
- vibe_surf/backend/database/migrations/v004_add_voice_profiles.sql +35 -0
- vibe_surf/backend/database/models.py +38 -1
- vibe_surf/backend/database/queries.py +189 -1
- vibe_surf/backend/main.py +2 -0
- vibe_surf/backend/shared_state.py +1 -1
- vibe_surf/backend/voice_model_config.py +25 -0
- vibe_surf/browser/agen_browser_profile.py +2 -0
- vibe_surf/browser/agent_browser_session.py +3 -3
- vibe_surf/chrome_extension/background.js +224 -9
- vibe_surf/chrome_extension/content.js +147 -0
- vibe_surf/chrome_extension/manifest.json +11 -2
- vibe_surf/chrome_extension/permission-iframe.html +38 -0
- vibe_surf/chrome_extension/permission-request.html +104 -0
- vibe_surf/chrome_extension/scripts/api-client.js +61 -0
- vibe_surf/chrome_extension/scripts/main.js +8 -2
- vibe_surf/chrome_extension/scripts/permission-iframe-request.js +188 -0
- vibe_surf/chrome_extension/scripts/permission-request.js +118 -0
- vibe_surf/chrome_extension/scripts/settings-manager.js +690 -3
- vibe_surf/chrome_extension/scripts/ui-manager.js +730 -119
- vibe_surf/chrome_extension/scripts/user-settings-storage.js +422 -0
- vibe_surf/chrome_extension/scripts/voice-recorder.js +514 -0
- vibe_surf/chrome_extension/sidepanel.html +106 -29
- vibe_surf/chrome_extension/styles/components.css +35 -0
- vibe_surf/chrome_extension/styles/input.css +164 -1
- vibe_surf/chrome_extension/styles/layout.css +1 -1
- vibe_surf/chrome_extension/styles/settings-environment.css +138 -0
- vibe_surf/chrome_extension/styles/settings-forms.css +7 -7
- vibe_surf/chrome_extension/styles/variables.css +51 -0
- vibe_surf/tools/voice_asr.py +79 -8
- {vibesurf-0.1.20.dist-info → vibesurf-0.1.21.dist-info}/METADATA +8 -12
- {vibesurf-0.1.20.dist-info → vibesurf-0.1.21.dist-info}/RECORD +38 -31
- vibe_surf/chrome_extension/icons/convert-svg.js +0 -33
- vibe_surf/chrome_extension/icons/logo-preview.html +0 -187
- {vibesurf-0.1.20.dist-info → vibesurf-0.1.21.dist-info}/WHEEL +0 -0
- {vibesurf-0.1.20.dist-info → vibesurf-0.1.21.dist-info}/entry_points.txt +0 -0
- {vibesurf-0.1.20.dist-info → vibesurf-0.1.21.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
459
|
+
|
|
330
460
|
const remainingTime = 1000;
|
|
331
461
|
setTimeout(() => {
|
|
332
|
-
|
|
462
|
+
|
|
333
463
|
this.updateControlPanel('ready');
|
|
334
464
|
}, remainingTime);
|
|
335
465
|
} else {
|
|
336
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
525
|
+
|
|
396
526
|
this.updateControlPanel('ready');
|
|
397
527
|
} else {
|
|
398
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1037
|
+
|
|
753
1038
|
}
|
|
754
1039
|
|
|
755
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1276
|
+
|
|
942
1277
|
if (this.elements.sessionId) {
|
|
943
1278
|
this.elements.sessionId.textContent = sessionId || '-';
|
|
944
|
-
|
|
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
|
-
|
|
1306
|
+
|
|
972
1307
|
panel.classList.add('hidden');
|
|
973
1308
|
panel.classList.remove('error-state');
|
|
974
1309
|
break;
|
|
975
1310
|
|
|
976
1311
|
case 'running':
|
|
977
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1355
|
+
|
|
1021
1356
|
}, 2000);
|
|
1022
1357
|
}
|
|
1023
1358
|
|
|
@@ -1218,7 +1553,7 @@ class VibeSurfUIManager {
|
|
|
1218
1553
|
}
|
|
1219
1554
|
|
|
1220
1555
|
handleSuggestionTaskClick(taskDescription) {
|
|
1221
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1743
|
+
|
|
1409
1744
|
await navigator.clipboard.writeText(messageText);
|
|
1410
1745
|
copySuccess = true;
|
|
1411
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1700
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2548
|
+
|
|
1900
2549
|
|
|
1901
2550
|
// Bind tab selector events
|
|
1902
2551
|
this.bindTabSelectorEvents();
|
|
1903
2552
|
}
|
|
1904
2553
|
|
|
1905
2554
|
bindTabSelectorEvents() {
|
|
1906
|
-
|
|
1907
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2604
|
+
|
|
1966
2605
|
this.hideTabSelector();
|
|
1967
2606
|
}
|
|
1968
2607
|
}
|
|
1969
2608
|
}
|
|
1970
2609
|
|
|
1971
2610
|
async showTabSelector() {
|
|
1972
|
-
|
|
1973
|
-
|
|
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
|
-
|
|
2623
|
+
|
|
1989
2624
|
// Fetch tab data from backend
|
|
1990
2625
|
await this.populateTabSelector();
|
|
1991
2626
|
|
|
1992
|
-
|
|
2627
|
+
|
|
1993
2628
|
// Position the dropdown relative to the input
|
|
1994
2629
|
this.positionTabSelector();
|
|
1995
2630
|
|
|
1996
|
-
|
|
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
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2797
|
+
|
|
2187
2798
|
|
|
2188
2799
|
// Auto-confirm selection immediately
|
|
2189
2800
|
this.confirmTabSelection();
|