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
|
@@ -53,10 +53,36 @@
|
|
|
53
53
|
sendResponse(clickResult);
|
|
54
54
|
break;
|
|
55
55
|
|
|
56
|
+
case 'INJECT_MICROPHONE_PERMISSION_IFRAME':
|
|
57
|
+
this.injectMicrophonePermissionIframe()
|
|
58
|
+
.then(result => sendResponse(result))
|
|
59
|
+
.catch(error => sendResponse({ success: false, error: error.message }));
|
|
60
|
+
return true; // Will respond asynchronously
|
|
61
|
+
|
|
62
|
+
case 'REMOVE_MICROPHONE_PERMISSION_IFRAME':
|
|
63
|
+
this.removeMicrophonePermissionIframe();
|
|
64
|
+
sendResponse({ success: true });
|
|
65
|
+
break;
|
|
66
|
+
|
|
56
67
|
default:
|
|
57
68
|
console.warn('[VibeSurf Content] Unknown message type:', message.type);
|
|
58
69
|
}
|
|
59
70
|
});
|
|
71
|
+
|
|
72
|
+
// Listen for postMessage from iframe
|
|
73
|
+
window.addEventListener('message', (event) => {
|
|
74
|
+
if (event.data && event.data.type === 'MICROPHONE_PERMISSION_RESULT') {
|
|
75
|
+
console.log('[VibeSurf Content] Received permission result from iframe:', event.data);
|
|
76
|
+
|
|
77
|
+
// Forward to extension
|
|
78
|
+
chrome.runtime.sendMessage({
|
|
79
|
+
type: 'MICROPHONE_PERMISSION_RESULT',
|
|
80
|
+
...event.data
|
|
81
|
+
}).catch(() => {
|
|
82
|
+
// Ignore if no listeners
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
});
|
|
60
86
|
}
|
|
61
87
|
|
|
62
88
|
collectPageContext() {
|
|
@@ -233,6 +259,127 @@
|
|
|
233
259
|
}
|
|
234
260
|
}
|
|
235
261
|
|
|
262
|
+
// Inject hidden iframe for microphone permission request
|
|
263
|
+
async injectMicrophonePermissionIframe() {
|
|
264
|
+
try {
|
|
265
|
+
console.log('[VibeSurf Content] Injecting microphone permission iframe...');
|
|
266
|
+
|
|
267
|
+
// Check if iframe already exists
|
|
268
|
+
const existingIframe = document.getElementById('vibesurf-permission-iframe');
|
|
269
|
+
if (existingIframe) {
|
|
270
|
+
console.log('[VibeSurf Content] Permission iframe already exists');
|
|
271
|
+
return { success: true, alreadyExists: true };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Create the iframe element
|
|
275
|
+
const iframe = document.createElement('iframe');
|
|
276
|
+
iframe.setAttribute('id', 'vibesurf-permission-iframe');
|
|
277
|
+
iframe.setAttribute('allow', 'microphone');
|
|
278
|
+
iframe.setAttribute('hidden', 'hidden');
|
|
279
|
+
iframe.style.display = 'none';
|
|
280
|
+
iframe.style.width = '0px';
|
|
281
|
+
iframe.style.height = '0px';
|
|
282
|
+
iframe.style.border = 'none';
|
|
283
|
+
iframe.style.position = 'fixed';
|
|
284
|
+
iframe.style.top = '-9999px';
|
|
285
|
+
iframe.style.left = '-9999px';
|
|
286
|
+
iframe.style.zIndex = '-1';
|
|
287
|
+
|
|
288
|
+
// Set the source to our permission iframe page
|
|
289
|
+
const iframeUrl = chrome.runtime.getURL('permission-iframe.html');
|
|
290
|
+
iframe.src = iframeUrl;
|
|
291
|
+
|
|
292
|
+
console.log('[VibeSurf Content] Creating iframe with URL:', iframeUrl);
|
|
293
|
+
|
|
294
|
+
// Return a promise that resolves when permission is granted/denied
|
|
295
|
+
return new Promise((resolve, reject) => {
|
|
296
|
+
const timeout = setTimeout(() => {
|
|
297
|
+
console.log('[VibeSurf Content] Permission iframe timeout');
|
|
298
|
+
this.removeMicrophonePermissionIframe();
|
|
299
|
+
reject(new Error('Permission request timeout'));
|
|
300
|
+
}, 30000); // 30 second timeout
|
|
301
|
+
|
|
302
|
+
// Listen for permission result
|
|
303
|
+
const messageHandler = (event) => {
|
|
304
|
+
if (event.data && event.data.type === 'MICROPHONE_PERMISSION_RESULT') {
|
|
305
|
+
console.log('[VibeSurf Content] Received permission result:', event.data);
|
|
306
|
+
|
|
307
|
+
clearTimeout(timeout);
|
|
308
|
+
window.removeEventListener('message', messageHandler);
|
|
309
|
+
|
|
310
|
+
if (event.data.success) {
|
|
311
|
+
resolve({
|
|
312
|
+
success: true,
|
|
313
|
+
granted: event.data.granted,
|
|
314
|
+
source: 'iframe'
|
|
315
|
+
});
|
|
316
|
+
} else {
|
|
317
|
+
resolve({
|
|
318
|
+
success: false,
|
|
319
|
+
granted: false,
|
|
320
|
+
error: event.data.error || 'Permission denied',
|
|
321
|
+
userMessage: event.data.userMessage
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Clean up iframe after a short delay
|
|
326
|
+
setTimeout(() => {
|
|
327
|
+
this.removeMicrophonePermissionIframe();
|
|
328
|
+
}, 1000);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
window.addEventListener('message', messageHandler);
|
|
333
|
+
|
|
334
|
+
// Handle iframe load
|
|
335
|
+
iframe.onload = () => {
|
|
336
|
+
console.log('[VibeSurf Content] Permission iframe loaded successfully');
|
|
337
|
+
|
|
338
|
+
// Send message to iframe to start permission request
|
|
339
|
+
setTimeout(() => {
|
|
340
|
+
if (iframe.contentWindow) {
|
|
341
|
+
iframe.contentWindow.postMessage({
|
|
342
|
+
type: 'REQUEST_MICROPHONE_PERMISSION'
|
|
343
|
+
}, '*');
|
|
344
|
+
}
|
|
345
|
+
}, 100);
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
iframe.onerror = (error) => {
|
|
349
|
+
console.error('[VibeSurf Content] Permission iframe load error:', error);
|
|
350
|
+
clearTimeout(timeout);
|
|
351
|
+
window.removeEventListener('message', messageHandler);
|
|
352
|
+
this.removeMicrophonePermissionIframe();
|
|
353
|
+
reject(new Error('Failed to load permission iframe'));
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// Append to document body
|
|
357
|
+
document.body.appendChild(iframe);
|
|
358
|
+
console.log('[VibeSurf Content] Permission iframe injected into page');
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error('[VibeSurf Content] Failed to inject permission iframe:', error);
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Remove microphone permission iframe
|
|
368
|
+
removeMicrophonePermissionIframe() {
|
|
369
|
+
try {
|
|
370
|
+
const iframe = document.getElementById('vibesurf-permission-iframe');
|
|
371
|
+
if (iframe) {
|
|
372
|
+
console.log('[VibeSurf Content] Removing permission iframe');
|
|
373
|
+
iframe.remove();
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
return false;
|
|
377
|
+
} catch (error) {
|
|
378
|
+
console.error('[VibeSurf Content] Error removing permission iframe:', error);
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
236
383
|
// Utility method to send context updates to background
|
|
237
384
|
sendContextUpdate() {
|
|
238
385
|
try {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Microphone Permission Request</title>
|
|
6
|
+
<style>
|
|
7
|
+
body {
|
|
8
|
+
margin: 0;
|
|
9
|
+
padding: 10px;
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
11
|
+
font-size: 12px;
|
|
12
|
+
background: #f5f5f5;
|
|
13
|
+
color: #333;
|
|
14
|
+
}
|
|
15
|
+
.status {
|
|
16
|
+
padding: 8px;
|
|
17
|
+
border-radius: 4px;
|
|
18
|
+
text-align: center;
|
|
19
|
+
}
|
|
20
|
+
.loading {
|
|
21
|
+
background: #bee3f8;
|
|
22
|
+
color: #2a4365;
|
|
23
|
+
}
|
|
24
|
+
.success {
|
|
25
|
+
background: #c6f6d5;
|
|
26
|
+
color: #22543d;
|
|
27
|
+
}
|
|
28
|
+
.error {
|
|
29
|
+
background: #fed7d7;
|
|
30
|
+
color: #742a2a;
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<div id="status" class="status loading">Requesting microphone access...</div>
|
|
36
|
+
<script src="scripts/permission-iframe-request.js"></script>
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>VibeSurf - Microphone Permission</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
10
|
+
display: flex;
|
|
11
|
+
justify-content: center;
|
|
12
|
+
align-items: center;
|
|
13
|
+
min-height: 100vh;
|
|
14
|
+
margin: 0;
|
|
15
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
16
|
+
color: #333;
|
|
17
|
+
}
|
|
18
|
+
.container {
|
|
19
|
+
background: white;
|
|
20
|
+
padding: 40px;
|
|
21
|
+
border-radius: 12px;
|
|
22
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
|
23
|
+
text-align: center;
|
|
24
|
+
max-width: 400px;
|
|
25
|
+
}
|
|
26
|
+
.logo {
|
|
27
|
+
width: 60px;
|
|
28
|
+
height: 60px;
|
|
29
|
+
margin-bottom: 20px;
|
|
30
|
+
}
|
|
31
|
+
h1 {
|
|
32
|
+
color: #2d3748;
|
|
33
|
+
margin-bottom: 10px;
|
|
34
|
+
font-size: 24px;
|
|
35
|
+
}
|
|
36
|
+
p {
|
|
37
|
+
color: #718096;
|
|
38
|
+
margin-bottom: 30px;
|
|
39
|
+
line-height: 1.6;
|
|
40
|
+
}
|
|
41
|
+
.button-group {
|
|
42
|
+
display: flex;
|
|
43
|
+
gap: 12px;
|
|
44
|
+
justify-content: center;
|
|
45
|
+
}
|
|
46
|
+
button {
|
|
47
|
+
padding: 12px 24px;
|
|
48
|
+
border: none;
|
|
49
|
+
border-radius: 6px;
|
|
50
|
+
font-size: 16px;
|
|
51
|
+
font-weight: 500;
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
transition: all 0.2s;
|
|
54
|
+
}
|
|
55
|
+
.allow-btn {
|
|
56
|
+
background: #4299e1;
|
|
57
|
+
color: white;
|
|
58
|
+
}
|
|
59
|
+
.allow-btn:hover {
|
|
60
|
+
background: #3182ce;
|
|
61
|
+
transform: translateY(-1px);
|
|
62
|
+
}
|
|
63
|
+
.deny-btn {
|
|
64
|
+
background: #e2e8f0;
|
|
65
|
+
color: #4a5568;
|
|
66
|
+
}
|
|
67
|
+
.deny-btn:hover {
|
|
68
|
+
background: #cbd5e0;
|
|
69
|
+
}
|
|
70
|
+
#status {
|
|
71
|
+
margin-top: 20px;
|
|
72
|
+
padding: 12px;
|
|
73
|
+
border-radius: 6px;
|
|
74
|
+
font-weight: 500;
|
|
75
|
+
}
|
|
76
|
+
.success {
|
|
77
|
+
background: #c6f6d5;
|
|
78
|
+
color: #22543d;
|
|
79
|
+
}
|
|
80
|
+
.error {
|
|
81
|
+
background: #fed7d7;
|
|
82
|
+
color: #742a2a;
|
|
83
|
+
}
|
|
84
|
+
.loading {
|
|
85
|
+
background: #bee3f8;
|
|
86
|
+
color: #2a4365;
|
|
87
|
+
}
|
|
88
|
+
</style>
|
|
89
|
+
</head>
|
|
90
|
+
<body>
|
|
91
|
+
<div class="container">
|
|
92
|
+
<img src="icons/logo.png" alt="VibeSurf" class="logo">
|
|
93
|
+
<h1>Microphone Permission Required</h1>
|
|
94
|
+
<p>VibeSurf needs access to your microphone to enable voice input. This permission is used only when you click the microphone button.</p>
|
|
95
|
+
<div class="button-group">
|
|
96
|
+
<button id="allowBtn" class="allow-btn">Allow Microphone</button>
|
|
97
|
+
<button id="denyBtn" class="deny-btn">Deny</button>
|
|
98
|
+
</div>
|
|
99
|
+
<div id="status"></div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<script src="scripts/permission-request.js"></script>
|
|
103
|
+
</body>
|
|
104
|
+
</html>
|
|
@@ -410,6 +410,67 @@ class VibeSurfAPIClient {
|
|
|
410
410
|
return this.get(`/config/llm/providers/${encodeURIComponent(providerName)}/models`);
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
+
// Voice Profile Management
|
|
414
|
+
async getVoiceProfiles(activeOnly = true, voiceModelType = null, limit = 50, offset = 0) {
|
|
415
|
+
const params = { active_only: activeOnly, limit, offset };
|
|
416
|
+
if (voiceModelType) {
|
|
417
|
+
params.voice_model_type = voiceModelType;
|
|
418
|
+
}
|
|
419
|
+
return this.get('/voices/voice-profiles', { params });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async getVoiceProfile(profileName) {
|
|
423
|
+
return this.get(`/voices/${encodeURIComponent(profileName)}`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async createVoiceProfile(profileData) {
|
|
427
|
+
return this.post('/voices/voice-profiles', profileData);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async updateVoiceProfile(profileName, updateData) {
|
|
431
|
+
return this.put(`/voices/voice-profiles/${encodeURIComponent(profileName)}`, updateData);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async deleteVoiceProfile(profileName) {
|
|
435
|
+
return this.delete(`/voices/voice-profiles/${encodeURIComponent(profileName)}`);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Voice Models - matches the backend route @router.get("/models")
|
|
439
|
+
async getVoiceModels(modelType = null) {
|
|
440
|
+
let url = '/voices/models';
|
|
441
|
+
if (modelType) {
|
|
442
|
+
url += `?model_type=${encodeURIComponent(modelType)}`;
|
|
443
|
+
}
|
|
444
|
+
return this.get(url);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Voice Recording API
|
|
448
|
+
async transcribeAudio(audioBlob, voiceProfileName = null) {
|
|
449
|
+
const formData = new FormData();
|
|
450
|
+
formData.append('audio_file', audioBlob, 'recording.webm');
|
|
451
|
+
|
|
452
|
+
// Add voice profile name if provided
|
|
453
|
+
const params = {};
|
|
454
|
+
if (voiceProfileName) {
|
|
455
|
+
params.voice_profile_name = voiceProfileName;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return this.post('/voices/asr', formData, {
|
|
459
|
+
params,
|
|
460
|
+
headers: {} // Let browser set Content-Type with boundary for FormData
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Get available ASR profiles
|
|
465
|
+
async getASRProfiles(activeOnly = true) {
|
|
466
|
+
return this.get('/voices/voice-profiles', {
|
|
467
|
+
params: {
|
|
468
|
+
voice_model_type: 'asr',
|
|
469
|
+
active_only: activeOnly
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
413
474
|
// Environment Variables
|
|
414
475
|
async getEnvironmentVariables() {
|
|
415
476
|
return this.get('/config/environments');
|
|
@@ -275,14 +275,28 @@ class VibeSurfFileManager {
|
|
|
275
275
|
async handleFileLink(filePath) {
|
|
276
276
|
// Prevent multiple simultaneous calls
|
|
277
277
|
if (this.state.isHandlingFileLink) {
|
|
278
|
+
console.log('[FileManager] File link handling already in progress, skipping...');
|
|
278
279
|
return;
|
|
279
280
|
}
|
|
280
281
|
|
|
281
282
|
this.state.isHandlingFileLink = true;
|
|
282
283
|
|
|
283
284
|
try {
|
|
284
|
-
|
|
285
|
-
|
|
285
|
+
console.log('[FileManager] Handling file link:', filePath);
|
|
286
|
+
|
|
287
|
+
// Validate input
|
|
288
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
289
|
+
throw new Error('Invalid file path provided');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// First decode the URL-encoded path safely
|
|
293
|
+
let decodedPath;
|
|
294
|
+
try {
|
|
295
|
+
decodedPath = decodeURIComponent(filePath);
|
|
296
|
+
} catch (decodeError) {
|
|
297
|
+
console.warn('[FileManager] Failed to decode URL, using original path:', decodeError);
|
|
298
|
+
decodedPath = filePath;
|
|
299
|
+
}
|
|
286
300
|
|
|
287
301
|
// Remove file:// protocol prefix and normalize
|
|
288
302
|
let cleanPath = decodedPath.replace(/^file:\/\/\//, '').replace(/^file:\/\//, '');
|
|
@@ -300,21 +314,28 @@ class VibeSurfFileManager {
|
|
|
300
314
|
`file:///${cleanPath}` :
|
|
301
315
|
`file:///${cleanPath.replace(/^\//, '')}`; // Remove leading slash and add triple slash
|
|
302
316
|
|
|
317
|
+
console.log('[FileManager] Processed file URL:', fileUrl);
|
|
318
|
+
|
|
303
319
|
// Show user notification about the action
|
|
304
320
|
this.emit('notification', {
|
|
305
321
|
message: `Opening file: ${cleanPath}`,
|
|
306
322
|
type: 'info'
|
|
307
323
|
});
|
|
308
324
|
|
|
309
|
-
// Use setTimeout to prevent UI blocking
|
|
325
|
+
// Use setTimeout to prevent UI blocking and ensure proper cleanup
|
|
310
326
|
setTimeout(async () => {
|
|
311
327
|
try {
|
|
312
328
|
// For user-clicked file links, use OPEN_FILE_URL to keep tab open
|
|
313
329
|
// This prevents the auto-close behavior in OPEN_FILE_SYSTEM
|
|
314
|
-
const fileOpenResponse = await
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
330
|
+
const fileOpenResponse = await Promise.race([
|
|
331
|
+
chrome.runtime.sendMessage({
|
|
332
|
+
type: 'OPEN_FILE_URL',
|
|
333
|
+
data: { fileUrl: fileUrl }
|
|
334
|
+
}),
|
|
335
|
+
new Promise((_, reject) =>
|
|
336
|
+
setTimeout(() => reject(new Error('File open timeout')), 5000)
|
|
337
|
+
)
|
|
338
|
+
]);
|
|
318
339
|
|
|
319
340
|
if (fileOpenResponse && fileOpenResponse.success) {
|
|
320
341
|
this.emit('notification', {
|
|
@@ -322,12 +343,14 @@ class VibeSurfFileManager {
|
|
|
322
343
|
type: 'success'
|
|
323
344
|
});
|
|
324
345
|
return;
|
|
346
|
+
} else if (fileOpenResponse && fileOpenResponse.error) {
|
|
347
|
+
console.warn('[FileManager] Background script file open failed:', fileOpenResponse.error);
|
|
325
348
|
}
|
|
326
349
|
|
|
327
|
-
// If OPEN_FILE_URL fails, try direct browser open
|
|
350
|
+
// If OPEN_FILE_URL fails, try direct browser open with additional safety
|
|
328
351
|
try {
|
|
329
352
|
const opened = window.open(fileUrl, '_blank', 'noopener,noreferrer');
|
|
330
|
-
if (opened) {
|
|
353
|
+
if (opened && !opened.closed) {
|
|
331
354
|
this.emit('notification', {
|
|
332
355
|
message: 'File opened in browser',
|
|
333
356
|
type: 'success'
|
|
@@ -339,14 +362,32 @@ class VibeSurfFileManager {
|
|
|
339
362
|
}
|
|
340
363
|
|
|
341
364
|
// Last resort: Copy path to clipboard
|
|
342
|
-
this.copyToClipboardFallback(fileUrl);
|
|
365
|
+
await this.copyToClipboardFallback(fileUrl);
|
|
343
366
|
|
|
344
367
|
} catch (error) {
|
|
345
368
|
console.error('[FileManager] Error in async file handling:', error);
|
|
369
|
+
|
|
370
|
+
// Provide more helpful error messages
|
|
371
|
+
let userMessage = 'Unable to open file';
|
|
372
|
+
if (error.message.includes('timeout')) {
|
|
373
|
+
userMessage = 'File open operation timed out. Try copying the path manually.';
|
|
374
|
+
} else if (error.message.includes('protocol')) {
|
|
375
|
+
userMessage = 'Browser security restricts opening local files. File path copied to clipboard.';
|
|
376
|
+
} else {
|
|
377
|
+
userMessage = `Unable to open file: ${error.message}`;
|
|
378
|
+
}
|
|
379
|
+
|
|
346
380
|
this.emit('notification', {
|
|
347
|
-
message:
|
|
381
|
+
message: userMessage,
|
|
348
382
|
type: 'error'
|
|
349
383
|
});
|
|
384
|
+
|
|
385
|
+
// Fallback to clipboard
|
|
386
|
+
try {
|
|
387
|
+
await this.copyToClipboardFallback(fileUrl);
|
|
388
|
+
} catch (clipboardError) {
|
|
389
|
+
console.error('[FileManager] Clipboard fallback also failed:', clipboardError);
|
|
390
|
+
}
|
|
350
391
|
} finally {
|
|
351
392
|
this.state.isHandlingFileLink = false;
|
|
352
393
|
}
|
|
@@ -355,7 +396,7 @@ class VibeSurfFileManager {
|
|
|
355
396
|
} catch (error) {
|
|
356
397
|
console.error('[FileManager] Error handling file link:', error);
|
|
357
398
|
this.emit('notification', {
|
|
358
|
-
message: `
|
|
399
|
+
message: `File link processing failed: ${error.message}`,
|
|
359
400
|
type: 'error'
|
|
360
401
|
});
|
|
361
402
|
this.state.isHandlingFileLink = false;
|
|
@@ -154,11 +154,11 @@ class VibeSurfApp {
|
|
|
154
154
|
|
|
155
155
|
async initializeUIManager() {
|
|
156
156
|
this.uiManager = new VibeSurfUIManager(this.sessionManager, this.apiClient);
|
|
157
|
-
|
|
157
|
+
|
|
158
158
|
// Initialize UI with loaded data
|
|
159
159
|
await this.uiManager.initialize();
|
|
160
160
|
|
|
161
|
-
console.log('[VibeSurf] UI manager initialized');
|
|
161
|
+
console.log('[VibeSurf] UI manager initialized successfully');
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
setupErrorHandling() {
|
|
@@ -189,6 +189,12 @@ class VibeSurfApp {
|
|
|
189
189
|
// Periodic backend health check
|
|
190
190
|
setInterval(async () => {
|
|
191
191
|
try {
|
|
192
|
+
// Check if apiClient exists and is initialized
|
|
193
|
+
if (!this.apiClient || typeof this.apiClient.healthCheck !== 'function') {
|
|
194
|
+
console.warn('[VibeSurf] Health check skipped - API client not available');
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
192
198
|
const healthCheck = await this.apiClient.healthCheck();
|
|
193
199
|
|
|
194
200
|
if (healthCheck.status === 'healthy') {
|
|
@@ -413,18 +419,37 @@ class VibeSurfApp {
|
|
|
413
419
|
|
|
414
420
|
// Cleanup method
|
|
415
421
|
destroy() {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
422
|
+
// Prevent multiple cleanup calls
|
|
423
|
+
if (this.isDestroying || !this.isInitialized) {
|
|
424
|
+
console.log('[VibeSurf] Cleanup already in progress or app not initialized, skipping...');
|
|
425
|
+
return;
|
|
420
426
|
}
|
|
421
427
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
428
|
+
this.isDestroying = true;
|
|
429
|
+
console.log('[VibeSurf] Cleaning up application...');
|
|
425
430
|
|
|
426
|
-
|
|
427
|
-
|
|
431
|
+
try {
|
|
432
|
+
if (this.uiManager) {
|
|
433
|
+
this.uiManager.destroy();
|
|
434
|
+
this.uiManager = null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (this.sessionManager) {
|
|
438
|
+
this.sessionManager.destroy();
|
|
439
|
+
this.sessionManager = null;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (this.apiClient) {
|
|
443
|
+
this.apiClient = null;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
this.isInitialized = false;
|
|
447
|
+
console.log('[VibeSurf] Application cleanup complete');
|
|
448
|
+
} catch (error) {
|
|
449
|
+
console.error('[VibeSurf] Error during cleanup:', error);
|
|
450
|
+
} finally {
|
|
451
|
+
this.isDestroying = false;
|
|
452
|
+
}
|
|
428
453
|
}
|
|
429
454
|
|
|
430
455
|
// Get application status
|
|
@@ -456,11 +481,21 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
456
481
|
|
|
457
482
|
// Handle page unload
|
|
458
483
|
window.addEventListener('beforeunload', () => {
|
|
459
|
-
if (window.vibeSurfApp) {
|
|
484
|
+
if (window.vibeSurfApp && window.vibeSurfApp.isInitialized && !window.vibeSurfApp.isDestroying) {
|
|
485
|
+
console.log('[VibeSurf] Page unloading, cleaning up...');
|
|
460
486
|
window.vibeSurfApp.destroy();
|
|
461
487
|
}
|
|
462
488
|
});
|
|
463
489
|
|
|
490
|
+
// Handle visibility change to prevent unnecessary cleanup
|
|
491
|
+
document.addEventListener('visibilitychange', () => {
|
|
492
|
+
if (document.visibilityState === 'hidden') {
|
|
493
|
+
console.log('[VibeSurf] Page hidden, but not cleaning up (might be tab switch)');
|
|
494
|
+
} else if (document.visibilityState === 'visible') {
|
|
495
|
+
console.log('[VibeSurf] Page visible again');
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
464
499
|
// Make app accessible for debugging
|
|
465
500
|
if (typeof window !== 'undefined') {
|
|
466
501
|
window.VibeSurfApp = VibeSurfApp;
|
|
@@ -482,6 +517,12 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
482
517
|
.catch(error => sendResponse({ success: false, error: error.message }));
|
|
483
518
|
return true; // Keep message channel open for async response
|
|
484
519
|
|
|
520
|
+
case 'MICROPHONE_PERMISSION_RESULT':
|
|
521
|
+
console.log('[VibeSurf] Received microphone permission result:', message);
|
|
522
|
+
// This message is typically handled by voice recorder, just acknowledge
|
|
523
|
+
sendResponse({ acknowledged: true });
|
|
524
|
+
break;
|
|
525
|
+
|
|
485
526
|
default:
|
|
486
527
|
console.warn('[VibeSurf] Unknown message type:', message.type);
|
|
487
528
|
}
|