vibesurf 0.1.2__tar.gz → 0.1.4__tar.gz

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 (85) hide show
  1. {vibesurf-0.1.2 → vibesurf-0.1.4}/PKG-INFO +6 -1
  2. {vibesurf-0.1.2 → vibesurf-0.1.4}/README.md +4 -0
  3. {vibesurf-0.1.2 → vibesurf-0.1.4}/pyproject.toml +1 -0
  4. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/_version.py +3 -3
  5. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/api/config.py +18 -4
  6. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/api/task.py +5 -0
  7. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/shared_state.py +4 -3
  8. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/utils/llm_factory.py +23 -6
  9. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/scripts/ui-manager.js +118 -1
  10. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/styles/components.css +40 -0
  11. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibesurf.egg-info/PKG-INFO +6 -1
  12. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibesurf.egg-info/requires.txt +1 -0
  13. {vibesurf-0.1.2 → vibesurf-0.1.4}/.env.example +0 -0
  14. {vibesurf-0.1.2 → vibesurf-0.1.4}/.github/workflows/publish.yml +0 -0
  15. {vibesurf-0.1.2 → vibesurf-0.1.4}/.gitignore +0 -0
  16. {vibesurf-0.1.2 → vibesurf-0.1.4}/.python-version +0 -0
  17. {vibesurf-0.1.2 → vibesurf-0.1.4}/LICENSE +0 -0
  18. {vibesurf-0.1.2 → vibesurf-0.1.4}/MANIFEST.in +0 -0
  19. {vibesurf-0.1.2 → vibesurf-0.1.4}/docs/PYPI_SETUP.md +0 -0
  20. {vibesurf-0.1.2 → vibesurf-0.1.4}/setup.cfg +0 -0
  21. {vibesurf-0.1.2 → vibesurf-0.1.4}/tests/test_agents.py +0 -0
  22. {vibesurf-0.1.2 → vibesurf-0.1.4}/tests/test_backend_api.py +0 -0
  23. {vibesurf-0.1.2 → vibesurf-0.1.4}/tests/test_browser.py +0 -0
  24. {vibesurf-0.1.2 → vibesurf-0.1.4}/tests/test_controller.py +0 -0
  25. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/__init__.py +0 -0
  26. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/agents/__init__.py +0 -0
  27. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/agents/browser_use_agent.py +0 -0
  28. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/agents/prompts/__init__.py +0 -0
  29. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/agents/prompts/vibe_surf_prompt.py +0 -0
  30. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/agents/report_writer_agent.py +0 -0
  31. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/agents/vibe_surf_agent.py +0 -0
  32. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/__init__.py +0 -0
  33. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/api/__init__.py +0 -0
  34. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/api/activity.py +0 -0
  35. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/api/files.py +0 -0
  36. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/api/models.py +0 -0
  37. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/database/__init__.py +0 -0
  38. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/database/manager.py +0 -0
  39. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/database/models.py +0 -0
  40. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/database/queries.py +0 -0
  41. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/database/schemas.py +0 -0
  42. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/llm_config.py +0 -0
  43. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/main.py +0 -0
  44. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/migrations/__init__.py +0 -0
  45. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/migrations/init_db.py +0 -0
  46. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/migrations/seed_data.py +0 -0
  47. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/utils/__init__.py +0 -0
  48. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/backend/utils/encryption.py +0 -0
  49. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/browser/__init__.py +0 -0
  50. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/browser/agen_browser_profile.py +0 -0
  51. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/browser/agent_browser_session.py +0 -0
  52. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/browser/browser_manager.py +0 -0
  53. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/browser/utils.py +0 -0
  54. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/browser/watchdogs/__init__.py +0 -0
  55. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/browser/watchdogs/action_watchdog.py +0 -0
  56. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/browser/watchdogs/dom_watchdog.py +0 -0
  57. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/background.js +0 -0
  58. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/config.js +0 -0
  59. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/content.js +0 -0
  60. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/dev-reload.js +0 -0
  61. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/icons/convert-svg.js +0 -0
  62. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/icons/logo-preview.html +0 -0
  63. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/icons/logo.png +0 -0
  64. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/manifest.json +0 -0
  65. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/popup.html +0 -0
  66. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/scripts/api-client.js +0 -0
  67. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/scripts/main.js +0 -0
  68. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/scripts/markdown-it.min.js +0 -0
  69. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/scripts/session-manager.js +0 -0
  70. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/sidepanel.html +0 -0
  71. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/styles/animations.css +0 -0
  72. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/styles/main.css +0 -0
  73. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/chrome_extension/styles/settings.css +0 -0
  74. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/cli.py +0 -0
  75. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/controller/__init__.py +0 -0
  76. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/controller/file_system.py +0 -0
  77. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/controller/mcp_client.py +0 -0
  78. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/controller/vibesurf_controller.py +0 -0
  79. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/controller/views.py +0 -0
  80. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/llm/__init__.py +0 -0
  81. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibe_surf/llm/openai_compatible.py +0 -0
  82. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibesurf.egg-info/SOURCES.txt +0 -0
  83. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibesurf.egg-info/dependency_links.txt +0 -0
  84. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibesurf.egg-info/entry_points.txt +0 -0
  85. {vibesurf-0.1.2 → vibesurf-0.1.4}/vibesurf.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vibesurf
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: VibeSurf: A powerful browser assistant for vibe surfing
5
5
  Author: Shao Warm
6
6
  License: Apache-2.0
@@ -36,12 +36,17 @@ Requires-Dist: python-dotenv>=1.0.0
36
36
  Requires-Dist: sqlalchemy>=2.0.43
37
37
  Requires-Dist: aiosqlite>=0.21.0
38
38
  Requires-Dist: rich>=13.0.0
39
+ Requires-Dist: greenlet>=3.2.4
39
40
  Dynamic: license-file
40
41
 
41
42
  # VibeSurf: A powerful browser assistant for vibe surfing
43
+ [![Discord](https://img.shields.io/discord/1303749220842340412?color=7289DA&label=Discord&logo=discord&logoColor=white)](https://discord.gg/TXNnP9gJ)
44
+ [![WarmShao](https://img.shields.io/twitter/follow/warmshao?style=social)](https://x.com/warmshao)
42
45
 
43
46
  VibeSurf is an open-source AI agentic browser that revolutionizes browser automation and research.
44
47
 
48
+ If you're as excited about open-source AI browsing as I am, give it a star! ⭐
49
+
45
50
  ## ✨ Key Features
46
51
 
47
52
  - 🧠 **Advanced AI Automation**: Beyond browser automation, VibeSurf performs deep research, intelligent crawling, content summarization, and more to exploration.
@@ -1,7 +1,11 @@
1
1
  # VibeSurf: A powerful browser assistant for vibe surfing
2
+ [![Discord](https://img.shields.io/discord/1303749220842340412?color=7289DA&label=Discord&logo=discord&logoColor=white)](https://discord.gg/TXNnP9gJ)
3
+ [![WarmShao](https://img.shields.io/twitter/follow/warmshao?style=social)](https://x.com/warmshao)
2
4
 
3
5
  VibeSurf is an open-source AI agentic browser that revolutionizes browser automation and research.
4
6
 
7
+ If you're as excited about open-source AI browsing as I am, give it a star! ⭐
8
+
5
9
  ## ✨ Key Features
6
10
 
7
11
  - 🧠 **Advanced AI Automation**: Beyond browser automation, VibeSurf performs deep research, intelligent crawling, content summarization, and more to exploration.
@@ -37,6 +37,7 @@ dependencies = [
37
37
  "sqlalchemy>=2.0.43",
38
38
  "aiosqlite>=0.21.0",
39
39
  "rich>=13.0.0",
40
+ "greenlet>=3.2.4",
40
41
  ]
41
42
 
42
43
  [project.urls]
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.1.2'
32
- __version_tuple__ = version_tuple = (0, 1, 2)
31
+ __version__ = version = '0.1.4'
32
+ __version_tuple__ = version_tuple = (0, 1, 4)
33
33
 
34
- __commit_id__ = commit_id = 'g1f4701acd'
34
+ __commit_id__ = commit_id = 'gec7bc3b2e'
@@ -115,10 +115,24 @@ async def create_llm_profile(
115
115
 
116
116
  except Exception as e:
117
117
  logger.error(f"Failed to create LLM profile: {e}")
118
- raise HTTPException(
119
- status_code=500,
120
- detail=f"Failed to create LLM profile: {str(e)}"
121
- )
118
+
119
+ # Handle specific database constraint errors
120
+ error_msg = str(e)
121
+ if "UNIQUE constraint failed: llm_profiles.profile_name" in error_msg:
122
+ raise HTTPException(
123
+ status_code=400,
124
+ detail=f"Profile with name '{profile_request.profile_name}' already exists. Please choose a different name."
125
+ )
126
+ elif "IntegrityError" in error_msg and "profile_name" in error_msg:
127
+ raise HTTPException(
128
+ status_code=400,
129
+ detail=f"Profile name '{profile_request.profile_name}' is already in use. Please choose a different name."
130
+ )
131
+ else:
132
+ raise HTTPException(
133
+ status_code=500,
134
+ detail=f"Failed to create LLM profile: {str(e)}"
135
+ )
122
136
 
123
137
  @router.get("/llm-profiles", response_model=List[LLMProfileResponse])
124
138
  async def list_llm_profiles(
@@ -80,6 +80,11 @@ async def submit_task(
80
80
  if not mcp_server_config and controller and hasattr(controller, 'mcp_server_config'):
81
81
  mcp_server_config = controller.mcp_server_config
82
82
 
83
+ # Ensure we have a valid MCP server config (never None)
84
+ if mcp_server_config is None:
85
+ mcp_server_config = {"mcpServers": {}}
86
+ logger.info("Using default empty MCP server configuration")
87
+
83
88
  # DEBUG: Log the type and content of mcp_server_config
84
89
  logger.info(f"mcp_server_config type: {type(mcp_server_config)}, value: {mcp_server_config}")
85
90
 
@@ -277,7 +277,8 @@ async def _load_active_mcp_servers():
277
277
 
278
278
  try:
279
279
  if not db_manager:
280
- return {}
280
+ logger.info("Database manager not available, returning empty MCP config")
281
+ return {"mcpServers": {}}
281
282
 
282
283
  from .database.queries import McpProfileQueries
283
284
 
@@ -298,13 +299,13 @@ async def _load_active_mcp_servers():
298
299
 
299
300
  except Exception as e:
300
301
  logger.warning(f"Failed to load MCP servers from database: {e}")
301
- return {}
302
+ return {"mcpServers": {}}
302
303
  finally:
303
304
  break
304
305
 
305
306
  except Exception as e:
306
307
  logger.warning(f"Database not available for MCP server loading: {e}")
307
- return {}
308
+ return {"mcpServers": {}}
308
309
 
309
310
 
310
311
  async def initialize_vibesurf_components():
@@ -41,17 +41,34 @@ def create_llm_from_profile(llm_profile):
41
41
  if not is_provider_supported(provider):
42
42
  raise ValueError(f"Unsupported provider: {provider}. Supported: {get_supported_providers()}")
43
43
 
44
- # Common parameters
44
+ # Define provider-specific parameter support
45
+ provider_param_support = {
46
+ "openai": ["temperature", "top_p", "frequency_penalty", "seed"],
47
+ "anthropic": ["temperature", "top_p"],
48
+ "google": ["temperature", "top_p"],
49
+ "azure_openai": ["temperature", "top_p", "frequency_penalty", "seed"],
50
+ "groq": ["temperature", "top_p"],
51
+ "ollama": ["temperature"],
52
+ "openrouter": ["temperature", "top_p"], # OpenRouter doesn't support max_tokens
53
+ "deepseek": ["temperature", "top_p"],
54
+ "aws_bedrock": ["temperature"],
55
+ "anthropic_bedrock": ["temperature"],
56
+ "openai_compatible": ["temperature", "top_p", "frequency_penalty", "seed"]
57
+ }
58
+
59
+ # Build common parameters based on provider support
60
+ supported_params = provider_param_support.get(provider, ["temperature", "top_p", "frequency_penalty", "seed"])
45
61
  common_params = {}
46
- if temperature is not None:
62
+
63
+ if temperature is not None and "temperature" in supported_params:
47
64
  common_params["temperature"] = temperature
48
- if max_tokens is not None:
65
+ if max_tokens is not None and "max_tokens" in supported_params:
49
66
  common_params["max_tokens"] = max_tokens
50
- if top_p is not None:
67
+ if top_p is not None and "top_p" in supported_params:
51
68
  common_params["top_p"] = top_p
52
- if frequency_penalty is not None:
69
+ if frequency_penalty is not None and "frequency_penalty" in supported_params:
53
70
  common_params["frequency_penalty"] = frequency_penalty
54
- if seed is not None:
71
+ if seed is not None and "seed" in supported_params:
55
72
  common_params["seed"] = seed
56
73
 
57
74
  # Add provider-specific config if available
@@ -1609,6 +1609,13 @@ class VibeSurfUIManager {
1609
1609
  console.log('[UIManager] Profile form submit triggered');
1610
1610
 
1611
1611
  const form = event.target;
1612
+
1613
+ // Prevent multiple submissions
1614
+ if (form.dataset.submitting === 'true') {
1615
+ console.log('[UIManager] Form already submitting, ignoring duplicate submission');
1616
+ return;
1617
+ }
1618
+
1612
1619
  const formData = new FormData(form);
1613
1620
  const type = form.dataset.type;
1614
1621
  const mode = form.dataset.mode;
@@ -1616,6 +1623,10 @@ class VibeSurfUIManager {
1616
1623
 
1617
1624
  console.log('[UIManager] Form submission details:', { type, mode, profileId });
1618
1625
 
1626
+ // Set submitting state and disable form
1627
+ form.dataset.submitting = 'true';
1628
+ this.setProfileFormSubmitting(true);
1629
+
1619
1630
  // Convert FormData to object
1620
1631
  const data = {};
1621
1632
 
@@ -1746,7 +1757,113 @@ class VibeSurfUIManager {
1746
1757
 
1747
1758
  } catch (error) {
1748
1759
  console.error(`[UIManager] Failed to ${mode} ${type} profile:`, error);
1749
- this.showNotification(`Failed to ${mode} ${type} profile: ${error.message}`, 'error');
1760
+
1761
+ // Handle specific error types for better user experience
1762
+ let errorMessage = error.message || 'Unknown error occurred';
1763
+
1764
+ if (errorMessage.includes('already exists') || errorMessage.includes('already in use')) {
1765
+ // For duplicate profile name errors, highlight the name field
1766
+ this.highlightProfileNameError(errorMessage);
1767
+ errorMessage = errorMessage; // Use the specific error message from backend
1768
+ } else if (errorMessage.includes('UNIQUE constraint')) {
1769
+ errorMessage = `Profile name '${data.profile_name || data.display_name}' already exists. Please choose a different name.`;
1770
+ this.highlightProfileNameError(errorMessage);
1771
+ }
1772
+
1773
+ this.showNotification(`Failed to ${mode} ${type} profile: ${errorMessage}`, 'error');
1774
+ } finally {
1775
+ // Reset form state
1776
+ form.dataset.submitting = 'false';
1777
+ this.setProfileFormSubmitting(false);
1778
+ }
1779
+ }
1780
+
1781
+ setProfileFormSubmitting(isSubmitting) {
1782
+ const form = this.elements.profileForm;
1783
+ const submitButton = this.elements.profileFormSubmit;
1784
+ const cancelButton = this.elements.profileFormCancel;
1785
+
1786
+ if (!form) return;
1787
+
1788
+ // Disable/enable form inputs
1789
+ const inputs = form.querySelectorAll('input, select, textarea');
1790
+ inputs.forEach(input => {
1791
+ input.disabled = isSubmitting;
1792
+ });
1793
+
1794
+ // Update submit button
1795
+ if (submitButton) {
1796
+ submitButton.disabled = isSubmitting;
1797
+ submitButton.textContent = isSubmitting ? 'Saving...' : 'Save Profile';
1798
+ }
1799
+
1800
+ // Update cancel button
1801
+ if (cancelButton) {
1802
+ cancelButton.disabled = isSubmitting;
1803
+ }
1804
+
1805
+ console.log(`[UIManager] Profile form submitting state: ${isSubmitting}`);
1806
+ }
1807
+
1808
+ highlightProfileNameError(errorMessage) {
1809
+ const nameInput = this.elements.profileForm?.querySelector('input[name="profile_name"], input[name="display_name"]');
1810
+
1811
+ if (nameInput) {
1812
+ // Add error styling
1813
+ nameInput.classList.add('form-error');
1814
+ nameInput.focus();
1815
+
1816
+ // Create or update error message
1817
+ let errorElement = nameInput.parentElement.querySelector('.profile-name-error');
1818
+ if (!errorElement) {
1819
+ errorElement = document.createElement('div');
1820
+ errorElement.className = 'form-error-message profile-name-error';
1821
+ nameInput.parentElement.appendChild(errorElement);
1822
+ }
1823
+
1824
+ errorElement.textContent = errorMessage;
1825
+
1826
+ // Remove error styling after user starts typing
1827
+ const removeError = () => {
1828
+ nameInput.classList.remove('form-error');
1829
+ if (errorElement) {
1830
+ errorElement.remove();
1831
+ }
1832
+ nameInput.removeEventListener('input', removeError);
1833
+ };
1834
+
1835
+ nameInput.addEventListener('input', removeError);
1836
+
1837
+ console.log('[UIManager] Highlighted profile name error:', errorMessage);
1838
+ }
1839
+ }
1840
+
1841
+ // Real-time profile name validation
1842
+ async validateProfileNameAvailability(profileName, profileType) {
1843
+ if (!profileName || profileName.trim().length < 2) {
1844
+ return { isValid: true, message: '' }; // Don't validate very short names
1845
+ }
1846
+
1847
+ try {
1848
+ // Check if profile already exists
1849
+ const profiles = profileType === 'llm' ? this.state.llmProfiles : this.state.mcpProfiles;
1850
+ const nameField = profileType === 'llm' ? 'profile_name' : 'display_name';
1851
+
1852
+ const existingProfile = profiles.find(profile =>
1853
+ profile[nameField].toLowerCase() === profileName.toLowerCase()
1854
+ );
1855
+
1856
+ if (existingProfile) {
1857
+ return {
1858
+ isValid: false,
1859
+ message: `${profileType.toUpperCase()} profile "${profileName}" already exists. Please choose a different name.`
1860
+ };
1861
+ }
1862
+
1863
+ return { isValid: true, message: '' };
1864
+ } catch (error) {
1865
+ console.error('[UIManager] Error validating profile name:', error);
1866
+ return { isValid: true, message: '' }; // Don't block on validation errors
1750
1867
  }
1751
1868
  }
1752
1869
 
@@ -366,6 +366,46 @@
366
366
  padding-right: 32px;
367
367
  }
368
368
 
369
+ /* Form input error state */
370
+ .form-input.form-error {
371
+ border-color: var(--danger-color) !important;
372
+ box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.1) !important;
373
+ }
374
+
375
+ .form-input.form-error:focus {
376
+ border-color: var(--danger-color) !important;
377
+ box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.2) !important;
378
+ }
379
+
380
+ /* Form error message */
381
+ .form-error-message {
382
+ font-size: var(--font-size-xs);
383
+ color: var(--danger-color);
384
+ margin-top: var(--spacing-xs);
385
+ display: block;
386
+ font-weight: var(--font-weight-medium);
387
+ animation: slideDown 0.2s ease-out;
388
+ }
389
+
390
+ .profile-name-error {
391
+ background-color: rgba(220, 53, 69, 0.05);
392
+ padding: var(--spacing-xs) var(--spacing-sm);
393
+ border-radius: var(--radius-sm);
394
+ border: 1px solid rgba(220, 53, 69, 0.2);
395
+ }
396
+
397
+ @keyframes slideDown {
398
+ from {
399
+ opacity: 0;
400
+ transform: translateY(-10px);
401
+ }
402
+ to {
403
+ opacity: 1;
404
+ transform: translateY(0);
405
+ }
406
+ }
407
+
408
+ /* Legacy form error class for backwards compatibility */
369
409
  .form-error {
370
410
  font-size: var(--font-size-xs);
371
411
  color: var(--danger-color);
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vibesurf
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: VibeSurf: A powerful browser assistant for vibe surfing
5
5
  Author: Shao Warm
6
6
  License: Apache-2.0
@@ -36,12 +36,17 @@ Requires-Dist: python-dotenv>=1.0.0
36
36
  Requires-Dist: sqlalchemy>=2.0.43
37
37
  Requires-Dist: aiosqlite>=0.21.0
38
38
  Requires-Dist: rich>=13.0.0
39
+ Requires-Dist: greenlet>=3.2.4
39
40
  Dynamic: license-file
40
41
 
41
42
  # VibeSurf: A powerful browser assistant for vibe surfing
43
+ [![Discord](https://img.shields.io/discord/1303749220842340412?color=7289DA&label=Discord&logo=discord&logoColor=white)](https://discord.gg/TXNnP9gJ)
44
+ [![WarmShao](https://img.shields.io/twitter/follow/warmshao?style=social)](https://x.com/warmshao)
42
45
 
43
46
  VibeSurf is an open-source AI agentic browser that revolutionizes browser automation and research.
44
47
 
48
+ If you're as excited about open-source AI browsing as I am, give it a star! ⭐
49
+
45
50
  ## ✨ Key Features
46
51
 
47
52
  - 🧠 **Advanced AI Automation**: Beyond browser automation, VibeSurf performs deep research, intelligent crawling, content summarization, and more to exploration.
@@ -18,3 +18,4 @@ python-dotenv>=1.0.0
18
18
  sqlalchemy>=2.0.43
19
19
  aiosqlite>=0.21.0
20
20
  rich>=13.0.0
21
+ greenlet>=3.2.4
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes