mirror of
https://github.com/usestrix/strix.git
synced 2026-08-16 09:26:39 +02:00
Add Pygments-based syntax highlighting with native hacker theme: - Python renderer: Python code highlighting - Browser renderer: JavaScript code highlighting - Terminal renderer: Bash command highlighting - File edit renderer: Auto-detect language from file extension, diff-style display
157 lines
5.6 KiB
Python
157 lines
5.6 KiB
Python
from functools import cache
|
|
from typing import Any, ClassVar
|
|
|
|
from pygments.lexers import get_lexer_by_name
|
|
from pygments.styles import get_style_by_name
|
|
from textual.widgets import Static
|
|
|
|
from .base_renderer import BaseToolRenderer
|
|
from .registry import register_tool_renderer
|
|
|
|
|
|
@cache
|
|
def _get_style_colors() -> dict[Any, str]:
|
|
style = get_style_by_name("native")
|
|
return {token: f"#{style_def['color']}" for token, style_def in style if style_def["color"]}
|
|
|
|
|
|
@register_tool_renderer
|
|
class BrowserRenderer(BaseToolRenderer):
|
|
tool_name: ClassVar[str] = "browser_action"
|
|
css_classes: ClassVar[list[str]] = ["tool-call", "browser-tool"]
|
|
|
|
@classmethod
|
|
def _get_token_color(cls, token_type: Any) -> str | None:
|
|
colors = _get_style_colors()
|
|
while token_type:
|
|
if token_type in colors:
|
|
return colors[token_type]
|
|
token_type = token_type.parent
|
|
return None
|
|
|
|
@classmethod
|
|
def _highlight_js(cls, code: str) -> str:
|
|
lexer = get_lexer_by_name("javascript")
|
|
result_parts: list[str] = []
|
|
|
|
for token_type, token_value in lexer.get_tokens(code):
|
|
if not token_value:
|
|
continue
|
|
|
|
escaped_value = cls.escape_markup(token_value)
|
|
color = cls._get_token_color(token_type)
|
|
|
|
if color:
|
|
result_parts.append(f"[{color}]{escaped_value}[/]")
|
|
else:
|
|
result_parts.append(escaped_value)
|
|
|
|
return "".join(result_parts)
|
|
|
|
@classmethod
|
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
|
args = tool_data.get("args", {})
|
|
status = tool_data.get("status", "unknown")
|
|
|
|
action = args.get("action", "unknown")
|
|
|
|
content = cls._build_sleek_content(action, args)
|
|
|
|
css_classes = cls.get_css_classes(status)
|
|
return Static(content, classes=css_classes)
|
|
|
|
@classmethod
|
|
def _build_sleek_content(cls, action: str, args: dict[str, Any]) -> str:
|
|
browser_icon = "🌐"
|
|
|
|
url = args.get("url")
|
|
text = args.get("text")
|
|
js_code = args.get("js_code")
|
|
key = args.get("key")
|
|
file_path = args.get("file_path")
|
|
|
|
if action in [
|
|
"launch",
|
|
"goto",
|
|
"new_tab",
|
|
"type",
|
|
"execute_js",
|
|
"click",
|
|
"double_click",
|
|
"hover",
|
|
"press_key",
|
|
"save_pdf",
|
|
]:
|
|
if action == "launch":
|
|
display_url = cls._format_url(url) if url else None
|
|
message = (
|
|
f"launching {display_url} on browser" if display_url else "launching browser"
|
|
)
|
|
elif action == "goto":
|
|
display_url = cls._format_url(url) if url else None
|
|
message = f"navigating to {display_url}" if display_url else "navigating"
|
|
elif action == "new_tab":
|
|
display_url = cls._format_url(url) if url else None
|
|
message = f"opening tab {display_url}" if display_url else "opening tab"
|
|
elif action == "type":
|
|
display_text = cls._format_text(text) if text else None
|
|
message = f"typing {display_text}" if display_text else "typing"
|
|
elif action == "execute_js":
|
|
display_js = cls._format_js(js_code) if js_code else None
|
|
message = (
|
|
f"executing javascript\n{display_js}" if display_js else "executing javascript"
|
|
)
|
|
elif action == "press_key":
|
|
display_key = cls.escape_markup(key) if key else None
|
|
message = f"pressing key {display_key}" if display_key else "pressing key"
|
|
elif action == "save_pdf":
|
|
display_path = cls.escape_markup(file_path) if file_path else None
|
|
message = f"saving PDF to {display_path}" if display_path else "saving PDF"
|
|
else:
|
|
action_words = {
|
|
"click": "clicking",
|
|
"double_click": "double clicking",
|
|
"hover": "hovering",
|
|
}
|
|
message = cls.escape_markup(action_words[action])
|
|
|
|
return f"{browser_icon} [#06b6d4]{message}[/]"
|
|
|
|
simple_actions = {
|
|
"back": "going back in browser history",
|
|
"forward": "going forward in browser history",
|
|
"scroll_down": "scrolling down",
|
|
"scroll_up": "scrolling up",
|
|
"refresh": "refreshing browser tab",
|
|
"close_tab": "closing browser tab",
|
|
"switch_tab": "switching browser tab",
|
|
"list_tabs": "listing browser tabs",
|
|
"view_source": "viewing page source",
|
|
"get_console_logs": "getting console logs",
|
|
"screenshot": "taking screenshot of browser tab",
|
|
"wait": "waiting...",
|
|
"close": "closing browser",
|
|
}
|
|
|
|
if action in simple_actions:
|
|
return f"{browser_icon} [#06b6d4]{cls.escape_markup(simple_actions[action])}[/]"
|
|
|
|
return f"{browser_icon} [#06b6d4]{cls.escape_markup(action)}[/]"
|
|
|
|
@classmethod
|
|
def _format_url(cls, url: str) -> str:
|
|
if len(url) > 300:
|
|
url = url[:297] + "..."
|
|
return cls.escape_markup(url)
|
|
|
|
@classmethod
|
|
def _format_text(cls, text: str) -> str:
|
|
if len(text) > 200:
|
|
text = text[:197] + "..."
|
|
return cls.escape_markup(text)
|
|
|
|
@classmethod
|
|
def _format_js(cls, js_code: str) -> str:
|
|
code_display = js_code[:2000] + "..." if len(js_code) > 2000 else js_code
|
|
return cls._highlight_js(code_display)
|