mirror of
https://github.com/mistralai/mistral-vibe
synced 2026-04-30 19:38:04 +02:00
Co-Authored-By: Quentin Torroba <quentin.torroba@mistral.ai> Co-Authored-By: Laure Hugo <laure.hugo@mistral.ai> Co-Authored-By: Benjamin Trom <benjamin.trom@mistral.ai> Co-Authored-By: Mathias Gesbert <mathias.gesbert@ext.mistral.ai> Co-Authored-By: Michel Thomazo <michel.thomazo@mistral.ai> Co-Authored-By: Clément Drouin <clement.drouin@mistral.ai> Co-Authored-By: Vincent Guilloux <vincent.guilloux@mistral.ai> Co-Authored-By: Valentin Berard <val@mistral.ai> Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
157 lines
4.9 KiB
Python
157 lines
4.9 KiB
Python
from __future__ import annotations
|
||
|
||
from typing import TYPE_CHECKING, ClassVar, TypedDict
|
||
|
||
from textual import events
|
||
from textual.app import ComposeResult
|
||
from textual.binding import Binding, BindingType
|
||
from textual.containers import Container, Vertical
|
||
from textual.message import Message
|
||
from textual.theme import BUILTIN_THEMES
|
||
from textual.widgets import Static
|
||
|
||
if TYPE_CHECKING:
|
||
from vibe.core.config import VibeConfig
|
||
|
||
THEMES = sorted(k for k in BUILTIN_THEMES if k != "textual-ansi")
|
||
|
||
|
||
class SettingDefinition(TypedDict):
|
||
key: str
|
||
label: str
|
||
type: str
|
||
options: list[str]
|
||
value: str
|
||
|
||
|
||
class ConfigApp(Container):
|
||
can_focus = True
|
||
can_focus_children = False
|
||
|
||
BINDINGS: ClassVar[list[BindingType]] = [
|
||
Binding("up", "move_up", "Up", show=False),
|
||
Binding("down", "move_down", "Down", show=False),
|
||
Binding("space", "toggle_setting", "Toggle", show=False),
|
||
Binding("enter", "cycle", "Next", show=False),
|
||
]
|
||
|
||
class SettingChanged(Message):
|
||
def __init__(self, key: str, value: str) -> None:
|
||
super().__init__()
|
||
self.key = key
|
||
self.value = value
|
||
|
||
class ConfigClosed(Message):
|
||
def __init__(self, changes: dict[str, str]) -> None:
|
||
super().__init__()
|
||
self.changes = changes
|
||
|
||
def __init__(self, config: VibeConfig) -> None:
|
||
super().__init__(id="config-app")
|
||
self.config = config
|
||
self.selected_index = 0
|
||
self.changes: dict[str, str] = {}
|
||
|
||
self.settings: list[SettingDefinition] = [
|
||
{
|
||
"key": "active_model",
|
||
"label": "Model",
|
||
"type": "cycle",
|
||
"options": [m.alias for m in self.config.models],
|
||
"value": self.config.active_model,
|
||
},
|
||
{
|
||
"key": "textual_theme",
|
||
"label": "Theme",
|
||
"type": "cycle",
|
||
"options": THEMES,
|
||
"value": self.config.textual_theme,
|
||
},
|
||
]
|
||
|
||
self.title_widget: Static | None = None
|
||
self.setting_widgets: list[Static] = []
|
||
self.help_widget: Static | None = None
|
||
|
||
def compose(self) -> ComposeResult:
|
||
with Vertical(id="config-content"):
|
||
self.title_widget = Static("Settings", classes="settings-title")
|
||
yield self.title_widget
|
||
|
||
yield Static("")
|
||
|
||
for _ in self.settings:
|
||
widget = Static("", classes="settings-option")
|
||
self.setting_widgets.append(widget)
|
||
yield widget
|
||
|
||
yield Static("")
|
||
|
||
self.help_widget = Static(
|
||
"↑↓ navigate Space/Enter toggle ESC exit", classes="settings-help"
|
||
)
|
||
yield self.help_widget
|
||
|
||
def on_mount(self) -> None:
|
||
self._update_display()
|
||
self.focus()
|
||
|
||
def _update_display(self) -> None:
|
||
for i, (setting, widget) in enumerate(
|
||
zip(self.settings, self.setting_widgets, strict=True)
|
||
):
|
||
is_selected = i == self.selected_index
|
||
cursor = "› " if is_selected else " "
|
||
|
||
label: str = setting["label"]
|
||
value: str = self.changes.get(setting["key"], setting["value"])
|
||
|
||
text = f"{cursor}{label}: {value}"
|
||
|
||
widget.update(text)
|
||
|
||
widget.remove_class("settings-cursor-selected")
|
||
widget.remove_class("settings-value-cycle-selected")
|
||
widget.remove_class("settings-value-cycle-unselected")
|
||
|
||
if is_selected:
|
||
widget.add_class("settings-value-cycle-selected")
|
||
else:
|
||
widget.add_class("settings-value-cycle-unselected")
|
||
|
||
def action_move_up(self) -> None:
|
||
self.selected_index = (self.selected_index - 1) % len(self.settings)
|
||
self._update_display()
|
||
|
||
def action_move_down(self) -> None:
|
||
self.selected_index = (self.selected_index + 1) % len(self.settings)
|
||
self._update_display()
|
||
|
||
def action_toggle_setting(self) -> None:
|
||
setting = self.settings[self.selected_index]
|
||
key: str = setting["key"]
|
||
current: str = self.changes.get(key, setting["value"])
|
||
|
||
options: list[str] = setting["options"]
|
||
try:
|
||
current_idx = options.index(current)
|
||
next_idx = (current_idx + 1) % len(options)
|
||
new_value: str = options[next_idx]
|
||
except (ValueError, IndexError):
|
||
new_value: str = options[0] if options else current
|
||
|
||
self.changes[key] = new_value
|
||
|
||
self.post_message(self.SettingChanged(key=key, value=new_value))
|
||
|
||
self._update_display()
|
||
|
||
def action_cycle(self) -> None:
|
||
self.action_toggle_setting()
|
||
|
||
def action_close(self) -> None:
|
||
self.post_message(self.ConfigClosed(changes=self.changes.copy()))
|
||
|
||
def on_blur(self, event: events.Blur) -> None:
|
||
self.call_after_refresh(self.focus)
|