Fix escape issues causing tui to crash (#36)
This commit is contained in:
@@ -9,6 +9,8 @@ import threading
|
||||
from collections.abc import Callable
|
||||
from typing import Any, ClassVar
|
||||
|
||||
from rich.markup import MarkupError
|
||||
from rich.markup import escape as rich_escape
|
||||
from textual import events, on
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.binding import Binding
|
||||
@@ -24,7 +26,7 @@ from strix.llm.config import LLMConfig
|
||||
|
||||
|
||||
def escape_markup(text: str) -> str:
|
||||
return text.replace("[", "\\[").replace("]", "\\]")
|
||||
return rich_escape(text)
|
||||
|
||||
|
||||
class ChatTextArea(TextArea): # type: ignore[misc]
|
||||
@@ -483,7 +485,7 @@ class StrixCLIApp(App): # type: ignore[misc]
|
||||
self._displayed_events = current_event_ids
|
||||
|
||||
chat_display = self.query_one("#chat_display", Static)
|
||||
chat_display.update(content)
|
||||
self._update_static_content_safe(chat_display, content)
|
||||
|
||||
chat_display.set_classes(css_class)
|
||||
|
||||
@@ -952,7 +954,7 @@ class StrixCLIApp(App): # type: ignore[misc]
|
||||
content = "\n".join(lines)
|
||||
|
||||
lines = content.split("\n")
|
||||
bordered_lines = [f"[{color}]▍[/{color}] {line}" for line in lines]
|
||||
bordered_lines = [f"[{color}]▍[/] {line}" for line in lines]
|
||||
return "\n".join(bordered_lines)
|
||||
|
||||
@on(Tree.NodeHighlighted) # type: ignore[misc]
|
||||
@@ -1141,6 +1143,15 @@ class StrixCLIApp(App): # type: ignore[misc]
|
||||
else:
|
||||
return True
|
||||
|
||||
def _update_static_content_safe(self, widget: Static, content: str) -> None:
|
||||
try:
|
||||
widget.update(content)
|
||||
except MarkupError:
|
||||
try:
|
||||
widget.update(rich_escape(content))
|
||||
except Exception:
|
||||
widget.update(rich_escape(str(content)))
|
||||
|
||||
|
||||
async def run_strix_cli(args: argparse.Namespace) -> None:
|
||||
app = StrixCLIApp(args)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, ClassVar
|
||||
|
||||
from rich.markup import escape as rich_escape
|
||||
from textual.widgets import Static
|
||||
|
||||
|
||||
@@ -16,7 +17,7 @@ class BaseToolRenderer(ABC):
|
||||
|
||||
@classmethod
|
||||
def escape_markup(cls, text: str) -> str:
|
||||
return text.replace("[", "\\[").replace("]", "\\]")
|
||||
return rich_escape(text)
|
||||
|
||||
@classmethod
|
||||
def format_args(cls, args: dict[str, Any], max_length: int = 500) -> str:
|
||||
@@ -47,10 +48,10 @@ class BaseToolRenderer(ABC):
|
||||
@classmethod
|
||||
def get_status_icon(cls, status: str) -> str:
|
||||
status_icons = {
|
||||
"running": "[#f59e0b]●[/#f59e0b] In progress...",
|
||||
"completed": "[#22c55e]✓[/#22c55e] Done",
|
||||
"failed": "[#dc2626]✗[/#dc2626] Failed",
|
||||
"error": "[#dc2626]✗[/#dc2626] Error",
|
||||
"running": "[#f59e0b]●[/] In progress...",
|
||||
"completed": "[#22c55e]✓[/] Done",
|
||||
"failed": "[#dc2626]✗[/] Failed",
|
||||
"error": "[#dc2626]✗[/] Error",
|
||||
}
|
||||
return status_icons.get(status, "[dim]○[/dim] Unknown")
|
||||
|
||||
|
||||
@@ -28,11 +28,11 @@ class ScanStartInfoRenderer(BaseToolRenderer):
|
||||
@classmethod
|
||||
def _build_target_display(cls, target: dict[str, Any]) -> str:
|
||||
if target_url := target.get("target_url"):
|
||||
return f"[bold #22c55e]{target_url}[/]"
|
||||
return f"[bold #22c55e]{cls.escape_markup(target_url)}[/]"
|
||||
if target_repo := target.get("target_repo"):
|
||||
return f"[bold #22c55e]{target_repo}[/]"
|
||||
return f"[bold #22c55e]{cls.escape_markup(target_repo)}[/]"
|
||||
if target_path := target.get("target_path"):
|
||||
return f"[bold #22c55e]{target_path}[/]"
|
||||
return f"[bold #22c55e]{cls.escape_markup(target_path)}[/]"
|
||||
return "[dim]unknown target[/dim]"
|
||||
|
||||
|
||||
@@ -49,9 +49,9 @@ class SubagentStartInfoRenderer(BaseToolRenderer):
|
||||
name = args.get("name", "Unknown Agent")
|
||||
task = args.get("task", "")
|
||||
|
||||
content = f"🤖 Spawned subagent [bold #22c55e]{name}[/]"
|
||||
content = f"🤖 Spawned subagent [bold #22c55e]{cls.escape_markup(name)}[/]"
|
||||
if task:
|
||||
content += f"\n Task: [dim]{task}[/dim]"
|
||||
content += f"\n Task: [dim]{cls.escape_markup(task)}[/dim]"
|
||||
|
||||
css_classes = cls.get_css_classes(status)
|
||||
return Static(content, classes=css_classes)
|
||||
|
||||
@@ -22,9 +22,9 @@ class UserMessageRenderer(BaseToolRenderer):
|
||||
content = content[:297] + "..."
|
||||
|
||||
lines = content.split("\n")
|
||||
bordered_lines = [f"[#3b82f6]▍[/#3b82f6] {line}" for line in lines]
|
||||
bordered_lines = [f"[#3b82f6]▍[/] {line}" for line in lines]
|
||||
bordered_content = "\n".join(bordered_lines)
|
||||
formatted_content = f"[#3b82f6]▍[/#3b82f6] [bold]You:[/]\n{bordered_content}"
|
||||
formatted_content = f"[#3b82f6]▍[/] [bold]You:[/]\n{bordered_content}"
|
||||
|
||||
css_classes = " ".join(cls.css_classes)
|
||||
return Static(formatted_content, classes=css_classes)
|
||||
@@ -38,6 +38,6 @@ class UserMessageRenderer(BaseToolRenderer):
|
||||
content = content[:297] + "..."
|
||||
|
||||
lines = content.split("\n")
|
||||
bordered_lines = [f"[#3b82f6]▍[/#3b82f6] {line}" for line in lines]
|
||||
bordered_lines = [f"[#3b82f6]▍[/] {line}" for line in lines]
|
||||
bordered_content = "\n".join(bordered_lines)
|
||||
return f"[#3b82f6]▍[/#3b82f6] [bold]You:[/]\n{bordered_content}"
|
||||
return f"[#3b82f6]▍[/] [bold]You:[/]\n{bordered_content}"
|
||||
|
||||
Reference in New Issue
Block a user