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
|
@@ -8,7 +8,7 @@ from typing import List, Optional, Dict, Any
|
|
|
8
8
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
9
9
|
from sqlalchemy import select, update, delete, func, desc, and_, or_
|
|
10
10
|
from sqlalchemy.orm import selectinload
|
|
11
|
-
from .models import Task, TaskStatus, LLMProfile, UploadedFile, McpProfile
|
|
11
|
+
from .models import Task, TaskStatus, LLMProfile, UploadedFile, McpProfile, VoiceProfile, VoiceModelType
|
|
12
12
|
from ..utils.encryption import encrypt_api_key, decrypt_api_key
|
|
13
13
|
import logging
|
|
14
14
|
import json
|
|
@@ -929,3 +929,191 @@ class UploadedFileQueries:
|
|
|
929
929
|
except Exception as e:
|
|
930
930
|
logger.error(f"Failed to cleanup deleted files: {e}")
|
|
931
931
|
raise
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
class VoiceProfileQueries:
|
|
935
|
+
"""Query operations for VoiceProfile model"""
|
|
936
|
+
|
|
937
|
+
@staticmethod
|
|
938
|
+
async def create_profile(
|
|
939
|
+
db: AsyncSession,
|
|
940
|
+
voice_profile_name: str,
|
|
941
|
+
voice_model_type: str,
|
|
942
|
+
voice_model_name: str,
|
|
943
|
+
api_key: Optional[str] = None,
|
|
944
|
+
voice_meta_params: Optional[Dict[str, Any]] = None,
|
|
945
|
+
description: Optional[str] = None
|
|
946
|
+
) -> Dict[str, Any]:
|
|
947
|
+
"""Create a new Voice profile with encrypted API key"""
|
|
948
|
+
try:
|
|
949
|
+
# Encrypt API key if provided
|
|
950
|
+
encrypted_api_key = encrypt_api_key(api_key) if api_key else None
|
|
951
|
+
|
|
952
|
+
profile = VoiceProfile(
|
|
953
|
+
voice_profile_name=voice_profile_name,
|
|
954
|
+
voice_model_type=VoiceModelType(voice_model_type),
|
|
955
|
+
voice_model_name=voice_model_name,
|
|
956
|
+
encrypted_api_key=encrypted_api_key,
|
|
957
|
+
voice_meta_params=voice_meta_params or {},
|
|
958
|
+
description=description
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
db.add(profile)
|
|
962
|
+
await db.flush()
|
|
963
|
+
await db.refresh(profile)
|
|
964
|
+
|
|
965
|
+
# Extract data immediately to avoid greenlet issues
|
|
966
|
+
profile_data = {
|
|
967
|
+
"profile_id": profile.profile_id,
|
|
968
|
+
"voice_profile_name": profile.voice_profile_name,
|
|
969
|
+
"voice_model_type": profile.voice_model_type.value,
|
|
970
|
+
"voice_model_name": profile.voice_model_name,
|
|
971
|
+
"voice_meta_params": profile.voice_meta_params,
|
|
972
|
+
"description": profile.description,
|
|
973
|
+
"is_active": profile.is_active,
|
|
974
|
+
"created_at": profile.created_at,
|
|
975
|
+
"updated_at": profile.updated_at,
|
|
976
|
+
"last_used_at": profile.last_used_at
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
return profile_data
|
|
980
|
+
except Exception as e:
|
|
981
|
+
logger.error(f"Failed to create Voice profile {voice_profile_name}: {e}")
|
|
982
|
+
raise
|
|
983
|
+
|
|
984
|
+
@staticmethod
|
|
985
|
+
async def get_profile(db: AsyncSession, voice_profile_name: str) -> Optional[VoiceProfile]:
|
|
986
|
+
"""Get Voice profile by name"""
|
|
987
|
+
try:
|
|
988
|
+
result = await db.execute(
|
|
989
|
+
select(VoiceProfile).where(VoiceProfile.voice_profile_name == voice_profile_name)
|
|
990
|
+
)
|
|
991
|
+
profile = result.scalar_one_or_none()
|
|
992
|
+
if profile:
|
|
993
|
+
# Ensure all attributes are loaded by accessing them
|
|
994
|
+
_ = (profile.profile_id, profile.created_at, profile.updated_at,
|
|
995
|
+
profile.last_used_at, profile.is_active)
|
|
996
|
+
return profile
|
|
997
|
+
except Exception as e:
|
|
998
|
+
logger.error(f"Failed to get Voice profile {voice_profile_name}: {e}")
|
|
999
|
+
raise
|
|
1000
|
+
|
|
1001
|
+
@staticmethod
|
|
1002
|
+
async def get_profile_with_decrypted_key(db: AsyncSession, voice_profile_name: str) -> Optional[Dict[str, Any]]:
|
|
1003
|
+
"""Get Voice profile with decrypted API key"""
|
|
1004
|
+
try:
|
|
1005
|
+
profile = await VoiceProfileQueries.get_profile(db, voice_profile_name)
|
|
1006
|
+
if not profile:
|
|
1007
|
+
return None
|
|
1008
|
+
|
|
1009
|
+
# Decrypt API key
|
|
1010
|
+
decrypted_api_key = decrypt_api_key(profile.encrypted_api_key) if profile.encrypted_api_key else None
|
|
1011
|
+
|
|
1012
|
+
return {
|
|
1013
|
+
"profile_id": profile.profile_id,
|
|
1014
|
+
"voice_profile_name": profile.voice_profile_name,
|
|
1015
|
+
"voice_model_type": profile.voice_model_type.value,
|
|
1016
|
+
"voice_model_name": profile.voice_model_name,
|
|
1017
|
+
"api_key": decrypted_api_key, # Decrypted for use
|
|
1018
|
+
"voice_meta_params": profile.voice_meta_params,
|
|
1019
|
+
"description": profile.description,
|
|
1020
|
+
"is_active": profile.is_active,
|
|
1021
|
+
"created_at": profile.created_at,
|
|
1022
|
+
"updated_at": profile.updated_at,
|
|
1023
|
+
"last_used_at": profile.last_used_at
|
|
1024
|
+
}
|
|
1025
|
+
except Exception as e:
|
|
1026
|
+
logger.error(f"Failed to get Voice profile with decrypted key {voice_profile_name}: {e}")
|
|
1027
|
+
raise
|
|
1028
|
+
|
|
1029
|
+
@staticmethod
|
|
1030
|
+
async def list_profiles(
|
|
1031
|
+
db: AsyncSession,
|
|
1032
|
+
voice_model_type: Optional[str] = None,
|
|
1033
|
+
active_only: bool = True,
|
|
1034
|
+
limit: int = 50,
|
|
1035
|
+
offset: int = 0
|
|
1036
|
+
) -> List[VoiceProfile]:
|
|
1037
|
+
"""List Voice profiles"""
|
|
1038
|
+
try:
|
|
1039
|
+
query = select(VoiceProfile)
|
|
1040
|
+
|
|
1041
|
+
if active_only:
|
|
1042
|
+
query = query.where(VoiceProfile.is_active == True)
|
|
1043
|
+
|
|
1044
|
+
if voice_model_type:
|
|
1045
|
+
query = query.where(VoiceProfile.voice_model_type == VoiceModelType(voice_model_type))
|
|
1046
|
+
|
|
1047
|
+
query = query.order_by(desc(VoiceProfile.last_used_at), desc(VoiceProfile.created_at))
|
|
1048
|
+
query = query.limit(limit).offset(offset)
|
|
1049
|
+
|
|
1050
|
+
result = await db.execute(query)
|
|
1051
|
+
profiles = result.scalars().all()
|
|
1052
|
+
|
|
1053
|
+
# Ensure all attributes are loaded for each profile
|
|
1054
|
+
for profile in profiles:
|
|
1055
|
+
_ = (profile.profile_id, profile.created_at, profile.updated_at,
|
|
1056
|
+
profile.last_used_at, profile.is_active)
|
|
1057
|
+
|
|
1058
|
+
return profiles
|
|
1059
|
+
except Exception as e:
|
|
1060
|
+
logger.error(f"Failed to list Voice profiles: {e}")
|
|
1061
|
+
raise
|
|
1062
|
+
|
|
1063
|
+
@staticmethod
|
|
1064
|
+
async def update_profile(
|
|
1065
|
+
db: AsyncSession,
|
|
1066
|
+
voice_profile_name: str,
|
|
1067
|
+
updates: Dict[str, Any]
|
|
1068
|
+
) -> bool:
|
|
1069
|
+
"""Update Voice profile"""
|
|
1070
|
+
try:
|
|
1071
|
+
# Handle API key encryption if present
|
|
1072
|
+
if "api_key" in updates:
|
|
1073
|
+
api_key = updates.pop("api_key")
|
|
1074
|
+
if api_key:
|
|
1075
|
+
updates["encrypted_api_key"] = encrypt_api_key(api_key)
|
|
1076
|
+
else:
|
|
1077
|
+
updates["encrypted_api_key"] = None
|
|
1078
|
+
|
|
1079
|
+
# Handle voice_model_type enum conversion
|
|
1080
|
+
if "voice_model_type" in updates:
|
|
1081
|
+
updates["voice_model_type"] = VoiceModelType(updates["voice_model_type"])
|
|
1082
|
+
|
|
1083
|
+
result = await db.execute(
|
|
1084
|
+
update(VoiceProfile)
|
|
1085
|
+
.where(VoiceProfile.voice_profile_name == voice_profile_name)
|
|
1086
|
+
.values(**updates)
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
return result.rowcount > 0
|
|
1090
|
+
except Exception as e:
|
|
1091
|
+
logger.error(f"Failed to update Voice profile {voice_profile_name}: {e}")
|
|
1092
|
+
raise
|
|
1093
|
+
|
|
1094
|
+
@staticmethod
|
|
1095
|
+
async def delete_profile(db: AsyncSession, voice_profile_name: str) -> bool:
|
|
1096
|
+
"""Delete Voice profile"""
|
|
1097
|
+
try:
|
|
1098
|
+
result = await db.execute(
|
|
1099
|
+
delete(VoiceProfile).where(VoiceProfile.voice_profile_name == voice_profile_name)
|
|
1100
|
+
)
|
|
1101
|
+
return result.rowcount > 0
|
|
1102
|
+
except Exception as e:
|
|
1103
|
+
logger.error(f"Failed to delete Voice profile {voice_profile_name}: {e}")
|
|
1104
|
+
raise
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
@staticmethod
|
|
1108
|
+
async def update_last_used(db: AsyncSession, voice_profile_name: str) -> bool:
|
|
1109
|
+
"""Update the last_used_at timestamp for a profile"""
|
|
1110
|
+
try:
|
|
1111
|
+
result = await db.execute(
|
|
1112
|
+
update(VoiceProfile)
|
|
1113
|
+
.where(VoiceProfile.voice_profile_name == voice_profile_name)
|
|
1114
|
+
.values(last_used_at=func.now())
|
|
1115
|
+
)
|
|
1116
|
+
return result.rowcount > 0
|
|
1117
|
+
except Exception as e:
|
|
1118
|
+
logger.error(f"Failed to update last_used for Voice profile {voice_profile_name}: {e}")
|
|
1119
|
+
raise
|
vibe_surf/backend/main.py
CHANGED
|
@@ -20,6 +20,7 @@ from .api.files import router as files_router
|
|
|
20
20
|
from .api.activity import router as activity_router
|
|
21
21
|
from .api.config import router as config_router
|
|
22
22
|
from .api.browser import router as browser_router
|
|
23
|
+
from .api.voices import router as voices_router
|
|
23
24
|
|
|
24
25
|
# Import shared state
|
|
25
26
|
from . import shared_state
|
|
@@ -51,6 +52,7 @@ app.include_router(files_router, prefix="/api", tags=["files"])
|
|
|
51
52
|
app.include_router(activity_router, prefix="/api", tags=["activity"])
|
|
52
53
|
app.include_router(config_router, prefix="/api", tags=["config"])
|
|
53
54
|
app.include_router(browser_router, prefix="/api", tags=["browser"])
|
|
55
|
+
app.include_router(voices_router, prefix="/api", tags=["voices"])
|
|
54
56
|
|
|
55
57
|
# Global variable to control browser monitoring task
|
|
56
58
|
browser_monitor_task = None
|
|
@@ -174,7 +174,7 @@ async def execute_task_background(
|
|
|
174
174
|
db_session,
|
|
175
175
|
task_id=task_id,
|
|
176
176
|
task_result=result,
|
|
177
|
-
task_status=active_task.get("status", "completed"),
|
|
177
|
+
task_status=active_task.get("status", "completed") if active_task else "completed",
|
|
178
178
|
report_path=report_path
|
|
179
179
|
)
|
|
180
180
|
await db_session.commit()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Voice Model Configuration
|
|
3
|
+
|
|
4
|
+
Centralized configuration for all supported voice providers and their models.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Voice Providers and their supported models
|
|
8
|
+
VOICE_MODELS = {
|
|
9
|
+
"qwen-asr": {
|
|
10
|
+
"model_type": "asr",
|
|
11
|
+
"requires_api_key": True,
|
|
12
|
+
"provider": "qwen",
|
|
13
|
+
},
|
|
14
|
+
"openai-asr": {
|
|
15
|
+
"model_type": "asr",
|
|
16
|
+
"requires_api_key": True,
|
|
17
|
+
"provider": "openai",
|
|
18
|
+
"supports_base_url": True,
|
|
19
|
+
},
|
|
20
|
+
"gemini-asr": {
|
|
21
|
+
"model_type": "asr",
|
|
22
|
+
"requires_api_key": True,
|
|
23
|
+
"provider": "gemini",
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -17,6 +17,8 @@ from browser_use.observability import observe_debug
|
|
|
17
17
|
from browser_use.utils import _log_pretty_path, logger
|
|
18
18
|
|
|
19
19
|
from browser_use.browser import BrowserProfile
|
|
20
|
+
from browser_use.browser.profile import CHROME_DEFAULT_ARGS, CHROME_DOCKER_ARGS, CHROME_HEADLESS_ARGS, \
|
|
21
|
+
CHROME_DETERMINISTIC_RENDERING_ARGS, CHROME_DISABLE_SECURITY_ARGS, BrowserLaunchArgs
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
class AgentBrowserProfile(BrowserProfile):
|
|
@@ -43,6 +43,7 @@ from browser_use.browser.events import (
|
|
|
43
43
|
TabClosedEvent,
|
|
44
44
|
TabCreatedEvent,
|
|
45
45
|
)
|
|
46
|
+
from browser_use.browser.profile import BrowserProfile, ProxySettings
|
|
46
47
|
|
|
47
48
|
DEFAULT_BROWSER_PROFILE = AgentBrowserProfile()
|
|
48
49
|
|
|
@@ -94,7 +95,7 @@ class AgentBrowserSession(BrowserSession):
|
|
|
94
95
|
deterministic_rendering: bool | None = None,
|
|
95
96
|
allowed_domains: list[str] | None = None,
|
|
96
97
|
keep_alive: bool | None = None,
|
|
97
|
-
proxy:
|
|
98
|
+
proxy: ProxySettings | None = None,
|
|
98
99
|
enable_default_extensions: bool | None = None,
|
|
99
100
|
window_size: dict | None = None,
|
|
100
101
|
window_position: dict | None = None,
|
|
@@ -428,9 +429,9 @@ class AgentBrowserSession(BrowserSession):
|
|
|
428
429
|
self._popups_watchdog.attach_to_session()
|
|
429
430
|
|
|
430
431
|
# Initialize PermissionsWatchdog (handles granting and revoking browser permissions like clipboard, microphone, camera, etc.)
|
|
431
|
-
PermissionsWatchdog.model_rebuild()
|
|
432
|
-
self._permissions_watchdog = PermissionsWatchdog(event_bus=self.event_bus, browser_session=self)
|
|
433
|
-
self._permissions_watchdog.attach_to_session()
|
|
432
|
+
# PermissionsWatchdog.model_rebuild()
|
|
433
|
+
# self._permissions_watchdog = PermissionsWatchdog(event_bus=self.event_bus, browser_session=self)
|
|
434
|
+
# self._permissions_watchdog.attach_to_session()
|
|
434
435
|
|
|
435
436
|
# Initialize DefaultActionWatchdog (handles all default actions like click, type, scroll, go back, go forward, refresh, wait, send keys, upload file, scroll to text, etc.)
|
|
436
437
|
CustomActionWatchdog.model_rebuild()
|
|
@@ -135,12 +135,17 @@ class VibeSurfBackground {
|
|
|
135
135
|
console.error('[VibeSurf] Failed to open side panel:', error);
|
|
136
136
|
|
|
137
137
|
// Show notification with helpful message
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
try {
|
|
139
|
+
await chrome.notifications.create({
|
|
140
|
+
type: 'basic',
|
|
141
|
+
iconUrl: '', // Use empty string to avoid icon issues
|
|
142
|
+
title: 'VibeSurf',
|
|
143
|
+
message: 'Side panel failed. Please update Chrome to the latest version or try right-clicking the extension icon.'
|
|
144
|
+
});
|
|
145
|
+
} catch (notifError) {
|
|
146
|
+
console.warn('[VibeSurf] Notification failed:', notifError);
|
|
147
|
+
// Don't throw, just log the warning
|
|
148
|
+
}
|
|
144
149
|
|
|
145
150
|
// Fallback: try to open in new tab
|
|
146
151
|
try {
|
|
@@ -155,12 +160,14 @@ class VibeSurfBackground {
|
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
handleMessage(message, sender, sendResponse) {
|
|
163
|
+
console.log('[VibeSurf] Received message:', message.type);
|
|
158
164
|
|
|
159
165
|
// Handle async messages properly
|
|
160
166
|
(async () => {
|
|
161
167
|
try {
|
|
162
168
|
let result;
|
|
163
169
|
|
|
170
|
+
console.log('[VibeSurf] Processing message type:', message.type);
|
|
164
171
|
switch (message.type) {
|
|
165
172
|
case 'GET_CURRENT_TAB':
|
|
166
173
|
result = await this.getCurrentTabInfo();
|
|
@@ -206,9 +213,56 @@ class VibeSurfBackground {
|
|
|
206
213
|
result = await this.getAllTabs();
|
|
207
214
|
break;
|
|
208
215
|
|
|
216
|
+
case 'REQUEST_MICROPHONE_PERMISSION':
|
|
217
|
+
result = await this.requestMicrophonePermission();
|
|
218
|
+
break;
|
|
219
|
+
|
|
220
|
+
case 'REQUEST_MICROPHONE_PERMISSION_WITH_UI':
|
|
221
|
+
console.log('[VibeSurf] Handling REQUEST_MICROPHONE_PERMISSION_WITH_UI');
|
|
222
|
+
result = await this.requestMicrophonePermissionWithUI();
|
|
223
|
+
break;
|
|
224
|
+
|
|
225
|
+
case 'MICROPHONE_PERMISSION_RESULT':
|
|
226
|
+
console.log('[VibeSurf] Received MICROPHONE_PERMISSION_RESULT:', message);
|
|
227
|
+
console.log('[VibeSurf] Permission granted:', message.granted);
|
|
228
|
+
console.log('[VibeSurf] Permission error:', message.error);
|
|
229
|
+
|
|
230
|
+
// Handle permission result from URL parameter approach
|
|
231
|
+
if (message.granted !== undefined) {
|
|
232
|
+
console.log('[VibeSurf] Processing permission result with granted:', message.granted);
|
|
233
|
+
|
|
234
|
+
// Store the result for the original tab to retrieve
|
|
235
|
+
chrome.storage.local.set({
|
|
236
|
+
microphonePermissionResult: {
|
|
237
|
+
granted: message.granted,
|
|
238
|
+
error: message.error,
|
|
239
|
+
timestamp: Date.now()
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Also send to any waiting listeners
|
|
244
|
+
console.log('[VibeSurf] Broadcasting permission result to all tabs...');
|
|
245
|
+
chrome.runtime.sendMessage({
|
|
246
|
+
type: 'MICROPHONE_PERMISSION_RESULT',
|
|
247
|
+
granted: message.granted,
|
|
248
|
+
error: message.error
|
|
249
|
+
}).then(() => {
|
|
250
|
+
console.log('[VibeSurf] Permission result broadcast successful');
|
|
251
|
+
}).catch((err) => {
|
|
252
|
+
console.log('[VibeSurf] Permission result broadcast failed (no listeners):', err);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
result = { acknowledged: true };
|
|
256
|
+
break;
|
|
257
|
+
|
|
209
258
|
default:
|
|
210
|
-
console.warn('[VibeSurf] Unknown message type:', message.type
|
|
211
|
-
|
|
259
|
+
console.warn('[VibeSurf] Unknown message type:', message.type, 'Available handlers:', [
|
|
260
|
+
'GET_CURRENT_TAB', 'UPDATE_BADGE', 'SHOW_NOTIFICATION', 'COPY_TO_CLIPBOARD',
|
|
261
|
+
'HEALTH_CHECK', 'GET_BACKEND_STATUS', 'STORE_SESSION_DATA', 'GET_SESSION_DATA',
|
|
262
|
+
'OPEN_FILE_URL', 'OPEN_FILE_SYSTEM', 'GET_ALL_TABS', 'REQUEST_MICROPHONE_PERMISSION',
|
|
263
|
+
'REQUEST_MICROPHONE_PERMISSION_WITH_UI', 'MICROPHONE_PERMISSION_RESULT'
|
|
264
|
+
]);
|
|
265
|
+
result = { error: 'Unknown message type', receivedType: message.type };
|
|
212
266
|
}
|
|
213
267
|
|
|
214
268
|
sendResponse(result);
|
|
@@ -316,28 +370,63 @@ class VibeSurfBackground {
|
|
|
316
370
|
}
|
|
317
371
|
|
|
318
372
|
async showNotification(data) {
|
|
319
|
-
const { title, message, type = 'info', iconUrl
|
|
373
|
+
const { title, message, type = 'info', iconUrl } = data;
|
|
320
374
|
|
|
321
375
|
// Map custom types to valid Chrome notification types
|
|
322
376
|
const validType = ['basic', 'image', 'list', 'progress'].includes(type) ? type : 'basic';
|
|
323
377
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
378
|
+
// Simplified icon handling - try available icons without validation
|
|
379
|
+
let finalIconUrl = '';
|
|
380
|
+
|
|
381
|
+
// Try to use extension icons in order of preference, but don't validate with fetch
|
|
382
|
+
const iconCandidates = [
|
|
383
|
+
iconUrl ? chrome.runtime.getURL(iconUrl) : null,
|
|
384
|
+
chrome.runtime.getURL('icons/icon48.png'),
|
|
385
|
+
chrome.runtime.getURL('icons/logo.png')
|
|
386
|
+
].filter(Boolean);
|
|
330
387
|
|
|
331
|
-
|
|
388
|
+
// Use the first candidate, or empty string as fallback
|
|
389
|
+
finalIconUrl = iconCandidates[0] || '';
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
const notificationId = await chrome.notifications.create({
|
|
393
|
+
type: validType,
|
|
394
|
+
iconUrl: finalIconUrl,
|
|
395
|
+
title: title || 'VibeSurf',
|
|
396
|
+
message
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
return { notificationId };
|
|
400
|
+
} catch (error) {
|
|
401
|
+
console.warn('[VibeSurf] Notification with icon failed, trying without icon:', error);
|
|
402
|
+
// Try once more with empty icon URL
|
|
403
|
+
try {
|
|
404
|
+
const notificationId = await chrome.notifications.create({
|
|
405
|
+
type: validType,
|
|
406
|
+
iconUrl: '', // Empty string will use browser default
|
|
407
|
+
title: title || 'VibeSurf',
|
|
408
|
+
message
|
|
409
|
+
});
|
|
410
|
+
return { notificationId };
|
|
411
|
+
} catch (fallbackError) {
|
|
412
|
+
console.error('[VibeSurf] Fallback notification also failed:', fallbackError);
|
|
413
|
+
throw new Error(`Failed to create notification: ${error.message}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
332
416
|
}
|
|
333
417
|
|
|
334
418
|
async showWelcomeNotification() {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
419
|
+
try {
|
|
420
|
+
await chrome.notifications.create({
|
|
421
|
+
type: 'basic',
|
|
422
|
+
iconUrl: '', // Use empty string to avoid icon issues
|
|
423
|
+
title: 'Welcome to VibeSurf!',
|
|
424
|
+
message: 'Click the VibeSurf icon in the toolbar to start automating your browsing tasks.'
|
|
425
|
+
});
|
|
426
|
+
} catch (error) {
|
|
427
|
+
console.warn('[VibeSurf] Welcome notification failed:', error);
|
|
428
|
+
// Don't throw, just log the warning
|
|
429
|
+
}
|
|
341
430
|
}
|
|
342
431
|
|
|
343
432
|
async checkBackendStatus(backendUrl = null) {
|
|
@@ -458,7 +547,40 @@ class VibeSurfBackground {
|
|
|
458
547
|
return { success: false, error: 'No file URL provided' };
|
|
459
548
|
}
|
|
460
549
|
|
|
550
|
+
// Add a unique request ID to track duplicate calls
|
|
551
|
+
const requestId = Date.now() + Math.random();
|
|
552
|
+
console.log(`[VibeSurf] openFileUrl called with ID: ${requestId}, URL: ${fileUrl}`);
|
|
553
|
+
|
|
461
554
|
try {
|
|
555
|
+
// Validate URL format before attempting to open
|
|
556
|
+
try {
|
|
557
|
+
new URL(fileUrl);
|
|
558
|
+
} catch (urlError) {
|
|
559
|
+
console.warn('[VibeSurf] Invalid URL format:', fileUrl, urlError);
|
|
560
|
+
return { success: false, error: 'Invalid file URL format' };
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Check if this is an HTTP/HTTPS URL and handle it appropriately
|
|
564
|
+
if (fileUrl.startsWith('http://') || fileUrl.startsWith('https://')) {
|
|
565
|
+
console.log(`[VibeSurf] Detected HTTP(S) URL, creating tab for: ${fileUrl}`);
|
|
566
|
+
|
|
567
|
+
// Try to create a new tab with the URL
|
|
568
|
+
const tab = await chrome.tabs.create({
|
|
569
|
+
url: fileUrl,
|
|
570
|
+
active: true
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
if (tab && tab.id) {
|
|
574
|
+
console.log(`[VibeSurf] Successfully opened HTTP(S) URL in tab: ${tab.id} (request: ${requestId})`);
|
|
575
|
+
return { success: true, tabId: tab.id };
|
|
576
|
+
} else {
|
|
577
|
+
console.warn(`[VibeSurf] Tab creation returned but no tab ID for request: ${requestId}`);
|
|
578
|
+
return { success: false, error: 'Failed to create tab - no tab ID returned' };
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// For file:// URLs, try the original approach
|
|
583
|
+
console.log(`[VibeSurf] Attempting to open file URL: ${fileUrl} (request: ${requestId})`);
|
|
462
584
|
|
|
463
585
|
// Try to create a new tab with the file URL
|
|
464
586
|
const tab = await chrome.tabs.create({
|
|
@@ -467,16 +589,25 @@ class VibeSurfBackground {
|
|
|
467
589
|
});
|
|
468
590
|
|
|
469
591
|
if (tab && tab.id) {
|
|
592
|
+
console.log(`[VibeSurf] Successfully opened file in tab: ${tab.id} (request: ${requestId})`);
|
|
470
593
|
return { success: true, tabId: tab.id };
|
|
471
594
|
} else {
|
|
472
|
-
|
|
595
|
+
console.warn(`[VibeSurf] Tab creation returned but no tab ID for request: ${requestId}`);
|
|
596
|
+
return { success: false, error: 'Failed to create tab - no tab ID returned' };
|
|
473
597
|
}
|
|
474
598
|
|
|
475
599
|
} catch (error) {
|
|
476
|
-
console.error(
|
|
600
|
+
console.error(`[VibeSurf] Error opening file URL (request: ${requestId}):`, error);
|
|
601
|
+
|
|
602
|
+
// Provide more specific error messages
|
|
603
|
+
let errorMessage = error.message || 'Unknown error opening file';
|
|
604
|
+
if (error.message && error.message.includes('file://')) {
|
|
605
|
+
errorMessage = 'Browser security restricts opening local files. Try copying the file path and opening manually.';
|
|
606
|
+
}
|
|
607
|
+
|
|
477
608
|
return {
|
|
478
609
|
success: false,
|
|
479
|
-
error:
|
|
610
|
+
error: errorMessage
|
|
480
611
|
};
|
|
481
612
|
}
|
|
482
613
|
}
|
|
@@ -624,6 +755,121 @@ class VibeSurfBackground {
|
|
|
624
755
|
}
|
|
625
756
|
}
|
|
626
757
|
|
|
758
|
+
// Request microphone permission through background script
|
|
759
|
+
async requestMicrophonePermission() {
|
|
760
|
+
try {
|
|
761
|
+
console.log('[VibeSurf] Requesting microphone permission through background script');
|
|
762
|
+
|
|
763
|
+
// Get the active tab to inject script
|
|
764
|
+
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
765
|
+
|
|
766
|
+
if (!tab) {
|
|
767
|
+
throw new Error('No active tab found');
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Check if we can inject script into this tab
|
|
771
|
+
if (tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://') ||
|
|
772
|
+
tab.url.startsWith('edge://') || tab.url.startsWith('moz-extension://')) {
|
|
773
|
+
throw new Error('Cannot access microphone from this type of page');
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Inject script to request microphone permission
|
|
777
|
+
const results = await chrome.scripting.executeScript({
|
|
778
|
+
target: { tabId: tab.id },
|
|
779
|
+
func: () => {
|
|
780
|
+
return new Promise((resolve, reject) => {
|
|
781
|
+
try {
|
|
782
|
+
// Check if mediaDevices is available
|
|
783
|
+
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
784
|
+
reject(new Error('Media devices not supported'));
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Request microphone with minimal constraints
|
|
789
|
+
const constraints = { audio: true, video: false };
|
|
790
|
+
|
|
791
|
+
navigator.mediaDevices.getUserMedia(constraints)
|
|
792
|
+
.then(stream => {
|
|
793
|
+
// Stop the stream immediately after getting permission
|
|
794
|
+
stream.getTracks().forEach(track => track.stop());
|
|
795
|
+
resolve({ success: true, hasPermission: true });
|
|
796
|
+
})
|
|
797
|
+
.catch(error => {
|
|
798
|
+
reject(new Error(`Microphone permission denied: ${error.name} - ${error.message}`));
|
|
799
|
+
});
|
|
800
|
+
} catch (error) {
|
|
801
|
+
reject(new Error(`Failed to request microphone permission: ${error.message}`));
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
const result = await results[0].result;
|
|
808
|
+
console.log('[VibeSurf] Microphone permission result:', result);
|
|
809
|
+
return result;
|
|
810
|
+
|
|
811
|
+
} catch (error) {
|
|
812
|
+
console.error('[VibeSurf] Failed to request microphone permission:', error);
|
|
813
|
+
return { success: false, error: error.message };
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Create a proper permission request page that opens in a new tab
|
|
818
|
+
async requestMicrophonePermissionWithUI() {
|
|
819
|
+
try {
|
|
820
|
+
console.log('[VibeSurf] Opening permission request page in new tab');
|
|
821
|
+
|
|
822
|
+
// Use the existing permission-request.html file
|
|
823
|
+
const permissionPageUrl = chrome.runtime.getURL('permission-request.html');
|
|
824
|
+
|
|
825
|
+
// Create a tab with the permission page
|
|
826
|
+
const permissionTab = await chrome.tabs.create({
|
|
827
|
+
url: permissionPageUrl,
|
|
828
|
+
active: true
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
console.log('[VibeSurf] Created permission tab:', permissionTab.id);
|
|
832
|
+
|
|
833
|
+
// Return a promise that resolves when we get the permission result
|
|
834
|
+
return new Promise((resolve) => {
|
|
835
|
+
const messageHandler = (message, sender, sendResponse) => {
|
|
836
|
+
if (message.type === 'MICROPHONE_PERMISSION_RESULT') {
|
|
837
|
+
console.log('[VibeSurf] Received permission result:', message);
|
|
838
|
+
|
|
839
|
+
// Clean up the message listener
|
|
840
|
+
chrome.runtime.onMessage.removeListener(messageHandler);
|
|
841
|
+
|
|
842
|
+
// Close the permission tab
|
|
843
|
+
chrome.tabs.remove(permissionTab.id).catch(() => {
|
|
844
|
+
// Tab might already be closed
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
// Resolve the promise
|
|
848
|
+
if (message.granted) {
|
|
849
|
+
resolve({ success: true, hasPermission: true });
|
|
850
|
+
} else {
|
|
851
|
+
resolve({ success: false, error: message.error || 'Permission denied by user' });
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
// Add the message listener
|
|
857
|
+
chrome.runtime.onMessage.addListener(messageHandler);
|
|
858
|
+
|
|
859
|
+
// Set a timeout to clean up if the tab is closed without response
|
|
860
|
+
setTimeout(() => {
|
|
861
|
+
chrome.runtime.onMessage.removeListener(messageHandler);
|
|
862
|
+
chrome.tabs.remove(permissionTab.id).catch(() => {});
|
|
863
|
+
resolve({ success: false, error: 'Permission request timed out' });
|
|
864
|
+
}, 30000); // 30 second timeout
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
} catch (error) {
|
|
868
|
+
console.error('[VibeSurf] Failed to create permission UI:', error);
|
|
869
|
+
return { success: false, error: error.message };
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
627
873
|
// Cleanup method for extension unload
|
|
628
874
|
async cleanup() {
|
|
629
875
|
|