vibesurf 0.1.20__py3-none-any.whl → 0.1.22__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/agents/browser_use_agent.py +1 -1
- vibe_surf/agents/prompts/vibe_surf_prompt.py +1 -0
- 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 +5 -4
- vibe_surf/chrome_extension/background.js +271 -25
- vibe_surf/chrome_extension/content.js +147 -0
- 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/file-manager.js +53 -12
- vibe_surf/chrome_extension/scripts/main.js +53 -12
- 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/session-manager.js +30 -4
- vibe_surf/chrome_extension/scripts/settings-manager.js +690 -3
- vibe_surf/chrome_extension/scripts/ui-manager.js +961 -147
- 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.22.dist-info}/METADATA +9 -13
- {vibesurf-0.1.20.dist-info → vibesurf-0.1.22.dist-info}/RECORD +41 -34
- 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.22.dist-info}/WHEEL +0 -0
- {vibesurf-0.1.20.dist-info → vibesurf-0.1.22.dist-info}/entry_points.txt +0 -0
- {vibesurf-0.1.20.dist-info → vibesurf-0.1.22.dist-info}/licenses/LICENSE +0 -0
- {vibesurf-0.1.20.dist-info → vibesurf-0.1.22.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');
|
|
@@ -1003,6 +1338,15 @@ class VibeSurfUIManager {
|
|
|
1003
1338
|
terminateBtn?.classList.remove('hidden');
|
|
1004
1339
|
break;
|
|
1005
1340
|
|
|
1341
|
+
case 'done':
|
|
1342
|
+
case 'completed':
|
|
1343
|
+
case 'finished':
|
|
1344
|
+
console.log(`[UIManager] Task completed with status: ${status}, hiding panel after delay`);
|
|
1345
|
+
// Treat as ready state
|
|
1346
|
+
panel.classList.add('hidden');
|
|
1347
|
+
panel.classList.remove('error-state');
|
|
1348
|
+
break;
|
|
1349
|
+
|
|
1006
1350
|
default:
|
|
1007
1351
|
console.log(`[UIManager] Unknown control panel status: ${status}, hiding panel`);
|
|
1008
1352
|
panel.classList.add('hidden');
|
|
@@ -1017,7 +1361,7 @@ class VibeSurfUIManager {
|
|
|
1017
1361
|
// Clear the flag after minimum visibility period (2 seconds)
|
|
1018
1362
|
setTimeout(() => {
|
|
1019
1363
|
this.controlPanelMinVisibilityActive = false;
|
|
1020
|
-
|
|
1364
|
+
|
|
1021
1365
|
}, 2000);
|
|
1022
1366
|
}
|
|
1023
1367
|
|
|
@@ -1108,9 +1452,21 @@ class VibeSurfUIManager {
|
|
|
1108
1452
|
}
|
|
1109
1453
|
|
|
1110
1454
|
logs.forEach(log => this.addActivityLog(log));
|
|
1455
|
+
|
|
1456
|
+
// Bind link click handlers to all existing activity items after loading
|
|
1457
|
+
this.bindLinkHandlersToAllActivityItems();
|
|
1458
|
+
|
|
1111
1459
|
this.scrollActivityToBottom();
|
|
1112
1460
|
}
|
|
1113
1461
|
|
|
1462
|
+
bindLinkHandlersToAllActivityItems() {
|
|
1463
|
+
// Bind link click handlers to all existing activity items
|
|
1464
|
+
const activityItems = this.elements.activityLog.querySelectorAll('.activity-item');
|
|
1465
|
+
activityItems.forEach(item => {
|
|
1466
|
+
this.bindLinkClickHandlers(item);
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1114
1470
|
addActivityLog(activityData) {
|
|
1115
1471
|
// Filter out "done" status messages from UI display only
|
|
1116
1472
|
const agentStatus = activityData.agent_status || activityData.status || '';
|
|
@@ -1140,6 +1496,9 @@ class VibeSurfUIManager {
|
|
|
1140
1496
|
|
|
1141
1497
|
// Bind copy button functionality
|
|
1142
1498
|
this.bindCopyButtonEvent(activityItem, activityData);
|
|
1499
|
+
|
|
1500
|
+
// Bind link click handlers to prevent extension freezing
|
|
1501
|
+
this.bindLinkClickHandlers(activityItem);
|
|
1143
1502
|
}
|
|
1144
1503
|
}
|
|
1145
1504
|
}
|
|
@@ -1218,7 +1577,7 @@ class VibeSurfUIManager {
|
|
|
1218
1577
|
}
|
|
1219
1578
|
|
|
1220
1579
|
handleSuggestionTaskClick(taskDescription) {
|
|
1221
|
-
|
|
1580
|
+
|
|
1222
1581
|
|
|
1223
1582
|
// First check if task is running
|
|
1224
1583
|
if (this.state.isTaskRunning) {
|
|
@@ -1240,7 +1599,7 @@ class VibeSurfUIManager {
|
|
|
1240
1599
|
return;
|
|
1241
1600
|
}
|
|
1242
1601
|
|
|
1243
|
-
|
|
1602
|
+
|
|
1244
1603
|
this.elements.taskInput.value = taskDescription;
|
|
1245
1604
|
this.elements.taskInput.focus();
|
|
1246
1605
|
|
|
@@ -1249,7 +1608,7 @@ class VibeSurfUIManager {
|
|
|
1249
1608
|
|
|
1250
1609
|
// Auto-submit the task after a short delay
|
|
1251
1610
|
setTimeout(() => {
|
|
1252
|
-
|
|
1611
|
+
|
|
1253
1612
|
this.handleSendTask();
|
|
1254
1613
|
}, 100);
|
|
1255
1614
|
}
|
|
@@ -1342,6 +1701,147 @@ class VibeSurfUIManager {
|
|
|
1342
1701
|
}
|
|
1343
1702
|
}
|
|
1344
1703
|
|
|
1704
|
+
bindLinkClickHandlers(activityItem) {
|
|
1705
|
+
// Handle all link clicks within activity items to prevent extension freezing
|
|
1706
|
+
const links = activityItem.querySelectorAll('a');
|
|
1707
|
+
|
|
1708
|
+
links.forEach(link => {
|
|
1709
|
+
// Check if handler is already attached to prevent double binding
|
|
1710
|
+
if (link.hasAttribute('data-link-handler-attached')) {
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
link.setAttribute('data-link-handler-attached', 'true');
|
|
1715
|
+
|
|
1716
|
+
link.addEventListener('click', async (e) => {
|
|
1717
|
+
console.log('[VibeSurf] Link click event detected:', e);
|
|
1718
|
+
|
|
1719
|
+
// Comprehensive event prevention
|
|
1720
|
+
e.preventDefault();
|
|
1721
|
+
e.stopPropagation();
|
|
1722
|
+
e.stopImmediatePropagation(); // Prevent any other handlers
|
|
1723
|
+
|
|
1724
|
+
// Remove href temporarily to prevent default browser behavior
|
|
1725
|
+
const originalHref = link.getAttribute('href');
|
|
1726
|
+
link.setAttribute('href', '#');
|
|
1727
|
+
|
|
1728
|
+
const href = originalHref;
|
|
1729
|
+
if (!href || href === '#') return;
|
|
1730
|
+
|
|
1731
|
+
// Debounce - prevent rapid repeated clicks
|
|
1732
|
+
if (link.hasAttribute('data-link-processing')) {
|
|
1733
|
+
console.log('[VibeSurf] Link already processing, ignoring duplicate click');
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
link.setAttribute('data-link-processing', 'true');
|
|
1738
|
+
|
|
1739
|
+
try {
|
|
1740
|
+
console.log('[VibeSurf] Processing link:', href);
|
|
1741
|
+
|
|
1742
|
+
// Handle file:// links using existing logic
|
|
1743
|
+
if (href.startsWith('file://')) {
|
|
1744
|
+
await this.handleFileLinkClick(href);
|
|
1745
|
+
}
|
|
1746
|
+
// Handle HTTP/HTTPS links
|
|
1747
|
+
else if (href.startsWith('http://') || href.startsWith('https://')) {
|
|
1748
|
+
await this.handleHttpLinkClick(href);
|
|
1749
|
+
}
|
|
1750
|
+
// Handle other protocols or relative URLs
|
|
1751
|
+
else {
|
|
1752
|
+
await this.handleOtherLinkClick(href);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
console.log('[VibeSurf] Link processed successfully:', href);
|
|
1756
|
+
} catch (error) {
|
|
1757
|
+
console.error('[VibeSurf] Error handling link click:', error);
|
|
1758
|
+
this.showNotification(`Failed to open link: ${error.message}`, 'error');
|
|
1759
|
+
} finally {
|
|
1760
|
+
// Restore original href
|
|
1761
|
+
link.setAttribute('href', originalHref);
|
|
1762
|
+
|
|
1763
|
+
// Remove processing flag after a short delay
|
|
1764
|
+
setTimeout(() => {
|
|
1765
|
+
link.removeAttribute('data-link-processing');
|
|
1766
|
+
}, 1000);
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
async handleFileLinkClick(fileUrl) {
|
|
1773
|
+
console.log('[UIManager] Opening file URL:', fileUrl);
|
|
1774
|
+
|
|
1775
|
+
// Use the background script to handle file URLs
|
|
1776
|
+
const result = await chrome.runtime.sendMessage({
|
|
1777
|
+
type: 'OPEN_FILE_URL',
|
|
1778
|
+
data: { fileUrl }
|
|
1779
|
+
});
|
|
1780
|
+
|
|
1781
|
+
if (!result.success) {
|
|
1782
|
+
throw new Error(result.error || 'Failed to open file');
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
async handleHttpLinkClick(url) {
|
|
1787
|
+
console.log('[VibeSurf] Opening HTTP URL:', url);
|
|
1788
|
+
|
|
1789
|
+
try {
|
|
1790
|
+
// Open HTTP/HTTPS links in a new tab
|
|
1791
|
+
const result = await chrome.runtime.sendMessage({
|
|
1792
|
+
type: 'OPEN_FILE_URL',
|
|
1793
|
+
data: { fileUrl: url }
|
|
1794
|
+
});
|
|
1795
|
+
|
|
1796
|
+
console.log('[VibeSurf] Background script response:', result);
|
|
1797
|
+
|
|
1798
|
+
if (!result || !result.success) {
|
|
1799
|
+
throw new Error(result?.error || 'Failed to create tab for URL');
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
console.log('[VibeSurf] Successfully opened tab:', result.tabId);
|
|
1803
|
+
} catch (error) {
|
|
1804
|
+
console.error('[VibeSurf] Error opening HTTP URL:', error);
|
|
1805
|
+
throw error;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
async handleOtherLinkClick(url) {
|
|
1810
|
+
console.log('[UIManager] Opening other URL:', url);
|
|
1811
|
+
|
|
1812
|
+
// For relative URLs or other protocols, try to open in new tab
|
|
1813
|
+
try {
|
|
1814
|
+
// Use the background script to handle URL opening
|
|
1815
|
+
const result = await chrome.runtime.sendMessage({
|
|
1816
|
+
type: 'OPEN_FILE_URL',
|
|
1817
|
+
data: { fileUrl: url }
|
|
1818
|
+
});
|
|
1819
|
+
|
|
1820
|
+
if (!result.success) {
|
|
1821
|
+
throw new Error(result.error || 'Failed to open URL');
|
|
1822
|
+
}
|
|
1823
|
+
} catch (error) {
|
|
1824
|
+
// If the background script method fails, try to construct absolute URL
|
|
1825
|
+
if (!url.startsWith('http')) {
|
|
1826
|
+
try {
|
|
1827
|
+
const absoluteUrl = new URL(url, window.location.href).href;
|
|
1828
|
+
const result = await chrome.runtime.sendMessage({
|
|
1829
|
+
type: 'OPEN_FILE_URL',
|
|
1830
|
+
data: { fileUrl: absoluteUrl }
|
|
1831
|
+
});
|
|
1832
|
+
|
|
1833
|
+
if (!result.success) {
|
|
1834
|
+
throw new Error(result.error || 'Failed to open absolute URL');
|
|
1835
|
+
}
|
|
1836
|
+
} catch (urlError) {
|
|
1837
|
+
throw new Error(`Failed to open URL: ${urlError.message}`);
|
|
1838
|
+
}
|
|
1839
|
+
} else {
|
|
1840
|
+
throw error;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1345
1845
|
async copyMessageToClipboard(activityData) {
|
|
1346
1846
|
try {
|
|
1347
1847
|
// Extract only the message content (no agent info or timestamps)
|
|
@@ -1370,9 +1870,9 @@ class VibeSurfUIManager {
|
|
|
1370
1870
|
}
|
|
1371
1871
|
|
|
1372
1872
|
// Check clipboard API availability
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1873
|
+
|
|
1874
|
+
|
|
1875
|
+
|
|
1376
1876
|
|
|
1377
1877
|
// Try multiple clipboard methods
|
|
1378
1878
|
let copySuccess = false;
|
|
@@ -1380,7 +1880,7 @@ class VibeSurfUIManager {
|
|
|
1380
1880
|
|
|
1381
1881
|
// Method 1: Chrome extension messaging approach
|
|
1382
1882
|
try {
|
|
1383
|
-
|
|
1883
|
+
|
|
1384
1884
|
await new Promise((resolve, reject) => {
|
|
1385
1885
|
chrome.runtime.sendMessage({
|
|
1386
1886
|
type: 'COPY_TO_CLIPBOARD',
|
|
@@ -1396,7 +1896,7 @@ class VibeSurfUIManager {
|
|
|
1396
1896
|
});
|
|
1397
1897
|
});
|
|
1398
1898
|
copySuccess = true;
|
|
1399
|
-
|
|
1899
|
+
|
|
1400
1900
|
} catch (extensionError) {
|
|
1401
1901
|
console.warn('[UIManager] Chrome extension messaging failed:', extensionError);
|
|
1402
1902
|
lastError = extensionError;
|
|
@@ -1405,10 +1905,10 @@ class VibeSurfUIManager {
|
|
|
1405
1905
|
// Method 2: Modern clipboard API (if extension method failed)
|
|
1406
1906
|
if (!copySuccess && navigator.clipboard && navigator.clipboard.writeText) {
|
|
1407
1907
|
try {
|
|
1408
|
-
|
|
1908
|
+
|
|
1409
1909
|
await navigator.clipboard.writeText(messageText);
|
|
1410
1910
|
copySuccess = true;
|
|
1411
|
-
|
|
1911
|
+
|
|
1412
1912
|
} catch (clipboardError) {
|
|
1413
1913
|
console.warn('[UIManager] Modern clipboard API failed:', clipboardError);
|
|
1414
1914
|
lastError = clipboardError;
|
|
@@ -1418,7 +1918,7 @@ class VibeSurfUIManager {
|
|
|
1418
1918
|
// Method 3: Fallback using execCommand
|
|
1419
1919
|
if (!copySuccess) {
|
|
1420
1920
|
try {
|
|
1421
|
-
|
|
1921
|
+
|
|
1422
1922
|
const textArea = document.createElement('textarea');
|
|
1423
1923
|
textArea.value = messageText;
|
|
1424
1924
|
textArea.style.position = 'fixed';
|
|
@@ -1435,7 +1935,7 @@ class VibeSurfUIManager {
|
|
|
1435
1935
|
|
|
1436
1936
|
if (success) {
|
|
1437
1937
|
copySuccess = true;
|
|
1438
|
-
|
|
1938
|
+
|
|
1439
1939
|
} else {
|
|
1440
1940
|
console.warn('[UIManager] execCommand returned false');
|
|
1441
1941
|
}
|
|
@@ -1448,7 +1948,7 @@ class VibeSurfUIManager {
|
|
|
1448
1948
|
if (copySuccess) {
|
|
1449
1949
|
// Show visual feedback
|
|
1450
1950
|
this.showCopyFeedback();
|
|
1451
|
-
|
|
1951
|
+
|
|
1452
1952
|
} else {
|
|
1453
1953
|
throw new Error(`All clipboard methods failed. Last error: ${lastError?.message || 'Unknown error'}`);
|
|
1454
1954
|
}
|
|
@@ -1668,9 +2168,12 @@ class VibeSurfUIManager {
|
|
|
1668
2168
|
}
|
|
1669
2169
|
}
|
|
1670
2170
|
|
|
1671
|
-
updateLLMProfileSelect() {
|
|
2171
|
+
async updateLLMProfileSelect() {
|
|
1672
2172
|
if (!this.elements.llmProfileSelect) return;
|
|
1673
2173
|
|
|
2174
|
+
// Preserve current user selection if any (to avoid overriding during profile updates)
|
|
2175
|
+
const currentSelection = this.elements.llmProfileSelect.value;
|
|
2176
|
+
|
|
1674
2177
|
const profiles = this.settingsManager.getLLMProfiles();
|
|
1675
2178
|
const select = this.elements.llmProfileSelect;
|
|
1676
2179
|
select.innerHTML = '';
|
|
@@ -1691,16 +2194,50 @@ class VibeSurfUIManager {
|
|
|
1691
2194
|
emptyOption.disabled = true;
|
|
1692
2195
|
select.appendChild(emptyOption);
|
|
1693
2196
|
|
|
2197
|
+
// Determine selection priority: current selection > saved selection > default profile
|
|
2198
|
+
let targetSelection = currentSelection; // Preserve current selection first
|
|
2199
|
+
|
|
2200
|
+
// If no current selection, get saved selection
|
|
2201
|
+
if (!targetSelection) {
|
|
2202
|
+
try {
|
|
2203
|
+
if (this.userSettingsStorage) {
|
|
2204
|
+
targetSelection = await this.userSettingsStorage.getSelectedLlmProfile();
|
|
2205
|
+
}
|
|
2206
|
+
} catch (error) {
|
|
2207
|
+
console.error('[UIManager] Failed to get saved LLM profile selection:', error);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
|
|
1694
2211
|
// Add actual profiles
|
|
2212
|
+
let hasSelectedProfile = false;
|
|
2213
|
+
let hasTargetProfile = false;
|
|
2214
|
+
|
|
1695
2215
|
profiles.forEach(profile => {
|
|
1696
2216
|
const option = document.createElement('option');
|
|
1697
2217
|
option.value = profile.profile_name;
|
|
1698
2218
|
option.textContent = profile.profile_name;
|
|
1699
|
-
|
|
1700
|
-
|
|
2219
|
+
|
|
2220
|
+
// Check if this profile matches our target selection
|
|
2221
|
+
if (targetSelection && profile.profile_name === targetSelection) {
|
|
2222
|
+
hasTargetProfile = true;
|
|
1701
2223
|
}
|
|
2224
|
+
|
|
1702
2225
|
select.appendChild(option);
|
|
1703
2226
|
});
|
|
2227
|
+
|
|
2228
|
+
// Apply selection based on priority
|
|
2229
|
+
if (hasTargetProfile) {
|
|
2230
|
+
select.value = targetSelection;
|
|
2231
|
+
hasSelectedProfile = true;
|
|
2232
|
+
} else {
|
|
2233
|
+
// Fall back to default profile if target not available
|
|
2234
|
+
const defaultProfile = profiles.find(p => p.is_default);
|
|
2235
|
+
if (defaultProfile) {
|
|
2236
|
+
select.value = defaultProfile.profile_name;
|
|
2237
|
+
hasSelectedProfile = true;
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
|
|
1704
2241
|
}
|
|
1705
2242
|
|
|
1706
2243
|
// Update send button state if taskInput exists
|
|
@@ -1721,7 +2258,7 @@ class VibeSurfUIManager {
|
|
|
1721
2258
|
confirmText: 'Open Settings',
|
|
1722
2259
|
cancelText: 'Cancel',
|
|
1723
2260
|
onConfirm: () => {
|
|
1724
|
-
this.
|
|
2261
|
+
this.handleShowLLMSettings();
|
|
1725
2262
|
}
|
|
1726
2263
|
}
|
|
1727
2264
|
: {
|
|
@@ -1731,15 +2268,61 @@ class VibeSurfUIManager {
|
|
|
1731
2268
|
this.elements.llmProfileSelect?.focus();
|
|
1732
2269
|
},
|
|
1733
2270
|
onCancel: () => {
|
|
1734
|
-
this.
|
|
2271
|
+
this.handleShowLLMSettings();
|
|
2272
|
+
}
|
|
2273
|
+
};
|
|
2274
|
+
|
|
2275
|
+
this.modalManager.showWarningModal(title, message, options);
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
showVoiceProfileRequiredModal(action) {
|
|
2279
|
+
const isConfigureAction = action === 'configure';
|
|
2280
|
+
const title = isConfigureAction ? 'Voice Profile Required' : 'Please Select Voice Profile';
|
|
2281
|
+
const message = isConfigureAction
|
|
2282
|
+
? 'No voice recognition (ASR) profiles are configured. You need to configure at least one voice profile before using voice input.'
|
|
2283
|
+
: 'Please configure a voice recognition profile to use voice input functionality.';
|
|
2284
|
+
|
|
2285
|
+
const options = isConfigureAction
|
|
2286
|
+
? {
|
|
2287
|
+
confirmText: 'Open Voice Settings',
|
|
2288
|
+
cancelText: 'Cancel',
|
|
2289
|
+
onConfirm: () => {
|
|
2290
|
+
this.handleShowVoiceSettings();
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
: {
|
|
2294
|
+
confirmText: 'Open Voice Settings',
|
|
2295
|
+
cancelText: 'Cancel',
|
|
2296
|
+
onConfirm: () => {
|
|
2297
|
+
this.handleShowVoiceSettings();
|
|
1735
2298
|
}
|
|
1736
2299
|
};
|
|
1737
2300
|
|
|
1738
2301
|
this.modalManager.showWarningModal(title, message, options);
|
|
1739
2302
|
}
|
|
1740
2303
|
|
|
2304
|
+
async handleShowVoiceSettings() {
|
|
2305
|
+
// Enhanced task running check
|
|
2306
|
+
const statusCheck = await this.checkTaskStatus();
|
|
2307
|
+
if (statusCheck.isRunning) {
|
|
2308
|
+
const canProceed = await this.showTaskRunningWarning('access voice settings');
|
|
2309
|
+
if (!canProceed) return;
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
// Show settings and navigate directly to Voice profiles tab
|
|
2313
|
+
this.settingsManager.showSettings();
|
|
2314
|
+
|
|
2315
|
+
// Switch to Voice profiles tab after settings are shown
|
|
2316
|
+
setTimeout(() => {
|
|
2317
|
+
const voiceTab = document.querySelector('.settings-tab[data-tab="voice-profiles"]');
|
|
2318
|
+
if (voiceTab) {
|
|
2319
|
+
voiceTab.click();
|
|
2320
|
+
}
|
|
2321
|
+
}, 100);
|
|
2322
|
+
}
|
|
2323
|
+
|
|
1741
2324
|
showLLMConnectionFailedModal(errorData) {
|
|
1742
|
-
|
|
2325
|
+
|
|
1743
2326
|
|
|
1744
2327
|
const llmProfile = errorData.llm_profile || 'unknown';
|
|
1745
2328
|
const errorMessage = errorData.message || 'Cannot connect to LLM API';
|
|
@@ -1756,7 +2339,7 @@ class VibeSurfUIManager {
|
|
|
1756
2339
|
}
|
|
1757
2340
|
};
|
|
1758
2341
|
|
|
1759
|
-
|
|
2342
|
+
|
|
1760
2343
|
this.modalManager.showWarningModal(title, message, options);
|
|
1761
2344
|
}
|
|
1762
2345
|
|
|
@@ -1825,20 +2408,132 @@ class VibeSurfUIManager {
|
|
|
1825
2408
|
});
|
|
1826
2409
|
}
|
|
1827
2410
|
|
|
2411
|
+
// Restore LLM profile selection from user settings storage
|
|
2412
|
+
async restoreLlmProfileSelection() {
|
|
2413
|
+
try {
|
|
2414
|
+
if (this.userSettingsStorage && this.elements.llmProfileSelect) {
|
|
2415
|
+
// Check current options available
|
|
2416
|
+
const availableOptions = Array.from(this.elements.llmProfileSelect.options).map(opt => opt.value);
|
|
2417
|
+
|
|
2418
|
+
const savedLlmProfile = await this.userSettingsStorage.getSelectedLlmProfile();
|
|
2419
|
+
|
|
2420
|
+
if (savedLlmProfile && savedLlmProfile.trim() !== '') {
|
|
2421
|
+
// Check if the saved profile exists in the current options
|
|
2422
|
+
const option = this.elements.llmProfileSelect.querySelector(`option[value="${savedLlmProfile}"]`);
|
|
2423
|
+
|
|
2424
|
+
if (option) {
|
|
2425
|
+
this.elements.llmProfileSelect.value = savedLlmProfile;
|
|
2426
|
+
} else {
|
|
2427
|
+
console.warn('[UIManager] Saved LLM profile not found in current options:', savedLlmProfile);
|
|
2428
|
+
}
|
|
2429
|
+
} else {
|
|
2430
|
+
// Check localStorage backup for browser restart scenarios
|
|
2431
|
+
const backupProfile = localStorage.getItem('vibesurf-llm-profile-backup');
|
|
2432
|
+
|
|
2433
|
+
if (backupProfile) {
|
|
2434
|
+
const option = this.elements.llmProfileSelect.querySelector(`option[value="${backupProfile}"]`);
|
|
2435
|
+
if (option) {
|
|
2436
|
+
this.elements.llmProfileSelect.value = backupProfile;
|
|
2437
|
+
// Also save it back to Chrome storage
|
|
2438
|
+
await this.userSettingsStorage.setSelectedLlmProfile(backupProfile);
|
|
2439
|
+
} else {
|
|
2440
|
+
console.warn('[UIManager] Backup profile not found in current options:', backupProfile);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
} else {
|
|
2445
|
+
console.warn('[UIManager] Required components not available - userSettingsStorage:', !!this.userSettingsStorage, 'llmProfileSelect:', !!this.elements.llmProfileSelect);
|
|
2446
|
+
}
|
|
2447
|
+
} catch (error) {
|
|
2448
|
+
console.error('[UIManager] Failed to restore LLM profile selection:', error);
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
// Check and update voice button state based on ASR profile availability
|
|
2453
|
+
async updateVoiceButtonState() {
|
|
2454
|
+
if (!this.elements.voiceRecordBtn) return;
|
|
2455
|
+
|
|
2456
|
+
try {
|
|
2457
|
+
const isVoiceAvailable = await this.voiceRecorder.isVoiceRecordingAvailable();
|
|
2458
|
+
|
|
2459
|
+
if (!isVoiceAvailable) {
|
|
2460
|
+
// Add visual indication but keep button enabled for click handling
|
|
2461
|
+
this.elements.voiceRecordBtn.classList.add('voice-disabled');
|
|
2462
|
+
this.elements.voiceRecordBtn.setAttribute('title', 'Voice input disabled - No ASR profiles configured. Click to configure.');
|
|
2463
|
+
this.elements.voiceRecordBtn.setAttribute('data-tooltip', 'Voice input disabled - No ASR profiles configured. Click to configure.');
|
|
2464
|
+
} else {
|
|
2465
|
+
// Remove visual indication and restore normal tooltip
|
|
2466
|
+
this.elements.voiceRecordBtn.classList.remove('voice-disabled');
|
|
2467
|
+
if (!this.elements.voiceRecordBtn.classList.contains('recording')) {
|
|
2468
|
+
this.elements.voiceRecordBtn.setAttribute('title', 'Click to start voice recording');
|
|
2469
|
+
this.elements.voiceRecordBtn.setAttribute('data-tooltip', 'Click to start voice recording');
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
} catch (error) {
|
|
2473
|
+
console.error('[UIManager] Error updating voice button state:', error);
|
|
2474
|
+
// Fallback: add visual indication but keep button enabled
|
|
2475
|
+
this.elements.voiceRecordBtn.classList.add('voice-disabled');
|
|
2476
|
+
this.elements.voiceRecordBtn.setAttribute('title', 'Voice input temporarily unavailable. Click for more info.');
|
|
2477
|
+
this.elements.voiceRecordBtn.setAttribute('data-tooltip', 'Voice input temporarily unavailable. Click for more info.');
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
|
|
1828
2481
|
// Initialization
|
|
1829
2482
|
async initialize() {
|
|
1830
2483
|
try {
|
|
2484
|
+
// Check for microphone permission parameter first (like Doubao AI)
|
|
2485
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
2486
|
+
const enterParam = urlParams.get('enter');
|
|
2487
|
+
|
|
2488
|
+
if (enterParam === 'mic-permission') {
|
|
2489
|
+
console.log('[UIManager] Detected microphone permission request parameter');
|
|
2490
|
+
this.showMicrophonePermissionRequest();
|
|
2491
|
+
return; // Skip normal initialization for permission request
|
|
2492
|
+
}
|
|
2493
|
+
|
|
1831
2494
|
this.showLoading('Initializing VibeSurf...');
|
|
1832
2495
|
|
|
2496
|
+
// Initialize user settings storage first
|
|
2497
|
+
if (this.userSettingsStorage) {
|
|
2498
|
+
|
|
2499
|
+
await this.userSettingsStorage.initialize();
|
|
2500
|
+
|
|
2501
|
+
} else {
|
|
2502
|
+
console.error('[UIManager] userSettingsStorage not available during initialization');
|
|
2503
|
+
}
|
|
2504
|
+
|
|
1833
2505
|
// Load settings data through settings manager
|
|
2506
|
+
|
|
1834
2507
|
await this.settingsManager.loadSettingsData();
|
|
1835
2508
|
|
|
2509
|
+
|
|
2510
|
+
// Now restore user selections AFTER profiles are loaded but before any other initialization
|
|
2511
|
+
|
|
2512
|
+
this.isRestoringSelections = true;
|
|
2513
|
+
|
|
2514
|
+
try {
|
|
2515
|
+
// Restore LLM profile selection first
|
|
2516
|
+
await this.restoreLlmProfileSelection();
|
|
2517
|
+
|
|
2518
|
+
// Restore agent mode selection
|
|
2519
|
+
await this.restoreAgentModeSelection();
|
|
2520
|
+
} finally {
|
|
2521
|
+
this.isRestoringSelections = false;
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
// Check and update voice button state based on ASR profile availability
|
|
2525
|
+
await this.updateVoiceButtonState();
|
|
2526
|
+
|
|
2527
|
+
|
|
1836
2528
|
// Create initial session if none exists
|
|
2529
|
+
|
|
1837
2530
|
if (!this.sessionManager.getCurrentSession()) {
|
|
2531
|
+
|
|
1838
2532
|
await this.sessionManager.createSession();
|
|
1839
2533
|
}
|
|
1840
2534
|
|
|
1841
2535
|
this.hideLoading();
|
|
2536
|
+
|
|
1842
2537
|
} catch (error) {
|
|
1843
2538
|
this.hideLoading();
|
|
1844
2539
|
console.error('[UIManager] Initialization failed:', error);
|
|
@@ -1846,47 +2541,204 @@ class VibeSurfUIManager {
|
|
|
1846
2541
|
}
|
|
1847
2542
|
}
|
|
1848
2543
|
|
|
1849
|
-
//
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
2544
|
+
// Show microphone permission request (like Doubao AI) - simplified approach
|
|
2545
|
+
showMicrophonePermissionRequest() {
|
|
2546
|
+
console.log('[UIManager] Showing simplified microphone permission request');
|
|
2547
|
+
console.log('[UIManager] Current URL:', window.location.href);
|
|
2548
|
+
console.log('[UIManager] URL params:', window.location.search);
|
|
1853
2549
|
|
|
1854
|
-
//
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
2550
|
+
// Simple approach: just try to get permission directly
|
|
2551
|
+
this.requestMicrophonePermissionDirectly();
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
// Direct microphone permission request (simplified like Doubao AI)
|
|
2555
|
+
async requestMicrophonePermissionDirectly() {
|
|
2556
|
+
console.log('[UIManager] Requesting microphone permission directly');
|
|
1861
2557
|
|
|
1862
|
-
|
|
1863
|
-
//
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
2558
|
+
try {
|
|
2559
|
+
// Immediately try to get microphone permission
|
|
2560
|
+
console.log('[UIManager] Calling getUserMedia...');
|
|
2561
|
+
console.log('[UIManager] User agent:', navigator.userAgent);
|
|
2562
|
+
console.log('[UIManager] Location protocol:', window.location.protocol);
|
|
2563
|
+
console.log('[UIManager] Location href:', window.location.href);
|
|
2564
|
+
|
|
2565
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
2566
|
+
audio: true,
|
|
2567
|
+
video: false
|
|
2568
|
+
});
|
|
2569
|
+
|
|
2570
|
+
console.log('[UIManager] Microphone permission granted!');
|
|
2571
|
+
|
|
2572
|
+
// Stop the stream immediately (we just need permission)
|
|
2573
|
+
stream.getTracks().forEach(track => track.stop());
|
|
2574
|
+
|
|
2575
|
+
// Send success message back to original tab
|
|
2576
|
+
console.log('[UIManager] Sending success message...');
|
|
2577
|
+
chrome.runtime.sendMessage({
|
|
2578
|
+
type: 'MICROPHONE_PERMISSION_RESULT',
|
|
2579
|
+
granted: true
|
|
2580
|
+
}, (response) => {
|
|
2581
|
+
console.log('[UIManager] Success message response:', response);
|
|
2582
|
+
});
|
|
2583
|
+
|
|
2584
|
+
// Close this tab after a short delay
|
|
2585
|
+
setTimeout(() => {
|
|
2586
|
+
console.log('[UIManager] Closing tab...');
|
|
2587
|
+
window.close();
|
|
2588
|
+
}, 1000);
|
|
2589
|
+
|
|
2590
|
+
} catch (error) {
|
|
2591
|
+
console.error('[UIManager] Microphone permission denied:', error);
|
|
2592
|
+
console.log('[UIManager] Error name:', error.name);
|
|
2593
|
+
console.log('[UIManager] Error message:', error.message);
|
|
2594
|
+
console.log('[UIManager] Error stack:', error.stack);
|
|
2595
|
+
|
|
2596
|
+
// Send error message back to original tab
|
|
2597
|
+
console.log('[UIManager] Sending error message...');
|
|
2598
|
+
chrome.runtime.sendMessage({
|
|
2599
|
+
type: 'MICROPHONE_PERMISSION_RESULT',
|
|
2600
|
+
granted: false,
|
|
2601
|
+
error: error.message,
|
|
2602
|
+
errorName: error.name
|
|
2603
|
+
}, (response) => {
|
|
2604
|
+
console.log('[UIManager] Error message response:', response);
|
|
2605
|
+
});
|
|
2606
|
+
|
|
2607
|
+
// Close this tab after a short delay
|
|
2608
|
+
setTimeout(() => {
|
|
2609
|
+
console.log('[UIManager] Closing tab after error...');
|
|
2610
|
+
window.close();
|
|
2611
|
+
}, 2000);
|
|
1867
2612
|
}
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
2613
|
+
}
|
|
2614
|
+
// Restore agent mode selection from user settings storage
|
|
2615
|
+
async restoreAgentModeSelection() {
|
|
2616
|
+
try {
|
|
2617
|
+
|
|
2618
|
+
if (this.userSettingsStorage && this.elements.agentModeSelect) {
|
|
2619
|
+
// Check current options available
|
|
2620
|
+
const availableOptions = Array.from(this.elements.agentModeSelect.options).map(opt => opt.value);
|
|
2621
|
+
|
|
2622
|
+
const savedAgentMode = await this.userSettingsStorage.getSelectedAgentMode();
|
|
2623
|
+
|
|
2624
|
+
if (savedAgentMode && savedAgentMode.trim() !== '') {
|
|
2625
|
+
// Restore any saved agent mode, including 'thinking'
|
|
2626
|
+
this.elements.agentModeSelect.value = savedAgentMode;
|
|
2627
|
+
// Ensure the option is actually selected
|
|
2628
|
+
const option = this.elements.agentModeSelect.querySelector(`option[value="${savedAgentMode}"]`);
|
|
2629
|
+
if (option) {
|
|
2630
|
+
option.selected = true;
|
|
2631
|
+
console.log('[UIManager] Restored agent mode selection:', savedAgentMode);
|
|
2632
|
+
} else {
|
|
2633
|
+
console.warn('[UIManager] Agent mode option not found:', savedAgentMode);
|
|
2634
|
+
}
|
|
2635
|
+
} else {
|
|
2636
|
+
// Check localStorage backup for browser restart scenarios
|
|
2637
|
+
const backupMode = localStorage.getItem('vibesurf-agent-mode-backup');
|
|
2638
|
+
|
|
2639
|
+
if (backupMode) {
|
|
2640
|
+
const option = this.elements.agentModeSelect.querySelector(`option[value="${backupMode}"]`);
|
|
2641
|
+
if (option) {
|
|
2642
|
+
this.elements.agentModeSelect.value = backupMode;
|
|
2643
|
+
// Also save it back to Chrome storage
|
|
2644
|
+
await this.userSettingsStorage.setSelectedAgentMode(backupMode);
|
|
2645
|
+
} else {
|
|
2646
|
+
console.warn('[UIManager] Backup agent mode not found in current options:', backupMode);
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
} else {
|
|
2651
|
+
console.warn('[UIManager] Required components not available - userSettingsStorage:', !!this.userSettingsStorage, 'agentModeSelect:', !!this.elements.agentModeSelect);
|
|
1873
2652
|
}
|
|
2653
|
+
} catch (error) {
|
|
2654
|
+
console.error('[UIManager] Failed to restore agent mode selection:', error);
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
// Cleanup
|
|
2659
|
+
destroy() {
|
|
2660
|
+
// Prevent multiple cleanup calls
|
|
2661
|
+
if (this.isDestroying) {
|
|
2662
|
+
console.log('[UIManager] Cleanup already in progress, skipping...');
|
|
2663
|
+
return;
|
|
1874
2664
|
}
|
|
1875
2665
|
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
2666
|
+
this.isDestroying = true;
|
|
2667
|
+
console.log('[UIManager] Destroying UI manager...');
|
|
2668
|
+
|
|
2669
|
+
try {
|
|
2670
|
+
// Stop task status monitoring
|
|
2671
|
+
this.stopTaskStatusMonitoring();
|
|
2672
|
+
|
|
2673
|
+
// Cleanup voice recorder
|
|
2674
|
+
if (this.voiceRecorder) {
|
|
2675
|
+
this.voiceRecorder.cleanup();
|
|
2676
|
+
this.voiceRecorder = null;
|
|
1880
2677
|
}
|
|
2678
|
+
|
|
2679
|
+
// Cleanup managers with error handling
|
|
2680
|
+
if (this.settingsManager) {
|
|
2681
|
+
try {
|
|
2682
|
+
if (typeof this.settingsManager.destroy === 'function') {
|
|
2683
|
+
this.settingsManager.destroy();
|
|
2684
|
+
}
|
|
2685
|
+
} catch (error) {
|
|
2686
|
+
console.error('[UIManager] Error destroying settings manager:', error);
|
|
2687
|
+
}
|
|
2688
|
+
this.settingsManager = null;
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
if (this.historyManager) {
|
|
2692
|
+
try {
|
|
2693
|
+
if (typeof this.historyManager.destroy === 'function') {
|
|
2694
|
+
this.historyManager.destroy();
|
|
2695
|
+
}
|
|
2696
|
+
} catch (error) {
|
|
2697
|
+
console.error('[UIManager] Error destroying history manager:', error);
|
|
2698
|
+
}
|
|
2699
|
+
this.historyManager = null;
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
if (this.fileManager) {
|
|
2703
|
+
try {
|
|
2704
|
+
if (typeof this.fileManager.destroy === 'function') {
|
|
2705
|
+
this.fileManager.destroy();
|
|
2706
|
+
}
|
|
2707
|
+
} catch (error) {
|
|
2708
|
+
console.error('[UIManager] Error destroying file manager:', error);
|
|
2709
|
+
}
|
|
2710
|
+
this.fileManager = null;
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
if (this.modalManager) {
|
|
2714
|
+
try {
|
|
2715
|
+
if (typeof this.modalManager.destroy === 'function') {
|
|
2716
|
+
this.modalManager.destroy();
|
|
2717
|
+
}
|
|
2718
|
+
} catch (error) {
|
|
2719
|
+
console.error('[UIManager] Error destroying modal manager:', error);
|
|
2720
|
+
}
|
|
2721
|
+
this.modalManager = null;
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
// Clear state
|
|
2725
|
+
this.state = {
|
|
2726
|
+
isLoading: false,
|
|
2727
|
+
isTaskRunning: false,
|
|
2728
|
+
taskInfo: null
|
|
2729
|
+
};
|
|
2730
|
+
|
|
2731
|
+
console.log('[UIManager] UI manager cleanup complete');
|
|
2732
|
+
} catch (error) {
|
|
2733
|
+
console.error('[UIManager] Error during destroy:', error);
|
|
2734
|
+
} finally {
|
|
2735
|
+
this.isDestroying = false;
|
|
1881
2736
|
}
|
|
1882
|
-
|
|
1883
|
-
// Clear state
|
|
1884
|
-
this.state.currentModal = null;
|
|
1885
2737
|
}
|
|
1886
2738
|
|
|
1887
2739
|
// Tab Selector Methods
|
|
1888
2740
|
initializeTabSelector() {
|
|
1889
|
-
|
|
2741
|
+
|
|
1890
2742
|
|
|
1891
2743
|
// Initialize tab selector state
|
|
1892
2744
|
this.tabSelectorState = {
|
|
@@ -1896,20 +2748,15 @@ class VibeSurfUIManager {
|
|
|
1896
2748
|
atPosition: -1 // Position where @ was typed
|
|
1897
2749
|
};
|
|
1898
2750
|
|
|
1899
|
-
|
|
2751
|
+
|
|
1900
2752
|
|
|
1901
2753
|
// Bind tab selector events
|
|
1902
2754
|
this.bindTabSelectorEvents();
|
|
1903
2755
|
}
|
|
1904
2756
|
|
|
1905
2757
|
bindTabSelectorEvents() {
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
tabSelectorCancel: !!this.elements.tabSelectorCancel,
|
|
1909
|
-
tabSelectorConfirm: !!this.elements.tabSelectorConfirm,
|
|
1910
|
-
selectAllTabs: !!this.elements.selectAllTabs,
|
|
1911
|
-
tabSelectorDropdown: !!this.elements.tabSelectorDropdown
|
|
1912
|
-
});
|
|
2758
|
+
|
|
2759
|
+
|
|
1913
2760
|
|
|
1914
2761
|
// Select all radio button
|
|
1915
2762
|
this.elements.selectAllTabs?.addEventListener('change', this.handleSelectAllTabs.bind(this));
|
|
@@ -1924,7 +2771,7 @@ class VibeSurfUIManager {
|
|
|
1924
2771
|
}
|
|
1925
2772
|
});
|
|
1926
2773
|
|
|
1927
|
-
|
|
2774
|
+
|
|
1928
2775
|
}
|
|
1929
2776
|
|
|
1930
2777
|
handleTabSelectorInput(event) {
|
|
@@ -1937,16 +2784,11 @@ class VibeSurfUIManager {
|
|
|
1937
2784
|
const inputValue = event.target.value;
|
|
1938
2785
|
const cursorPosition = event.target.selectionStart;
|
|
1939
2786
|
|
|
1940
|
-
|
|
1941
|
-
inputValue,
|
|
1942
|
-
cursorPosition,
|
|
1943
|
-
charAtCursor: inputValue[cursorPosition - 1],
|
|
1944
|
-
isAtSymbol: inputValue[cursorPosition - 1] === '@'
|
|
1945
|
-
});
|
|
2787
|
+
|
|
1946
2788
|
|
|
1947
2789
|
// Check if @ was just typed
|
|
1948
2790
|
if (inputValue[cursorPosition - 1] === '@') {
|
|
1949
|
-
|
|
2791
|
+
|
|
1950
2792
|
this.tabSelectorState.atPosition = cursorPosition - 1;
|
|
1951
2793
|
this.showTabSelector();
|
|
1952
2794
|
} else if (this.tabSelectorState.isVisible) {
|
|
@@ -1954,7 +2796,7 @@ class VibeSurfUIManager {
|
|
|
1954
2796
|
if (this.tabSelectorState.atPosition >= 0 &&
|
|
1955
2797
|
(this.tabSelectorState.atPosition >= inputValue.length ||
|
|
1956
2798
|
inputValue[this.tabSelectorState.atPosition] !== '@')) {
|
|
1957
|
-
|
|
2799
|
+
|
|
1958
2800
|
this.hideTabSelector();
|
|
1959
2801
|
return;
|
|
1960
2802
|
}
|
|
@@ -1962,19 +2804,15 @@ class VibeSurfUIManager {
|
|
|
1962
2804
|
// Hide tab selector if user continues typing after @
|
|
1963
2805
|
const textAfterAt = inputValue.substring(this.tabSelectorState.atPosition + 1, cursorPosition);
|
|
1964
2806
|
if (textAfterAt.length > 0 && !textAfterAt.match(/^[\s]*$/)) {
|
|
1965
|
-
|
|
2807
|
+
|
|
1966
2808
|
this.hideTabSelector();
|
|
1967
2809
|
}
|
|
1968
2810
|
}
|
|
1969
2811
|
}
|
|
1970
2812
|
|
|
1971
2813
|
async showTabSelector() {
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
dropdown: !!this.elements.tabSelectorDropdown,
|
|
1975
|
-
taskInput: !!this.elements.taskInput,
|
|
1976
|
-
tabOptionsList: !!this.elements.tabOptionsList
|
|
1977
|
-
});
|
|
2814
|
+
|
|
2815
|
+
|
|
1978
2816
|
|
|
1979
2817
|
if (!this.elements.tabSelectorDropdown || !this.elements.taskInput) {
|
|
1980
2818
|
console.error('[UIManager] Tab selector elements not found', {
|
|
@@ -1985,15 +2823,15 @@ class VibeSurfUIManager {
|
|
|
1985
2823
|
}
|
|
1986
2824
|
|
|
1987
2825
|
try {
|
|
1988
|
-
|
|
2826
|
+
|
|
1989
2827
|
// Fetch tab data from backend
|
|
1990
2828
|
await this.populateTabSelector();
|
|
1991
2829
|
|
|
1992
|
-
|
|
2830
|
+
|
|
1993
2831
|
// Position the dropdown relative to the input
|
|
1994
2832
|
this.positionTabSelector();
|
|
1995
2833
|
|
|
1996
|
-
|
|
2834
|
+
|
|
1997
2835
|
// Show the dropdown with explicit visibility
|
|
1998
2836
|
this.elements.tabSelectorDropdown.classList.remove('hidden');
|
|
1999
2837
|
this.elements.tabSelectorDropdown.style.display = 'block';
|
|
@@ -2001,16 +2839,10 @@ class VibeSurfUIManager {
|
|
|
2001
2839
|
this.elements.tabSelectorDropdown.style.opacity = '1';
|
|
2002
2840
|
this.tabSelectorState.isVisible = true;
|
|
2003
2841
|
|
|
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);
|
|
2842
|
+
|
|
2843
|
+
|
|
2844
|
+
|
|
2845
|
+
|
|
2014
2846
|
} catch (error) {
|
|
2015
2847
|
console.error('[UIManager] Failed to show tab selector:', error);
|
|
2016
2848
|
this.showNotification('Failed to load browser tabs', 'error');
|
|
@@ -2026,7 +2858,7 @@ class VibeSurfUIManager {
|
|
|
2026
2858
|
this.tabSelectorState.selectedTabs = [];
|
|
2027
2859
|
this.tabSelectorState.atPosition = -1;
|
|
2028
2860
|
|
|
2029
|
-
|
|
2861
|
+
|
|
2030
2862
|
}
|
|
2031
2863
|
|
|
2032
2864
|
positionTabSelector() {
|
|
@@ -2035,10 +2867,7 @@ class VibeSurfUIManager {
|
|
|
2035
2867
|
const inputRect = this.elements.taskInput.getBoundingClientRect();
|
|
2036
2868
|
const dropdown = this.elements.tabSelectorDropdown;
|
|
2037
2869
|
|
|
2038
|
-
|
|
2039
|
-
inputRect,
|
|
2040
|
-
dropdownElement: dropdown
|
|
2041
|
-
});
|
|
2870
|
+
|
|
2042
2871
|
|
|
2043
2872
|
// Calculate 90% width of input
|
|
2044
2873
|
const dropdownWidth = inputRect.width * 0.9;
|
|
@@ -2052,47 +2881,32 @@ class VibeSurfUIManager {
|
|
|
2052
2881
|
dropdown.style.maxHeight = '300px';
|
|
2053
2882
|
dropdown.style.overflowY = 'auto';
|
|
2054
2883
|
|
|
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
|
-
});
|
|
2884
|
+
|
|
2062
2885
|
}
|
|
2063
2886
|
|
|
2064
2887
|
async populateTabSelector() {
|
|
2065
2888
|
try {
|
|
2066
|
-
|
|
2889
|
+
|
|
2067
2890
|
// Get all tabs and active tab from backend
|
|
2068
2891
|
const [allTabsResponse, activeTabResponse] = await Promise.all([
|
|
2069
2892
|
this.apiClient.getAllBrowserTabs(),
|
|
2070
2893
|
this.apiClient.getActiveBrowserTab()
|
|
2071
2894
|
]);
|
|
2072
2895
|
|
|
2073
|
-
|
|
2074
|
-
allTabsResponse: JSON.stringify(allTabsResponse, null, 2),
|
|
2075
|
-
activeTabResponse: JSON.stringify(activeTabResponse, null, 2)
|
|
2076
|
-
});
|
|
2896
|
+
|
|
2077
2897
|
|
|
2078
2898
|
const allTabs = allTabsResponse.tabs || allTabsResponse || {};
|
|
2079
2899
|
const activeTab = activeTabResponse.tab || activeTabResponse || {};
|
|
2080
2900
|
const activeTabId = Object.keys(activeTab)[0];
|
|
2081
2901
|
|
|
2082
|
-
|
|
2083
|
-
allTabsCount: Object.keys(allTabs).length,
|
|
2084
|
-
activeTabId,
|
|
2085
|
-
allTabIds: Object.keys(allTabs),
|
|
2086
|
-
allTabsData: allTabs,
|
|
2087
|
-
activeTabData: activeTab
|
|
2088
|
-
});
|
|
2902
|
+
|
|
2089
2903
|
|
|
2090
2904
|
this.tabSelectorState.allTabs = allTabs;
|
|
2091
2905
|
|
|
2092
2906
|
// Clear existing options
|
|
2093
2907
|
if (this.elements.tabOptionsList) {
|
|
2094
2908
|
this.elements.tabOptionsList.innerHTML = '';
|
|
2095
|
-
|
|
2909
|
+
|
|
2096
2910
|
} else {
|
|
2097
2911
|
console.error('[UIManager] tabOptionsList element not found!');
|
|
2098
2912
|
return;
|
|
@@ -2109,7 +2923,7 @@ class VibeSurfUIManager {
|
|
|
2109
2923
|
|
|
2110
2924
|
Object.entries(testTabs).forEach(([tabId, tabInfo]) => {
|
|
2111
2925
|
const isActive = tabId === 'test-1';
|
|
2112
|
-
|
|
2926
|
+
|
|
2113
2927
|
const option = this.createTabOption(tabId, tabInfo, isActive);
|
|
2114
2928
|
this.elements.tabOptionsList.appendChild(option);
|
|
2115
2929
|
});
|
|
@@ -2119,7 +2933,7 @@ class VibeSurfUIManager {
|
|
|
2119
2933
|
// Add real tab options
|
|
2120
2934
|
Object.entries(allTabs).forEach(([tabId, tabInfo]) => {
|
|
2121
2935
|
const isActive = tabId === activeTabId;
|
|
2122
|
-
|
|
2936
|
+
|
|
2123
2937
|
const option = this.createTabOption(tabId, tabInfo, isActive);
|
|
2124
2938
|
this.elements.tabOptionsList.appendChild(option);
|
|
2125
2939
|
});
|
|
@@ -2130,7 +2944,7 @@ class VibeSurfUIManager {
|
|
|
2130
2944
|
this.elements.selectAllTabs.checked = false;
|
|
2131
2945
|
}
|
|
2132
2946
|
|
|
2133
|
-
|
|
2947
|
+
|
|
2134
2948
|
} catch (error) {
|
|
2135
2949
|
console.error('[UIManager] Failed to populate tab selector:', error);
|
|
2136
2950
|
throw error;
|
|
@@ -2170,7 +2984,7 @@ class VibeSurfUIManager {
|
|
|
2170
2984
|
// For radio buttons, replace the selected tabs array with just this tab
|
|
2171
2985
|
this.tabSelectorState.selectedTabs = [tabId];
|
|
2172
2986
|
|
|
2173
|
-
|
|
2987
|
+
|
|
2174
2988
|
|
|
2175
2989
|
// Auto-confirm selection immediately
|
|
2176
2990
|
this.confirmTabSelection();
|
|
@@ -2183,7 +2997,7 @@ class VibeSurfUIManager {
|
|
|
2183
2997
|
const allTabIds = Object.keys(this.tabSelectorState.allTabs);
|
|
2184
2998
|
this.tabSelectorState.selectedTabs = allTabIds;
|
|
2185
2999
|
|
|
2186
|
-
|
|
3000
|
+
|
|
2187
3001
|
|
|
2188
3002
|
// Auto-confirm selection immediately
|
|
2189
3003
|
this.confirmTabSelection();
|