vibesurf 0.1.21__py3-none-any.whl → 0.1.23__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 +11 -0
- vibe_surf/agents/vibe_surf_agent.py +13 -2
- vibe_surf/backend/api/agent.py +38 -0
- vibe_surf/backend/api/task.py +1 -1
- vibe_surf/backend/main.py +2 -0
- vibe_surf/browser/agent_browser_session.py +7 -6
- vibe_surf/chrome_extension/background.js +82 -51
- vibe_surf/chrome_extension/manifest.json +2 -11
- vibe_surf/chrome_extension/scripts/api-client.js +5 -0
- vibe_surf/chrome_extension/scripts/file-manager.js +53 -12
- vibe_surf/chrome_extension/scripts/main.js +46 -11
- vibe_surf/chrome_extension/scripts/session-manager.js +30 -4
- vibe_surf/chrome_extension/scripts/ui-manager.js +623 -43
- vibe_surf/chrome_extension/sidepanel.html +13 -1
- vibe_surf/chrome_extension/styles/input.css +115 -0
- vibe_surf/tools/browser_use_tools.py +0 -90
- vibe_surf/tools/vibesurf_registry.py +52 -0
- vibe_surf/tools/vibesurf_tools.py +954 -5
- vibe_surf/tools/views.py +60 -0
- {vibesurf-0.1.21.dist-info → vibesurf-0.1.23.dist-info}/METADATA +2 -2
- {vibesurf-0.1.21.dist-info → vibesurf-0.1.23.dist-info}/RECORD +27 -25
- {vibesurf-0.1.21.dist-info → vibesurf-0.1.23.dist-info}/WHEEL +0 -0
- {vibesurf-0.1.21.dist-info → vibesurf-0.1.23.dist-info}/entry_points.txt +0 -0
- {vibesurf-0.1.21.dist-info → vibesurf-0.1.23.dist-info}/licenses/LICENSE +0 -0
- {vibesurf-0.1.21.dist-info → vibesurf-0.1.23.dist-info}/top_level.txt +0 -0
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
<textarea
|
|
155
155
|
id="task-input"
|
|
156
156
|
class="task-input"
|
|
157
|
-
placeholder="
|
|
157
|
+
placeholder="Ask anything (/ for skills, @ to specify tab)"
|
|
158
158
|
rows="3"></textarea>
|
|
159
159
|
<!-- Tab Selection Dropdown -->
|
|
160
160
|
<div id="tab-selector-dropdown" class="tab-selector-dropdown hidden">
|
|
@@ -173,6 +173,18 @@
|
|
|
173
173
|
</div>
|
|
174
174
|
</div>
|
|
175
175
|
</div>
|
|
176
|
+
|
|
177
|
+
<!-- Skill Selection Dropdown -->
|
|
178
|
+
<div id="skill-selector-dropdown" class="skill-selector-dropdown hidden">
|
|
179
|
+
<div class="skill-selector-header">
|
|
180
|
+
<span class="skill-selector-title">Select Skills</span>
|
|
181
|
+
</div>
|
|
182
|
+
<div class="skill-selector-content">
|
|
183
|
+
<div id="skill-options-list" class="skill-options-list">
|
|
184
|
+
<!-- Skill options will be populated here -->
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
176
188
|
<div class="input-actions">
|
|
177
189
|
<button id="voice-record-btn" class="action-btn voice-record-btn" title="Voice Input">
|
|
178
190
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
@@ -642,4 +642,119 @@ select.task-running-disabled {
|
|
|
642
642
|
.voice-record-btn.recording .recording-duration {
|
|
643
643
|
opacity: 1;
|
|
644
644
|
visibility: visible;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/* Skill Selection Dropdown */
|
|
648
|
+
.skill-selector-dropdown {
|
|
649
|
+
position: fixed !important;
|
|
650
|
+
z-index: 1000;
|
|
651
|
+
background: var(--bg-primary);
|
|
652
|
+
border: 1px solid var(--border-color);
|
|
653
|
+
border-radius: var(--radius-md);
|
|
654
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
655
|
+
max-height: 300px;
|
|
656
|
+
overflow: hidden;
|
|
657
|
+
display: none;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
.skill-selector-header {
|
|
661
|
+
display: flex;
|
|
662
|
+
align-items: center;
|
|
663
|
+
justify-content: space-between;
|
|
664
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
665
|
+
background: var(--bg-secondary);
|
|
666
|
+
border-bottom: 1px solid var(--border-color);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.skill-selector-title {
|
|
670
|
+
font-size: var(--font-size-sm);
|
|
671
|
+
font-weight: var(--font-weight-medium);
|
|
672
|
+
color: var(--text-primary);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.skill-selector-close {
|
|
676
|
+
width: 24px;
|
|
677
|
+
height: 24px;
|
|
678
|
+
border: none;
|
|
679
|
+
background: none;
|
|
680
|
+
font-size: 18px;
|
|
681
|
+
color: var(--text-muted);
|
|
682
|
+
cursor: pointer;
|
|
683
|
+
border-radius: var(--radius-sm);
|
|
684
|
+
display: flex;
|
|
685
|
+
align-items: center;
|
|
686
|
+
justify-content: center;
|
|
687
|
+
transition: all var(--transition-fast);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.skill-selector-close:hover {
|
|
691
|
+
background: var(--bg-hover);
|
|
692
|
+
color: var(--text-primary);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
.skill-selector-content {
|
|
696
|
+
max-height: 200px;
|
|
697
|
+
overflow-y: auto;
|
|
698
|
+
padding: var(--spacing-xs);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.skill-options-list {
|
|
702
|
+
display: flex;
|
|
703
|
+
flex-direction: column;
|
|
704
|
+
gap: 2px;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.skill-option {
|
|
708
|
+
display: flex;
|
|
709
|
+
align-items: center;
|
|
710
|
+
gap: var(--spacing-sm);
|
|
711
|
+
padding: var(--spacing-xs) var(--spacing-sm);
|
|
712
|
+
cursor: pointer;
|
|
713
|
+
border-radius: var(--radius-sm);
|
|
714
|
+
transition: background-color var(--transition-fast);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.skill-option:hover {
|
|
718
|
+
background: var(--bg-hover);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
.skill-option.selected {
|
|
722
|
+
background: rgba(0, 122, 204, 0.1);
|
|
723
|
+
border: 1px solid rgba(0, 122, 204, 0.3);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
.skill-name {
|
|
727
|
+
flex: 1;
|
|
728
|
+
font-size: var(--font-size-sm);
|
|
729
|
+
color: var(--text-primary);
|
|
730
|
+
overflow: hidden;
|
|
731
|
+
text-overflow: ellipsis;
|
|
732
|
+
white-space: nowrap;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.skill-description {
|
|
736
|
+
font-size: var(--font-size-xs);
|
|
737
|
+
color: var(--text-muted);
|
|
738
|
+
margin-top: 2px;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/* Skill selection indicator in textarea */
|
|
742
|
+
.task-input.has-selected-skills {
|
|
743
|
+
border-color: var(--accent-color);
|
|
744
|
+
background: rgba(40, 167, 69, 0.02);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/* Selected skills display */
|
|
748
|
+
.selected-skills-indicator {
|
|
749
|
+
position: absolute;
|
|
750
|
+
bottom: 8px;
|
|
751
|
+
left: 8px;
|
|
752
|
+
background: var(--accent-color);
|
|
753
|
+
color: white;
|
|
754
|
+
padding: 2px 6px;
|
|
755
|
+
border-radius: var(--radius-sm);
|
|
756
|
+
font-size: 10px;
|
|
757
|
+
font-weight: var(--font-weight-medium);
|
|
758
|
+
pointer-events: none;
|
|
759
|
+
z-index: 2;
|
|
645
760
|
}
|
|
@@ -462,96 +462,6 @@ class BrowserUseTools(Tools, VibeSurfTools):
|
|
|
462
462
|
logger.error(f'Failed to switch tab: {str(e)}')
|
|
463
463
|
return ActionResult(error=f'Failed to switch to tab {params.tab_id or params.url}: {str(e)}')
|
|
464
464
|
|
|
465
|
-
@self.registry.action(
|
|
466
|
-
"""Extract structured, semantic data (e.g. product description, price, all information about XYZ) from the current webpage based on a textual query.
|
|
467
|
-
This tool takes the entire markdown of the page and extracts the query from it.
|
|
468
|
-
Set extract_links=True ONLY if your query requires extracting links/URLs from the page.
|
|
469
|
-
Only use this for specific queries for information retrieval from the page. Don't use this to get interactive elements - the tool does not see HTML elements, only the markdown.
|
|
470
|
-
Note: Extracting from the same page will yield the same results unless more content is loaded (e.g., through scrolling for dynamic content, or new page is loaded) - so one extraction per page state is sufficient. If you want to scrape a listing of many elements always first scroll a lot until the page end to load everything and then call this tool in the end.
|
|
471
|
-
If you called extract_structured_data in the last step and the result was not good (e.g. because of antispam protection), use the current browser state and scrolling to get the information, dont call extract_structured_data again.
|
|
472
|
-
""",
|
|
473
|
-
param_model=ExtractionAction
|
|
474
|
-
)
|
|
475
|
-
async def extract_structured_data(
|
|
476
|
-
params: ExtractionAction,
|
|
477
|
-
browser_session: AgentBrowserSession,
|
|
478
|
-
page_extraction_llm: BaseChatModel,
|
|
479
|
-
file_system: FileSystem,
|
|
480
|
-
):
|
|
481
|
-
try:
|
|
482
|
-
# Use AgentBrowserSession's direct method to get HTML content
|
|
483
|
-
target_id = None
|
|
484
|
-
if params.tab_id:
|
|
485
|
-
target_id = await browser_session.get_target_id_from_tab_id(params.tab_id)
|
|
486
|
-
page_html = await browser_session.get_html_content(target_id)
|
|
487
|
-
|
|
488
|
-
# Simple markdown conversion
|
|
489
|
-
import re
|
|
490
|
-
import markdownify
|
|
491
|
-
|
|
492
|
-
if params.extract_links:
|
|
493
|
-
content = markdownify.markdownify(page_html, heading_style='ATX', bullets='-')
|
|
494
|
-
else:
|
|
495
|
-
content = markdownify.markdownify(page_html, heading_style='ATX', bullets='-', strip=['a'])
|
|
496
|
-
# Remove all markdown links and images, keep only the text
|
|
497
|
-
content = re.sub(r'!\[.*?\]\([^)]*\)', '', content, flags=re.MULTILINE | re.DOTALL) # Remove images
|
|
498
|
-
content = re.sub(
|
|
499
|
-
r'\[([^\]]*)\]\([^)]*\)', r'\1', content, flags=re.MULTILINE | re.DOTALL
|
|
500
|
-
) # Convert [text](url) -> text
|
|
501
|
-
|
|
502
|
-
# Remove weird positioning artifacts
|
|
503
|
-
content = re.sub(r'❓\s*\[\d+\]\s*\w+.*?Position:.*?Size:.*?\n?', '', content,
|
|
504
|
-
flags=re.MULTILINE | re.DOTALL)
|
|
505
|
-
content = re.sub(r'Primary: UNKNOWN\n\nNo specific evidence found', '', content,
|
|
506
|
-
flags=re.MULTILINE | re.DOTALL)
|
|
507
|
-
content = re.sub(r'UNKNOWN CONFIDENCE', '', content, flags=re.MULTILINE | re.DOTALL)
|
|
508
|
-
content = re.sub(r'!\[\]\(\)', '', content, flags=re.MULTILINE | re.DOTALL)
|
|
509
|
-
|
|
510
|
-
# Simple truncation to 30k characters
|
|
511
|
-
if len(content) > 30000:
|
|
512
|
-
content = content[:30000] + '\n\n... [Content truncated at 30k characters] ...'
|
|
513
|
-
|
|
514
|
-
# Simple prompt
|
|
515
|
-
prompt = f"""Extract the requested information from this webpage content.
|
|
516
|
-
|
|
517
|
-
Query: {params.query}
|
|
518
|
-
|
|
519
|
-
Webpage Content:
|
|
520
|
-
{content}
|
|
521
|
-
|
|
522
|
-
Provide the extracted information in a clear, structured format."""
|
|
523
|
-
|
|
524
|
-
from browser_use.llm.messages import UserMessage
|
|
525
|
-
|
|
526
|
-
response = await asyncio.wait_for(
|
|
527
|
-
page_extraction_llm.ainvoke([UserMessage(content=prompt)]),
|
|
528
|
-
timeout=120.0,
|
|
529
|
-
)
|
|
530
|
-
|
|
531
|
-
extracted_content = f'Query: {params.query}\nExtracted Content:\n{response.completion}'
|
|
532
|
-
|
|
533
|
-
# Simple memory handling
|
|
534
|
-
if len(extracted_content) < 1000:
|
|
535
|
-
memory = extracted_content
|
|
536
|
-
include_extracted_content_only_once = False
|
|
537
|
-
else:
|
|
538
|
-
save_result = await file_system.save_extracted_content(extracted_content)
|
|
539
|
-
current_url = await browser_session.get_current_page_url()
|
|
540
|
-
memory = (
|
|
541
|
-
f'Extracted content from {current_url} for query: {params.query}\nContent saved to file system: {save_result}'
|
|
542
|
-
)
|
|
543
|
-
include_extracted_content_only_once = True
|
|
544
|
-
|
|
545
|
-
logger.info(f'📄 {memory}')
|
|
546
|
-
return ActionResult(
|
|
547
|
-
extracted_content=extracted_content,
|
|
548
|
-
include_extracted_content_only_once=include_extracted_content_only_once,
|
|
549
|
-
long_term_memory=memory,
|
|
550
|
-
)
|
|
551
|
-
except Exception as e:
|
|
552
|
-
logger.debug(f'Error extracting content: {e}')
|
|
553
|
-
raise RuntimeError(str(e))
|
|
554
|
-
|
|
555
465
|
@self.registry.action(
|
|
556
466
|
'Take a screenshot of the current page and save it to the file system',
|
|
557
467
|
param_model=NoParamsAction
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from browser_use.tools.registry.service import Registry, Context
|
|
2
|
+
import asyncio
|
|
3
|
+
import functools
|
|
4
|
+
import inspect
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from inspect import Parameter, iscoroutinefunction, signature
|
|
9
|
+
from types import UnionType
|
|
10
|
+
from typing import Any, Generic, Optional, TypeVar, Union, get_args, get_origin
|
|
11
|
+
|
|
12
|
+
import pyotp
|
|
13
|
+
from pydantic import BaseModel, Field, RootModel, create_model
|
|
14
|
+
|
|
15
|
+
from browser_use.browser import BrowserSession
|
|
16
|
+
from browser_use.filesystem.file_system import FileSystem
|
|
17
|
+
from browser_use.llm.base import BaseChatModel
|
|
18
|
+
from browser_use.observability import observe_debug
|
|
19
|
+
from browser_use.telemetry.service import ProductTelemetry
|
|
20
|
+
from browser_use.tools.registry.views import (
|
|
21
|
+
ActionModel,
|
|
22
|
+
ActionRegistry,
|
|
23
|
+
RegisteredAction,
|
|
24
|
+
SpecialActionParameters,
|
|
25
|
+
)
|
|
26
|
+
from browser_use.utils import is_new_tab_page, match_url_with_domain_pattern, time_execution_async
|
|
27
|
+
|
|
28
|
+
from vibe_surf.logger import get_logger
|
|
29
|
+
from vibe_surf.browser.browser_manager import BrowserManager
|
|
30
|
+
|
|
31
|
+
logger = get_logger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class VibeSurfRegistry(Registry):
|
|
35
|
+
def _get_special_param_types(self) -> dict[str, type | UnionType | None]:
|
|
36
|
+
"""Get the expected types for special parameters from SpecialActionParameters"""
|
|
37
|
+
# Manually define the expected types to avoid issues with Optional handling.
|
|
38
|
+
# we should try to reduce this list to 0 if possible, give as few standardized objects to all the actions
|
|
39
|
+
# but each driver should decide what is relevant to expose the action methods,
|
|
40
|
+
# e.g. CDP client, 2fa code getters, sensitive_data wrappers, other context, etc.
|
|
41
|
+
return {
|
|
42
|
+
'context': None, # Context is a TypeVar, so we can't validate type
|
|
43
|
+
'browser_session': BrowserSession,
|
|
44
|
+
'page_url': str,
|
|
45
|
+
'cdp_client': None, # CDPClient type from cdp_use, but we don't import it here
|
|
46
|
+
'page_extraction_llm': BaseChatModel,
|
|
47
|
+
'available_file_paths': list,
|
|
48
|
+
'has_sensitive_data': bool,
|
|
49
|
+
'file_system': FileSystem,
|
|
50
|
+
'llm': BaseChatModel,
|
|
51
|
+
'browser_manager': BrowserManager
|
|
52
|
+
}
|