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>
This commit is contained in:
Quentin 2025-12-14 00:54:42 +01:00 committed by Mathias Gesbert
parent 661588de0c
commit d8dbeeb31e
91 changed files with 4521 additions and 873 deletions

View file

@ -5,7 +5,7 @@ import sys
from rich import print as rprint
from textual.app import App
from vibe.core.config_path import GLOBAL_ENV_FILE
from vibe.core.paths.global_paths import GLOBAL_ENV_FILE
from vibe.setup.onboarding.screens import (
ApiKeyScreen,
ThemeSelectionScreen,

View file

@ -13,7 +13,7 @@ from textual.widgets import Input, Link, Static
from vibe.cli.clipboard import copy_selection_to_clipboard
from vibe.core.config import VibeConfig
from vibe.core.config_path import GLOBAL_ENV_FILE
from vibe.core.paths.global_paths import GLOBAL_ENV_FILE
from vibe.setup.onboarding.base import OnboardingScreen
PROVIDER_HELP = {

View file

@ -0,0 +1,187 @@
from __future__ import annotations
from pathlib import Path
import tomllib
from typing import Any, ClassVar
from textual import events
from textual.app import App, ComposeResult
from textual.binding import Binding, BindingType
from textual.containers import CenterMiddle, Horizontal
from textual.message import Message
from textual.widgets import Static
from vibe.core.paths.global_paths import GLOBAL_CONFIG_FILE, TRUSTED_FOLDERS_FILE
class TrustDialogQuitException(Exception):
pass
class TrustFolderDialog(CenterMiddle):
can_focus = True
can_focus_children = True
BINDINGS: ClassVar[list[BindingType]] = [
Binding("left", "move_left", "Left", show=False),
Binding("right", "move_right", "Right", show=False),
Binding("enter", "select", "Select", show=False),
Binding("1", "select_1", "Yes", show=False),
Binding("y", "select_1", "Yes", show=False),
Binding("2", "select_2", "No", show=False),
Binding("n", "select_2", "No", show=False),
]
class Trusted(Message):
pass
class Untrusted(Message):
pass
def __init__(self, folder_path: Path, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.folder_path = folder_path
self.selected_option = 0
self.option_widgets: list[Static] = []
def compose(self) -> ComposeResult:
with CenterMiddle(id="trust-dialog"):
yield Static("⚠ Trust this folder?", id="trust-dialog-title")
yield Static(
str(self.folder_path),
id="trust-dialog-path",
classes="trust-dialog-path",
)
yield Static(
"A .vibe/ directory was found here. Should Vibe load custom configuration and tools from it?",
id="trust-dialog-message",
classes="trust-dialog-message",
)
with Horizontal(id="trust-options-container"):
options = ["Yes", "No"]
for idx, text in enumerate(options):
widget = Static(f" {idx + 1}. {text}", classes="trust-option")
self.option_widgets.append(widget)
yield widget
yield Static("← → navigate Enter select", classes="trust-dialog-help")
yield Static(
f"Setting will be saved in: {TRUSTED_FOLDERS_FILE.path}",
id="trust-dialog-save-info",
classes="trust-dialog-save-info",
)
async def on_mount(self) -> None:
self.selected_option = 1 # Default to "No"
self._update_options()
self.focus()
def _update_options(self) -> None:
options = ["Yes", "No"]
if len(self.option_widgets) != len(options):
return
for idx, (text, widget) in enumerate(
zip(options, self.option_widgets, strict=True)
):
is_selected = idx == self.selected_option
cursor = " " if is_selected else " "
option_text = f"{cursor}{text}"
widget.update(option_text)
widget.remove_class("trust-cursor-selected")
widget.remove_class("trust-option-selected")
if is_selected:
widget.add_class("trust-cursor-selected")
else:
widget.add_class("trust-option-selected")
def action_move_left(self) -> None:
self.selected_option = (self.selected_option - 1) % 2
self._update_options()
def action_move_right(self) -> None:
self.selected_option = (self.selected_option + 1) % 2
self._update_options()
def action_select(self) -> None:
self._handle_selection(self.selected_option)
def action_select_1(self) -> None:
self.selected_option = 0
self._handle_selection(0)
def action_select_2(self) -> None:
self.selected_option = 1
self._handle_selection(1)
def _handle_selection(self, option: int) -> None:
match option:
case 0:
self.post_message(self.Trusted())
case 1:
self.post_message(self.Untrusted())
def on_blur(self, event: events.Blur) -> None:
self.call_after_refresh(self.focus)
class TrustFolderApp(App):
CSS_PATH = "trust_folder_dialog.tcss"
BINDINGS: ClassVar[list[BindingType]] = [
Binding("ctrl+q", "quit_without_saving", "Quit", show=False, priority=True),
Binding("ctrl+c", "quit_without_saving", "Quit", show=False, priority=True),
]
def __init__(self, folder_path: Path, **kwargs: Any) -> None:
super().__init__(**kwargs)
self.folder_path = folder_path
self._result: bool | None = None
self._quit_without_saving = False
self._load_theme()
def _load_theme(self) -> None:
config_file = GLOBAL_CONFIG_FILE.path
if not config_file.is_file():
return
try:
with config_file.open("rb") as f:
config_data = tomllib.load(f)
if textual_theme := config_data.get("textual_theme"):
self.theme = textual_theme
except (OSError, tomllib.TOMLDecodeError, KeyError):
pass
def compose(self) -> ComposeResult:
yield TrustFolderDialog(self.folder_path)
def action_quit_without_saving(self) -> None:
self._quit_without_saving = True
self.exit()
def on_trust_folder_dialog_trusted(self, _: TrustFolderDialog.Trusted) -> None:
self._result = True
self.exit()
def on_trust_folder_dialog_untrusted(self, _: TrustFolderDialog.Untrusted) -> None:
self._result = False
self.exit()
def run_trust_dialog(self) -> bool | None:
self.run()
if self._quit_without_saving:
raise TrustDialogQuitException()
return self._result
def ask_trust_folder(folder_path: Path) -> bool | None:
app = TrustFolderApp(folder_path)
return app.run_trust_dialog()

View file

@ -0,0 +1,83 @@
Screen {
align: center middle;
background: $background 80%;
}
#trust-dialog {
width: 70;
max-width: 90%;
min-width: 50;
height: auto;
border: round $foreground-muted;
background: $background;
padding: 1;
}
#trust-dialog-title {
width: 100%;
height: auto;
text-style: bold;
color: $warning;
text-align: center;
margin-bottom: 1;
}
#trust-dialog-path {
width: 100%;
height: auto;
color: $primary;
text-align: center;
text-wrap: wrap;
margin-bottom: 1;
}
#trust-dialog-message {
width: 100%;
height: auto;
color: $foreground;
text-align: center;
text-wrap: wrap;
margin-bottom: 1;
}
#trust-options-container {
width: 100%;
height: auto;
align: center middle;
margin-bottom: 1;
}
.trust-option {
height: auto;
width: auto;
color: $foreground;
margin: 0 3;
content-align: center middle;
}
.trust-cursor-selected {
color: $foreground;
text-style: bold;
}
.trust-option-selected {
color: $foreground;
}
.trust-dialog-help {
width: 100%;
height: auto;
color: $text-muted;
text-align: center;
margin-bottom: 1;
}
.trust-dialog-save-info {
width: 100%;
height: auto;
color: $text-muted;
text-align: center;
text-style: italic;
text-wrap: wrap;
margin-bottom: 1;
}