mirror of
https://github.com/mistralai/mistral-vibe
synced 2026-04-26 01:24:55 +02:00
Co-Authored-By: Quentin Torroba <quentin.torroba@mistral.ai> Co-Authored-By: Michel Thomazo <michel.thomazo@mistral.ai> Co-Authored-By: Kracekumar <kracethekingmaker@gmail.com>
201 lines
6.5 KiB
Python
201 lines
6.5 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from textual.app import ComposeResult
|
|
from textual.containers import Horizontal, Vertical
|
|
from textual.widgets import Markdown, Static
|
|
from textual.widgets._markdown import MarkdownStream
|
|
|
|
|
|
class NonSelectableStatic(Static):
|
|
@property
|
|
def text_selection(self) -> None:
|
|
return None
|
|
|
|
@text_selection.setter
|
|
def text_selection(self, value: Any) -> None:
|
|
pass
|
|
|
|
def get_selection(self, selection: Any) -> None:
|
|
return None
|
|
|
|
|
|
class ExpandingBorder(NonSelectableStatic):
|
|
def render(self) -> str:
|
|
height = self.size.height
|
|
return "\n".join(["⎢"] * (height - 1) + ["⎣"])
|
|
|
|
def on_resize(self) -> None:
|
|
self.refresh()
|
|
|
|
|
|
class UserMessage(Static):
|
|
def __init__(self, content: str, pending: bool = False) -> None:
|
|
super().__init__()
|
|
self.add_class("user-message")
|
|
self._content = content
|
|
self._pending = pending
|
|
|
|
def compose(self) -> ComposeResult:
|
|
with Horizontal(classes="user-message-container"):
|
|
yield NonSelectableStatic("> ", classes="user-message-prompt")
|
|
yield Static(self._content, markup=False, classes="user-message-content")
|
|
if self._pending:
|
|
self.add_class("pending")
|
|
|
|
async def set_pending(self, pending: bool) -> None:
|
|
if pending == self._pending:
|
|
return
|
|
|
|
self._pending = pending
|
|
|
|
if pending:
|
|
self.add_class("pending")
|
|
return
|
|
|
|
self.remove_class("pending")
|
|
|
|
|
|
class AssistantMessage(Static):
|
|
def __init__(self, content: str) -> None:
|
|
super().__init__()
|
|
self.add_class("assistant-message")
|
|
self._content = content
|
|
self._markdown: Markdown | None = None
|
|
self._stream: MarkdownStream | None = None
|
|
|
|
def compose(self) -> ComposeResult:
|
|
with Horizontal(classes="assistant-message-container"):
|
|
yield NonSelectableStatic("● ", classes="assistant-message-dot")
|
|
with Vertical(classes="assistant-message-content"):
|
|
markdown = Markdown("")
|
|
self._markdown = markdown
|
|
yield markdown
|
|
|
|
def _get_markdown(self) -> Markdown:
|
|
if self._markdown is None:
|
|
self._markdown = self.query_one(Markdown)
|
|
return self._markdown
|
|
|
|
def _ensure_stream(self) -> MarkdownStream:
|
|
if self._stream is None:
|
|
self._stream = Markdown.get_stream(self._get_markdown())
|
|
return self._stream
|
|
|
|
async def append_content(self, content: str) -> None:
|
|
if not content:
|
|
return
|
|
|
|
self._content += content
|
|
stream = self._ensure_stream()
|
|
await stream.write(content)
|
|
|
|
async def write_initial_content(self) -> None:
|
|
if self._content:
|
|
stream = self._ensure_stream()
|
|
await stream.write(self._content)
|
|
|
|
async def stop_stream(self) -> None:
|
|
if self._stream is None:
|
|
return
|
|
|
|
await self._stream.stop()
|
|
self._stream = None
|
|
|
|
|
|
class UserCommandMessage(Static):
|
|
def __init__(self, content: str) -> None:
|
|
super().__init__()
|
|
self.add_class("user-command-message")
|
|
self._content = content
|
|
|
|
def compose(self) -> ComposeResult:
|
|
with Horizontal(classes="user-command-container"):
|
|
yield ExpandingBorder(classes="user-command-border")
|
|
with Vertical(classes="user-command-content"):
|
|
yield Markdown(self._content)
|
|
|
|
|
|
class InterruptMessage(Static):
|
|
def __init__(self) -> None:
|
|
super().__init__()
|
|
self.add_class("interrupt-message")
|
|
|
|
def compose(self) -> ComposeResult:
|
|
with Horizontal(classes="interrupt-container"):
|
|
yield ExpandingBorder(classes="interrupt-border")
|
|
yield Static(
|
|
"Interrupted · What should Vibe do instead?",
|
|
markup=False,
|
|
classes="interrupt-content",
|
|
)
|
|
|
|
|
|
class BashOutputMessage(Static):
|
|
def __init__(self, command: str, cwd: str, output: str, exit_code: int) -> None:
|
|
super().__init__()
|
|
self.add_class("bash-output-message")
|
|
self._command = command
|
|
self._cwd = cwd
|
|
self._output = output
|
|
self._exit_code = exit_code
|
|
|
|
def compose(self) -> ComposeResult:
|
|
with Vertical(classes="bash-output-container"):
|
|
with Horizontal(classes="bash-cwd-line"):
|
|
yield Static(self._cwd, markup=False, classes="bash-cwd")
|
|
yield Static("", classes="bash-cwd-spacer")
|
|
if self._exit_code == 0:
|
|
yield Static("✓", classes="bash-exit-success")
|
|
else:
|
|
yield Static("✗", classes="bash-exit-failure")
|
|
yield Static(f" ({self._exit_code})", classes="bash-exit-code")
|
|
with Horizontal(classes="bash-command-line"):
|
|
yield Static("> ", classes="bash-chevron")
|
|
yield Static(self._command, markup=False, classes="bash-command")
|
|
yield Static("", classes="bash-command-spacer")
|
|
yield Static(self._output, markup=False, classes="bash-output")
|
|
|
|
|
|
class ErrorMessage(Static):
|
|
def __init__(self, error: str, collapsed: bool = True) -> None:
|
|
super().__init__()
|
|
self.add_class("error-message")
|
|
self._error = error
|
|
self.collapsed = collapsed
|
|
self._content_widget: Static | None = None
|
|
|
|
def compose(self) -> ComposeResult:
|
|
with Horizontal(classes="error-container"):
|
|
yield ExpandingBorder(classes="error-border")
|
|
self._content_widget = Static(
|
|
self._get_text(), markup=False, classes="error-content"
|
|
)
|
|
yield self._content_widget
|
|
|
|
def _get_text(self) -> str:
|
|
if self.collapsed:
|
|
return "Error. (ctrl+o to expand)"
|
|
return f"Error: {self._error}"
|
|
|
|
def set_collapsed(self, collapsed: bool) -> None:
|
|
if self.collapsed == collapsed:
|
|
return
|
|
|
|
self.collapsed = collapsed
|
|
if self._content_widget:
|
|
self._content_widget.update(self._get_text())
|
|
|
|
|
|
class WarningMessage(Static):
|
|
def __init__(self, message: str) -> None:
|
|
super().__init__()
|
|
self.add_class("warning-message")
|
|
self._message = message
|
|
|
|
def compose(self) -> ComposeResult:
|
|
with Horizontal(classes="warning-container"):
|
|
yield ExpandingBorder(classes="warning-border")
|
|
yield Static(self._message, markup=False, classes="warning-content")
|