vibesurf 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (70) hide show
  1. vibe_surf/__init__.py +12 -0
  2. vibe_surf/_version.py +34 -0
  3. vibe_surf/agents/__init__.py +0 -0
  4. vibe_surf/agents/browser_use_agent.py +1106 -0
  5. vibe_surf/agents/prompts/__init__.py +1 -0
  6. vibe_surf/agents/prompts/vibe_surf_prompt.py +176 -0
  7. vibe_surf/agents/report_writer_agent.py +360 -0
  8. vibe_surf/agents/vibe_surf_agent.py +1632 -0
  9. vibe_surf/backend/__init__.py +0 -0
  10. vibe_surf/backend/api/__init__.py +3 -0
  11. vibe_surf/backend/api/activity.py +243 -0
  12. vibe_surf/backend/api/config.py +740 -0
  13. vibe_surf/backend/api/files.py +322 -0
  14. vibe_surf/backend/api/models.py +257 -0
  15. vibe_surf/backend/api/task.py +300 -0
  16. vibe_surf/backend/database/__init__.py +13 -0
  17. vibe_surf/backend/database/manager.py +129 -0
  18. vibe_surf/backend/database/models.py +164 -0
  19. vibe_surf/backend/database/queries.py +922 -0
  20. vibe_surf/backend/database/schemas.py +100 -0
  21. vibe_surf/backend/llm_config.py +182 -0
  22. vibe_surf/backend/main.py +137 -0
  23. vibe_surf/backend/migrations/__init__.py +16 -0
  24. vibe_surf/backend/migrations/init_db.py +303 -0
  25. vibe_surf/backend/migrations/seed_data.py +236 -0
  26. vibe_surf/backend/shared_state.py +601 -0
  27. vibe_surf/backend/utils/__init__.py +7 -0
  28. vibe_surf/backend/utils/encryption.py +164 -0
  29. vibe_surf/backend/utils/llm_factory.py +225 -0
  30. vibe_surf/browser/__init__.py +8 -0
  31. vibe_surf/browser/agen_browser_profile.py +130 -0
  32. vibe_surf/browser/agent_browser_session.py +416 -0
  33. vibe_surf/browser/browser_manager.py +296 -0
  34. vibe_surf/browser/utils.py +790 -0
  35. vibe_surf/browser/watchdogs/__init__.py +0 -0
  36. vibe_surf/browser/watchdogs/action_watchdog.py +291 -0
  37. vibe_surf/browser/watchdogs/dom_watchdog.py +954 -0
  38. vibe_surf/chrome_extension/background.js +558 -0
  39. vibe_surf/chrome_extension/config.js +48 -0
  40. vibe_surf/chrome_extension/content.js +284 -0
  41. vibe_surf/chrome_extension/dev-reload.js +47 -0
  42. vibe_surf/chrome_extension/icons/convert-svg.js +33 -0
  43. vibe_surf/chrome_extension/icons/logo-preview.html +187 -0
  44. vibe_surf/chrome_extension/icons/logo.png +0 -0
  45. vibe_surf/chrome_extension/manifest.json +53 -0
  46. vibe_surf/chrome_extension/popup.html +134 -0
  47. vibe_surf/chrome_extension/scripts/api-client.js +473 -0
  48. vibe_surf/chrome_extension/scripts/main.js +491 -0
  49. vibe_surf/chrome_extension/scripts/markdown-it.min.js +3 -0
  50. vibe_surf/chrome_extension/scripts/session-manager.js +599 -0
  51. vibe_surf/chrome_extension/scripts/ui-manager.js +3687 -0
  52. vibe_surf/chrome_extension/sidepanel.html +347 -0
  53. vibe_surf/chrome_extension/styles/animations.css +471 -0
  54. vibe_surf/chrome_extension/styles/components.css +670 -0
  55. vibe_surf/chrome_extension/styles/main.css +2307 -0
  56. vibe_surf/chrome_extension/styles/settings.css +1100 -0
  57. vibe_surf/cli.py +357 -0
  58. vibe_surf/controller/__init__.py +0 -0
  59. vibe_surf/controller/file_system.py +53 -0
  60. vibe_surf/controller/mcp_client.py +68 -0
  61. vibe_surf/controller/vibesurf_controller.py +616 -0
  62. vibe_surf/controller/views.py +37 -0
  63. vibe_surf/llm/__init__.py +21 -0
  64. vibe_surf/llm/openai_compatible.py +237 -0
  65. vibesurf-0.1.0.dist-info/METADATA +97 -0
  66. vibesurf-0.1.0.dist-info/RECORD +70 -0
  67. vibesurf-0.1.0.dist-info/WHEEL +5 -0
  68. vibesurf-0.1.0.dist-info/entry_points.txt +2 -0
  69. vibesurf-0.1.0.dist-info/licenses/LICENSE +201 -0
  70. vibesurf-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,601 @@
1
+ """
2
+ Shared State Module
3
+
4
+ Contains global state variables shared between main.py and routers
5
+ to avoid circular import issues.
6
+ """
7
+ import pdb
8
+ from typing import Optional, Dict, Any, List
9
+ from datetime import datetime
10
+ import logging
11
+ import os
12
+ import json
13
+ import platform
14
+ from pathlib import Path
15
+
16
+ # VibeSurf components
17
+ from vibe_surf.agents.vibe_surf_agent import VibeSurfAgent
18
+ from vibe_surf.controller.vibesurf_controller import VibeSurfController
19
+ from vibe_surf.browser.browser_manager import BrowserManager
20
+ from browser_use.llm.base import BaseChatModel
21
+ from browser_use.llm.openai.chat import ChatOpenAI
22
+ from browser_use.browser import BrowserProfile
23
+ from vibe_surf.llm.openai_compatible import ChatOpenAICompatible
24
+ from vibe_surf.browser.agent_browser_session import AgentBrowserSession
25
+ from vibe_surf.browser.agen_browser_profile import AgentBrowserProfile
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ # Global VibeSurf components
30
+ vibesurf_agent: Optional[VibeSurfAgent] = None
31
+ browser_manager: Optional[BrowserManager] = None
32
+ controller: Optional[VibeSurfController] = None
33
+ llm: Optional[BaseChatModel] = None
34
+ db_manager: Optional['DatabaseManager'] = None
35
+
36
+ # Environment variables
37
+ workspace_dir: str = ""
38
+ browser_execution_path: str = ""
39
+ browser_user_data: str = ""
40
+
41
+ # Global environment variables dictionary
42
+ envs: Dict[str, str] = {}
43
+
44
+ # MCP server management
45
+ active_mcp_server: Dict[str, str] = {} # Dict[mcp_id: mcp_server_name]
46
+
47
+ # Single task execution tracking
48
+ active_task: Optional[Dict[str, Any]] = None
49
+
50
+
51
+ def get_all_components():
52
+ """Get all components as a dictionary"""
53
+ return {
54
+ "vibesurf_agent": vibesurf_agent,
55
+ "browser_manager": browser_manager,
56
+ "controller": controller,
57
+ "llm": llm,
58
+ "db_manager": db_manager,
59
+ "workspace_dir": workspace_dir,
60
+ "browser_execution_path": browser_execution_path,
61
+ "browser_user_data": browser_user_data,
62
+ "active_mcp_server": active_mcp_server,
63
+ "active_task": active_task,
64
+ "envs": envs
65
+ }
66
+
67
+
68
+ def set_components(**kwargs):
69
+ """Update global components"""
70
+ global vibesurf_agent, browser_manager, controller, llm, db_manager
71
+ global workspace_dir, browser_execution_path, browser_user_data, active_mcp_server, envs
72
+
73
+ if "vibesurf_agent" in kwargs:
74
+ vibesurf_agent = kwargs["vibesurf_agent"]
75
+ if "browser_manager" in kwargs:
76
+ browser_manager = kwargs["browser_manager"]
77
+ if "controller" in kwargs:
78
+ controller = kwargs["controller"]
79
+ if "llm" in kwargs:
80
+ llm = kwargs["llm"]
81
+ if "db_manager" in kwargs:
82
+ db_manager = kwargs["db_manager"]
83
+ if "workspace_dir" in kwargs:
84
+ workspace_dir = kwargs["workspace_dir"]
85
+ if "browser_execution_path" in kwargs:
86
+ browser_execution_path = kwargs["browser_execution_path"]
87
+ if "browser_user_data" in kwargs:
88
+ browser_user_data = kwargs["browser_user_data"]
89
+ if "active_mcp_server" in kwargs:
90
+ active_mcp_server = kwargs["active_mcp_server"]
91
+ if "envs" in kwargs:
92
+ envs = kwargs["envs"]
93
+
94
+
95
+ async def execute_task_background(
96
+ task_id: str,
97
+ session_id: str,
98
+ task: str,
99
+ llm_profile_name: str,
100
+ upload_files: Optional[List[str]] = None,
101
+ db_session=None
102
+ ):
103
+ """Background task execution function for single task with LLM profile support"""
104
+ global vibesurf_agent, active_task
105
+
106
+ try:
107
+ # Check if MCP server configuration needs update
108
+ await _check_and_update_mcp_servers(db_session)
109
+
110
+ # Update active task status to running
111
+ active_task = {
112
+ "task_id": task_id,
113
+ "status": "running",
114
+ "session_id": session_id,
115
+ "task": task,
116
+ "llm_profile_name": llm_profile_name,
117
+ "workspace_dir": workspace_dir,
118
+ "upload_files": upload_files or [],
119
+ "active_mcp_servers": list(active_mcp_server.values()), # List of MCP server names
120
+ "start_time": datetime.now(),
121
+ "agent_id": task_id # Use task_id as agent_id for tracking
122
+ }
123
+
124
+ logger.info(f"Task {task_id} started for session {session_id} with profile {llm_profile_name}")
125
+
126
+ # Ensure correct workspace directory is set for this task
127
+ if vibesurf_agent:
128
+ vibesurf_agent.workspace_dir = workspace_dir
129
+
130
+ # Execute the task
131
+ result = await vibesurf_agent.run(
132
+ task=task,
133
+ upload_files=upload_files,
134
+ session_id=session_id
135
+ )
136
+
137
+ # Update task status to completed
138
+ if active_task and active_task.get("status") != "stopped":
139
+ active_task.update({
140
+ "status": "completed",
141
+ "result": result,
142
+ "end_time": datetime.now()
143
+ })
144
+
145
+ # Get session directory for report path
146
+ session_dir = os.path.join(workspace_dir, session_id)
147
+ report_path = None
148
+
149
+ # Look for generated report
150
+ reports_dir = os.path.join(session_dir, "reports")
151
+ if os.path.exists(reports_dir):
152
+ for file in os.listdir(reports_dir):
153
+ if file.endswith('.html'):
154
+ report_path = os.path.join(reports_dir, file)
155
+ break
156
+
157
+ # Save task to database
158
+ if db_session:
159
+ try:
160
+ from .database.queries import TaskQueries
161
+ await TaskQueries.update_task_completion(
162
+ db_session,
163
+ task_id=task_id,
164
+ task_result=result,
165
+ task_status=active_task.get("status", "completed"),
166
+ report_path=report_path
167
+ )
168
+ await db_session.commit()
169
+ except Exception as e:
170
+ logger.error(f"Failed to update task in database: {e}")
171
+
172
+ logger.info(f"Task {task_id} completed for session {session_id}")
173
+
174
+ except Exception as e:
175
+ logger.error(f"Task execution failed: {e}")
176
+ # Update task status to failed
177
+ if active_task and active_task.get("task_id") == task_id:
178
+ active_task.update({
179
+ "status": "failed",
180
+ "error": str(e),
181
+ "end_time": datetime.now()
182
+ })
183
+
184
+ # Save failed task to database
185
+ if db_session:
186
+ try:
187
+ from .database.queries import TaskQueries
188
+ await TaskQueries.update_task_completion(
189
+ db_session,
190
+ task_id=task_id,
191
+ task_result=None,
192
+ task_status="failed",
193
+ error_message=str(e)
194
+ )
195
+ await db_session.commit()
196
+ except Exception as e:
197
+ logger.error(f"Failed to save failed task to database: {e}")
198
+
199
+ logger.error(f"Task {task_id} failed for session {session_id}: {e}")
200
+ finally:
201
+ # Clear active task when execution is complete (success or failure)
202
+ active_task = None
203
+
204
+
205
+ def is_task_running() -> bool:
206
+ """Quick check if any task is currently running"""
207
+ global active_task
208
+ return active_task is not None and active_task.get("status") not in ["failed",
209
+ "completed",
210
+ "stopped"]
211
+
212
+
213
+ def get_active_task_info() -> Optional[Dict[str, Any]]:
214
+ """Get current active task information"""
215
+ global active_task
216
+ return active_task.copy() if active_task else None
217
+
218
+
219
+ def clear_active_task():
220
+ """Clear the active task (used when stopping)"""
221
+ global active_task
222
+ active_task = None
223
+
224
+
225
+ async def _check_and_update_mcp_servers(db_session):
226
+ """Check if MCP server configuration has changed and update controller if needed"""
227
+ global controller, active_mcp_server
228
+
229
+ try:
230
+ if not db_session:
231
+ return
232
+
233
+ from .database.queries import McpProfileQueries
234
+
235
+ # Get current active MCP servers from database
236
+ active_profiles = await McpProfileQueries.get_active_profiles(db_session)
237
+ current_active_servers = {profile.mcp_id: profile.mcp_server_name for profile in active_profiles}
238
+
239
+ # Compare with shared state
240
+ if current_active_servers != active_mcp_server:
241
+ logger.info(f"MCP server configuration changed. Updating controller...")
242
+ logger.info(f"Old config: {active_mcp_server}")
243
+ logger.info(f"New config: {current_active_servers}")
244
+
245
+ # Update shared state
246
+ active_mcp_server = current_active_servers.copy()
247
+
248
+ # Create new MCP server config for controller
249
+ mcp_server_config = await _build_mcp_server_config(active_profiles)
250
+
251
+ # Unregister old MCP clients and register new ones
252
+ if controller:
253
+ await controller.unregister_mcp_clients()
254
+ controller.mcp_server_config = mcp_server_config
255
+ await controller.register_mcp_clients()
256
+ logger.info("โœ… Controller MCP configuration updated successfully")
257
+
258
+ except Exception as e:
259
+ logger.error(f"Failed to check and update MCP servers: {e}")
260
+
261
+
262
+ async def _build_mcp_server_config(active_profiles) -> Dict[str, Any]:
263
+ """Build MCP server configuration from active profiles"""
264
+ mcp_server_config = {
265
+ "mcpServers": {}
266
+ }
267
+
268
+ for profile in active_profiles:
269
+ mcp_server_config["mcpServers"][profile.mcp_server_name] = json.loads(profile.mcp_server_params)
270
+
271
+ return mcp_server_config
272
+
273
+
274
+ async def _load_active_mcp_servers():
275
+ """Load active MCP servers from database and return config"""
276
+ global db_manager, active_mcp_server
277
+
278
+ try:
279
+ if not db_manager:
280
+ return {}
281
+
282
+ from .database.queries import McpProfileQueries
283
+
284
+ async for db in db_manager.get_session():
285
+ try:
286
+ # Get all active MCP profiles
287
+ active_profiles = await McpProfileQueries.get_active_profiles(db)
288
+
289
+ # Update shared state
290
+ active_mcp_server = {profile.mcp_id: profile.mcp_server_name for profile in active_profiles}
291
+
292
+ # Build MCP server config
293
+ mcp_server_config = await _build_mcp_server_config(active_profiles)
294
+
295
+ logger.info(f"โœ… Loaded {len(active_profiles)} active MCP servers: {list(active_mcp_server.values())}")
296
+
297
+ return mcp_server_config
298
+
299
+ except Exception as e:
300
+ logger.warning(f"Failed to load MCP servers from database: {e}")
301
+ return {}
302
+ finally:
303
+ break
304
+
305
+ except Exception as e:
306
+ logger.warning(f"Database not available for MCP server loading: {e}")
307
+ return {}
308
+
309
+
310
+ async def initialize_vibesurf_components():
311
+ """Initialize VibeSurf components from environment variables and default LLM profile"""
312
+ global vibesurf_agent, browser_manager, controller, llm, db_manager
313
+ global workspace_dir, browser_execution_path, browser_user_data, envs
314
+
315
+ try:
316
+ # Load environment variables
317
+ env_workspace_dir = os.getenv("VIBESURF_WORKSPACE", "")
318
+ if not env_workspace_dir or not env_workspace_dir.strip():
319
+ # Set default workspace directory based on OS
320
+ if platform.system() == "Windows":
321
+ default_workspace = os.path.join(os.environ.get("APPDATA", ""), "VibeSurf")
322
+ elif platform.system() == "Darwin": # macOS
323
+ default_workspace = os.path.join(os.path.expanduser("~"), "Library", "Application Support", "VibeSurf")
324
+ else: # Linux and others
325
+ default_workspace = os.path.join(os.path.expanduser("~"), ".vibesurf")
326
+ workspace_dir = default_workspace
327
+ else:
328
+ workspace_dir = env_workspace_dir
329
+ workspace_dir = os.path.abspath(workspace_dir)
330
+ os.makedirs(workspace_dir, exist_ok=True)
331
+ logger.info("WorkSpace directory: {}".format(workspace_dir))
332
+
333
+ # Load environment configuration from envs.json
334
+ envs_file_path = os.path.join(workspace_dir, "envs.json")
335
+ try:
336
+ if os.path.exists(envs_file_path):
337
+ with open(envs_file_path, 'r', encoding='utf-8') as f:
338
+ envs = json.load(f)
339
+ logger.info(f"โœ… Loaded environment configuration from {envs_file_path}")
340
+
341
+ # Set loaded environment variables to system environment
342
+ for key, value in envs.items():
343
+ if value: # Only set non-empty values
344
+ os.environ[key] = value
345
+ logger.info(f"๐Ÿ”ง Set environment variable: {key}")
346
+ else:
347
+ envs = {}
348
+ logger.info("๐Ÿ“ No existing envs.json found, initializing empty environment configuration")
349
+ except Exception as e:
350
+ logger.warning(f"Failed to load envs.json: {e}, initializing empty environment configuration")
351
+ envs = {}
352
+ browser_execution_path = os.getenv("BROWSER_EXECUTION_PATH", "")
353
+ assert os.path.exists(browser_execution_path), "Please set the BROWSER_EXECUTION_PATH environment variable"
354
+ browser_user_data = os.getenv("BROWSER_USER_DATA", "")
355
+ if not browser_user_data:
356
+ browser_user_data = os.path.join(workspace_dir, "browser_user_data",
357
+ f"{os.path.basename(browser_execution_path)}-profile")
358
+
359
+ # Get VibeSurf extension path
360
+ vibesurf_extension = os.getenv("VIBESURF_EXTENSION", "")
361
+ if not vibesurf_extension.strip():
362
+ current_file = Path(__file__)
363
+ project_root = current_file.parent.parent.absolute()
364
+ vibesurf_extension = str(project_root / "chrome_extension")
365
+ assert os.path.exists(vibesurf_extension)
366
+
367
+ # Get backend URL
368
+ backend_port = os.getenv("VIBESURF_BACKEND_PORT", "9335")
369
+ if not backend_port or not backend_port.strip():
370
+ backend_port = "9335"
371
+ backend_port = int(backend_port)
372
+
373
+ backend_url = f'http://127.0.0.1:{backend_port}'
374
+
375
+ # Update envs dictionary with current environment variables
376
+ envs.update({
377
+ "BROWSER_EXECUTION_PATH": browser_execution_path,
378
+ "BROWSER_USER_DATA": browser_user_data,
379
+ "VIBESURF_EXTENSION": vibesurf_extension,
380
+ "VIBESURF_BACKEND_URL": backend_url
381
+ })
382
+
383
+ # Create directories if they don't exist
384
+ os.makedirs(workspace_dir, exist_ok=True)
385
+
386
+ # Initialize database manager after workspace_dir is set
387
+ from .database.manager import DatabaseManager
388
+
389
+ # Debug: Check environment variable value
390
+ env_database_url = os.getenv('VIBESURF_DATABASE_URL')
391
+ logger.info(f"๐Ÿ” VIBESURF_DATABASE_URL environment variable: '{env_database_url}'")
392
+ logger.info(f"๐Ÿ” workspace_dir: '{workspace_dir}'")
393
+
394
+ # Handle empty string environment variable properly
395
+ if env_database_url and env_database_url.strip():
396
+ database_url = env_database_url
397
+ else:
398
+ database_url = f'sqlite+aiosqlite:///{os.path.join(workspace_dir, "vibe_surf.db")}'
399
+
400
+ logger.info(f"๐Ÿ” Final database_url: '{database_url}'")
401
+
402
+ db_manager = DatabaseManager(database_url)
403
+
404
+ # Initialize database tables
405
+ await db_manager.create_tables()
406
+ logger.info("โœ… Database manager initialized successfully")
407
+
408
+ # Initialize LLM from default profile (if available) or fallback to environment variables
409
+ llm = await _initialize_default_llm()
410
+
411
+ # Initialize browser manager
412
+ if browser_manager:
413
+ main_browser_session = browser_manager.main_browser_session
414
+ else:
415
+ from screeninfo import get_monitors
416
+ primary_monitor = get_monitors()[0]
417
+ _update_extension_backend_url(envs["VIBESURF_EXTENSION"], backend_url)
418
+
419
+ browser_profile = AgentBrowserProfile(
420
+ executable_path=browser_execution_path,
421
+ user_data_dir=browser_user_data,
422
+ headless=False,
423
+ keep_alive=True,
424
+ custom_extensions=[envs["VIBESURF_EXTENSION"]],
425
+ window_size={"width": primary_monitor.width, "height": primary_monitor.height}
426
+ )
427
+
428
+ # Initialize components
429
+ main_browser_session = AgentBrowserSession(browser_profile=browser_profile)
430
+ await main_browser_session.start()
431
+ browser_manager = BrowserManager(
432
+ main_browser_session=main_browser_session
433
+ )
434
+
435
+ # Load active MCP servers from database
436
+ mcp_server_config = await _load_active_mcp_servers()
437
+
438
+ # Initialize vibesurf controller with MCP server config
439
+ controller = VibeSurfController(mcp_server_config=mcp_server_config)
440
+
441
+ # Register MCP clients if there are any active MCP servers
442
+ if mcp_server_config and mcp_server_config.get("mcpServers"):
443
+ await controller.register_mcp_clients()
444
+ logger.info(f"โœ… Registered {len(mcp_server_config['mcpServers'])} MCP servers")
445
+
446
+ # Initialize VibeSurfAgent
447
+ vibesurf_agent = VibeSurfAgent(
448
+ llm=llm,
449
+ browser_manager=browser_manager,
450
+ controller=controller,
451
+ workspace_dir=workspace_dir
452
+ )
453
+
454
+ # Save environment configuration to envs.json
455
+ try:
456
+ with open(envs_file_path, 'w', encoding='utf-8') as f:
457
+ json.dump(envs, f, indent=2, ensure_ascii=False)
458
+ logger.info(f"โœ… Saved environment configuration to {envs_file_path}")
459
+ except Exception as e:
460
+ logger.warning(f"Failed to save envs.json: {e}")
461
+
462
+ logger.info("โœ… VibeSurf components initialized successfully")
463
+
464
+ except Exception as e:
465
+ logger.error(f"โŒ Failed to initialize VibeSurf components: {e}")
466
+ raise
467
+
468
+
469
+ async def _initialize_default_llm():
470
+ """Initialize LLM from default profile or fallback to environment variables"""
471
+ global db_manager
472
+
473
+ try:
474
+ # Try to get default LLM profile from database
475
+ from .database.queries import LLMProfileQueries
476
+ from .utils.llm_factory import create_llm_from_profile
477
+
478
+ # Get database session from shared state db_manager
479
+ if db_manager:
480
+ async for db in db_manager.get_session():
481
+ try:
482
+ default_profile = await LLMProfileQueries.get_default_profile(db)
483
+ if default_profile:
484
+ # Get profile with decrypted API key
485
+ profile_with_key = await LLMProfileQueries.get_profile_with_decrypted_key(
486
+ db, default_profile.profile_name
487
+ )
488
+ if profile_with_key:
489
+ llm_instance = create_llm_from_profile(profile_with_key)
490
+ logger.info(f"โœ… LLM initialized from default profile: {default_profile.profile_name}")
491
+ return llm_instance
492
+ break
493
+ except Exception as e:
494
+ logger.warning(f"Failed to load default LLM profile: {e}")
495
+ break
496
+ except Exception as e:
497
+ logger.warning(f"Database not available for LLM profile loading: {e}")
498
+
499
+ # Fallback to environment variables
500
+ logger.info("๐Ÿ”„ Falling back to environment variable LLM configuration")
501
+ return ChatOpenAI(
502
+ model=os.getenv("LLM_MODEL", "gpt-4.1-mini"),
503
+ base_url=os.getenv("OPENAI_ENDPOINT", "https://api.openai.com/v1"),
504
+ api_key=os.getenv("OPENAI_API_KEY", "")
505
+ )
506
+
507
+
508
+ async def update_llm_from_profile(profile_name: str):
509
+ """Update the global LLM instance from a specific profile"""
510
+ global vibesurf_agent, llm, db_manager
511
+
512
+ try:
513
+ from .database.queries import LLMProfileQueries
514
+ from .utils.llm_factory import create_llm_from_profile
515
+
516
+ # Get database session from shared state db_manager
517
+ if not db_manager:
518
+ raise ValueError("Database manager not initialized")
519
+
520
+ async for db in db_manager.get_session():
521
+ try:
522
+ # Get profile with decrypted API key
523
+ profile_with_key = await LLMProfileQueries.get_profile_with_decrypted_key(db, profile_name)
524
+ if not profile_with_key:
525
+ raise ValueError(f"LLM profile '{profile_name}' not found")
526
+
527
+ # Create new LLM instance
528
+ new_llm = create_llm_from_profile(profile_with_key)
529
+
530
+ # Update global state
531
+ llm = new_llm
532
+ if vibesurf_agent:
533
+ vibesurf_agent.llm = new_llm
534
+
535
+ logger.info(f"โœ… LLM updated to profile: {profile_name}")
536
+ return True
537
+
538
+ except Exception as e:
539
+ logger.error(f"Failed to update LLM from profile {profile_name}: {e}")
540
+ raise
541
+ finally:
542
+ break
543
+
544
+ except Exception as e:
545
+ logger.error(f"Database error while updating LLM profile: {e}")
546
+ raise
547
+
548
+
549
+ def get_envs() -> Dict[str, str]:
550
+ """Get the current environment variables dictionary"""
551
+ global envs
552
+ return envs.copy()
553
+
554
+
555
+ def update_envs(updates: Dict[str, str]) -> bool:
556
+ """Update environment variables and save to envs.json"""
557
+ global envs, workspace_dir
558
+
559
+ try:
560
+ # Update the envs dictionary
561
+ envs.update(updates)
562
+
563
+ # Save to envs.json
564
+ envs_file_path = os.path.join(workspace_dir, "envs.json")
565
+ with open(envs_file_path, 'w', encoding='utf-8') as f:
566
+ json.dump(envs, f, indent=2, ensure_ascii=False)
567
+
568
+ logger.info(f"โœ… Updated and saved environment variables to {envs_file_path}")
569
+ return True
570
+
571
+ except Exception as e:
572
+ logger.error(f"Failed to update environment variables: {e}")
573
+ return False
574
+
575
+
576
+ def _update_extension_backend_url(extension_path: str, backend_url: str):
577
+ try:
578
+ import re
579
+
580
+ config_js_path = os.path.join(extension_path, "config.js")
581
+ if not os.path.exists(config_js_path):
582
+ logger.warning(f"Extension config.js not found at: {config_js_path}")
583
+ return
584
+
585
+ with open(config_js_path, 'r', encoding='utf-8') as f:
586
+ content = f.read()
587
+
588
+ # ๅŒน้… BACKEND_URL: 'xyz' ๆˆ– BACKEND_URL: "xyz"๏ผŒxyzๆ˜ฏไปปๆ„ๅ†…ๅฎน
589
+ pattern = r"BACKEND_URL:\s*(['\"]).*?\1"
590
+ replacement = f"BACKEND_URL: '{backend_url}'"
591
+
592
+ updated_content = re.sub(pattern, replacement, content)
593
+
594
+ with open(config_js_path, 'w', encoding='utf-8') as f:
595
+ f.write(updated_content)
596
+
597
+ logger.info(f"โœ… Updated extension backend URL to: {backend_url}")
598
+
599
+ except Exception as e:
600
+ logger.error(f"โŒ Failed to update extension backend URL: {e}")
601
+
@@ -0,0 +1,7 @@
1
+ """
2
+ Utilities package for VibeSurf Backend
3
+ """
4
+
5
+ from .encryption import encrypt_api_key, decrypt_api_key, is_encrypted
6
+
7
+ __all__ = ['encrypt_api_key', 'decrypt_api_key', 'is_encrypted']