2.1.0 (#317)
Co-authored-by: Quentin Torroba <quentin.torroba@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: Clément Siriex <clement.sirieix@mistral.ai> Co-authored-by: Kim-Adeline Miguel <kimadeline.miguel@mistral.ai> Co-authored-by: Nicolas Karolak <nicolas@karolak.fr>
This commit is contained in:
parent
9809cfc831
commit
51fecc67d9
176 changed files with 8652 additions and 4451 deletions
|
|
@ -4,34 +4,18 @@ import sys
|
|||
|
||||
from rich import print as rprint
|
||||
from textual.app import App
|
||||
from textual.theme import Theme
|
||||
|
||||
from vibe.cli.textual_ui.terminal_theme import (
|
||||
TERMINAL_THEME_NAME,
|
||||
capture_terminal_theme,
|
||||
)
|
||||
from vibe.core.paths.global_paths import GLOBAL_ENV_FILE
|
||||
from vibe.setup.onboarding.screens import (
|
||||
ApiKeyScreen,
|
||||
ThemeSelectionScreen,
|
||||
WelcomeScreen,
|
||||
)
|
||||
from vibe.setup.onboarding.screens import ApiKeyScreen, WelcomeScreen
|
||||
|
||||
|
||||
class OnboardingApp(App[str | None]):
|
||||
CSS_PATH = "onboarding.tcss"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._terminal_theme: Theme | None = capture_terminal_theme()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
if self._terminal_theme:
|
||||
self.register_theme(self._terminal_theme)
|
||||
self.theme = TERMINAL_THEME_NAME
|
||||
self.theme = "textual-ansi"
|
||||
|
||||
self.install_screen(WelcomeScreen(), "welcome")
|
||||
self.install_screen(ThemeSelectionScreen(), "theme_selection")
|
||||
self.install_screen(ApiKeyScreen(), "api_key")
|
||||
self.push_screen("welcome")
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ OnboardingScreen {
|
|||
}
|
||||
|
||||
WelcomeScreen #enter-hint {
|
||||
color: $text-muted;
|
||||
color: ansi_bright_black;
|
||||
min-width: 16;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -36,102 +36,6 @@ WelcomeScreen #enter-hint.hidden {
|
|||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
Theme Selection Screen
|
||||
============================================================================= */
|
||||
|
||||
#theme-outer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align: center middle;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#theme-content {
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: 1 0;
|
||||
}
|
||||
|
||||
#theme-title {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-bottom: 2;
|
||||
}
|
||||
|
||||
#theme-row {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#nav-hint {
|
||||
color: $text-muted;
|
||||
width: 16;
|
||||
height: 100%;
|
||||
content-align: right middle;
|
||||
padding-right: 2;
|
||||
}
|
||||
|
||||
#theme-list {
|
||||
width: 24;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.theme-item {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.theme-item.selected {
|
||||
color: $text;
|
||||
text-style: bold;
|
||||
border: round #555555;
|
||||
padding: 0 2;
|
||||
}
|
||||
|
||||
.theme-item.fade-1 {
|
||||
text-opacity: 50%;
|
||||
}
|
||||
|
||||
.theme-item.fade-2 {
|
||||
text-opacity: 25%;
|
||||
}
|
||||
|
||||
.theme-item.fade-3 {
|
||||
text-opacity: 10%;
|
||||
}
|
||||
|
||||
ThemeSelectionScreen #enter-hint {
|
||||
color: $text-muted;
|
||||
width: 16;
|
||||
height: 100%;
|
||||
content-align: left middle;
|
||||
padding-left: 2;
|
||||
}
|
||||
|
||||
#preview-center {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin-top: 2;
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
#preview {
|
||||
min-width: 50;
|
||||
max-width: 70;
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
border: round #555555;
|
||||
border-title-align: center;
|
||||
}
|
||||
|
||||
#preview-inner {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 0 1 0 1;
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
API Key Screen
|
||||
============================================================================= */
|
||||
|
|
@ -177,11 +81,11 @@ ThemeSelectionScreen #enter-hint {
|
|||
}
|
||||
|
||||
#input-box.valid {
|
||||
border: round $success;
|
||||
border: round ansi_green;
|
||||
}
|
||||
|
||||
#input-box.invalid {
|
||||
border: round $error;
|
||||
border: round ansi_red;
|
||||
}
|
||||
|
||||
#key {
|
||||
|
|
@ -202,11 +106,11 @@ ThemeSelectionScreen #enter-hint {
|
|||
}
|
||||
|
||||
#feedback.error {
|
||||
color: $error;
|
||||
color: ansi_red;
|
||||
}
|
||||
|
||||
#feedback.success {
|
||||
color: $text-muted;
|
||||
color: ansi_bright_black;
|
||||
}
|
||||
|
||||
#config-docs-section {
|
||||
|
|
@ -222,7 +126,7 @@ ThemeSelectionScreen #enter-hint {
|
|||
}
|
||||
|
||||
#config-docs-group Static {
|
||||
color: $text-muted;
|
||||
color: ansi_bright_black;
|
||||
}
|
||||
|
||||
#config-docs-group .link-row {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from vibe.setup.onboarding.screens.api_key import ApiKeyScreen
|
||||
from vibe.setup.onboarding.screens.theme_selection import ThemeSelectionScreen
|
||||
from vibe.setup.onboarding.screens.welcome import WelcomeScreen
|
||||
|
||||
__all__ = ["ApiKeyScreen", "ThemeSelectionScreen", "WelcomeScreen"]
|
||||
__all__ = ["ApiKeyScreen", "WelcomeScreen"]
|
||||
|
|
|
|||
|
|
@ -1,164 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, ClassVar
|
||||
|
||||
from textual.app import ComposeResult
|
||||
from textual.binding import Binding, BindingType
|
||||
from textual.containers import Center, Container, Horizontal, Vertical
|
||||
from textual.events import Resize
|
||||
from textual.theme import BUILTIN_THEMES
|
||||
from textual.widgets import Markdown, Static
|
||||
|
||||
from vibe.cli.textual_ui.terminal_theme import TERMINAL_THEME_NAME
|
||||
from vibe.cli.textual_ui.widgets.no_markup_static import NoMarkupStatic
|
||||
from vibe.core.config import VibeConfig
|
||||
from vibe.setup.onboarding.base import OnboardingScreen
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from vibe.setup.onboarding import OnboardingApp
|
||||
|
||||
THEMES = [TERMINAL_THEME_NAME] + sorted(
|
||||
k for k in BUILTIN_THEMES if k != "textual-ansi"
|
||||
)
|
||||
|
||||
VISIBLE_NEIGHBORS = 3
|
||||
FADE_CLASSES = ["fade-1", "fade-2", "fade-3"]
|
||||
|
||||
PREVIEW_MARKDOWN = """
|
||||
### Heading
|
||||
|
||||
**Bold**, *italic*, and `inline code`.
|
||||
|
||||
- Bullet point
|
||||
- Another bullet point
|
||||
|
||||
1. First item
|
||||
2. Second item
|
||||
|
||||
```python
|
||||
def greet(name: str = "World") -> str:
|
||||
return f"Hello, {name}!"
|
||||
```
|
||||
|
||||
> Blockquote
|
||||
|
||||
---
|
||||
|
||||
| Column 1 | Column 2 |
|
||||
|----------|----------|
|
||||
| Item 1 | Item 2 |
|
||||
"""
|
||||
|
||||
|
||||
class ThemeSelectionScreen(OnboardingScreen):
|
||||
BINDINGS: ClassVar[list[BindingType]] = [
|
||||
Binding("enter", "next", "Next", show=False, priority=True),
|
||||
Binding("up", "prev_theme", "Previous", show=False),
|
||||
Binding("down", "next_theme", "Next Theme", show=False),
|
||||
Binding("ctrl+c", "cancel", "Cancel", show=False),
|
||||
Binding("escape", "cancel", "Cancel", show=False),
|
||||
]
|
||||
|
||||
NEXT_SCREEN = "api_key"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._theme_index = 0
|
||||
self._theme_widgets: list[Static] = []
|
||||
|
||||
def _compose_theme_list(self) -> ComposeResult:
|
||||
for _ in range(VISIBLE_NEIGHBORS * 2 + 1):
|
||||
widget = NoMarkupStatic("", classes="theme-item")
|
||||
self._theme_widgets.append(widget)
|
||||
yield widget
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Center(id="theme-outer"):
|
||||
with Vertical(id="theme-content"):
|
||||
yield NoMarkupStatic("Select your preferred theme", id="theme-title")
|
||||
yield Center(
|
||||
Horizontal(
|
||||
NoMarkupStatic("Navigate ↑ ↓", id="nav-hint"),
|
||||
Vertical(*self._compose_theme_list(), id="theme-list"),
|
||||
NoMarkupStatic("Press Enter \u21b5", id="enter-hint"),
|
||||
id="theme-row",
|
||||
)
|
||||
)
|
||||
with Container(id="preview-center"):
|
||||
preview = Container(id="preview")
|
||||
preview.border_title = "Preview"
|
||||
with preview:
|
||||
yield Container(Markdown(PREVIEW_MARKDOWN), id="preview-inner")
|
||||
|
||||
@property
|
||||
def _has_terminal_theme(self) -> bool:
|
||||
app: OnboardingApp = self.app # type: ignore[assignment]
|
||||
return app._terminal_theme is not None
|
||||
|
||||
@property
|
||||
def _available_themes(self) -> list[str]:
|
||||
if self._has_terminal_theme:
|
||||
return THEMES
|
||||
return [t for t in THEMES if t != TERMINAL_THEME_NAME]
|
||||
|
||||
def on_mount(self) -> None:
|
||||
current_theme = self.app.theme
|
||||
themes = self._available_themes
|
||||
if current_theme == TERMINAL_THEME_NAME:
|
||||
self._theme_index = 0
|
||||
elif current_theme in themes:
|
||||
self._theme_index = themes.index(current_theme)
|
||||
self._update_display()
|
||||
self._update_preview_height()
|
||||
self.focus()
|
||||
|
||||
def on_resize(self, _: Resize) -> None:
|
||||
self._update_preview_height()
|
||||
|
||||
def _update_preview_height(self) -> None:
|
||||
# Height is dynamically set because css won't allow filling available space and page scroll on overflow.
|
||||
preview = self.query_one("#preview", Container)
|
||||
header_height = 17 # title + margins + theme row + padding + buffer
|
||||
available = self.app.size.height - header_height
|
||||
preview.styles.max_height = max(10, available)
|
||||
|
||||
def _get_theme_at_offset(self, offset: int) -> str:
|
||||
themes = self._available_themes
|
||||
index = (self._theme_index + offset) % len(themes)
|
||||
return themes[index]
|
||||
|
||||
def _update_display(self) -> None:
|
||||
for i, widget in enumerate(self._theme_widgets):
|
||||
offset = i - VISIBLE_NEIGHBORS
|
||||
theme = self._get_theme_at_offset(offset)
|
||||
|
||||
widget.remove_class("selected", *FADE_CLASSES)
|
||||
|
||||
if offset == 0:
|
||||
widget.update(f" {theme} ")
|
||||
widget.add_class("selected")
|
||||
else:
|
||||
distance = min(abs(offset) - 1, len(FADE_CLASSES) - 1)
|
||||
widget.update(theme)
|
||||
widget.add_class(FADE_CLASSES[distance])
|
||||
|
||||
def _navigate(self, direction: int) -> None:
|
||||
themes = self._available_themes
|
||||
self._theme_index = (self._theme_index + direction) % len(themes)
|
||||
theme = themes[self._theme_index]
|
||||
self.app.theme = theme
|
||||
self._update_display()
|
||||
|
||||
def action_next_theme(self) -> None:
|
||||
self._navigate(1)
|
||||
|
||||
def action_prev_theme(self) -> None:
|
||||
self._navigate(-1)
|
||||
|
||||
def action_next(self) -> None:
|
||||
theme = self._available_themes[self._theme_index]
|
||||
try:
|
||||
VibeConfig.save_updates({"textual_theme": theme})
|
||||
except OSError:
|
||||
pass
|
||||
super().action_next()
|
||||
|
|
@ -50,7 +50,7 @@ class WelcomeScreen(OnboardingScreen):
|
|||
Binding("escape", "cancel", "Cancel", show=False),
|
||||
]
|
||||
|
||||
NEXT_SCREEN = "theme_selection"
|
||||
NEXT_SCREEN = "api_key"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import tomllib
|
||||
from typing import Any, ClassVar
|
||||
|
||||
from textual import events
|
||||
|
|
@ -11,12 +10,8 @@ from textual.containers import CenterMiddle, Horizontal
|
|||
from textual.message import Message
|
||||
from textual.widgets import Static
|
||||
|
||||
from vibe.cli.textual_ui.terminal_theme import (
|
||||
TERMINAL_THEME_NAME,
|
||||
capture_terminal_theme,
|
||||
)
|
||||
from vibe.cli.textual_ui.widgets.no_markup_static import NoMarkupStatic
|
||||
from vibe.core.paths.global_paths import GLOBAL_CONFIG_FILE, TRUSTED_FOLDERS_FILE
|
||||
from vibe.core.paths.global_paths import TRUSTED_FOLDERS_FILE
|
||||
|
||||
|
||||
class TrustDialogQuitException(Exception):
|
||||
|
|
@ -154,32 +149,9 @@ class TrustFolderApp(App):
|
|||
self.folder_path = folder_path
|
||||
self._result: bool | None = None
|
||||
self._quit_without_saving = False
|
||||
self._terminal_theme = capture_terminal_theme()
|
||||
self._load_theme()
|
||||
|
||||
def _load_theme(self) -> None:
|
||||
if self._terminal_theme:
|
||||
self.register_theme(self._terminal_theme)
|
||||
|
||||
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)
|
||||
except (OSError, tomllib.TOMLDecodeError):
|
||||
return
|
||||
|
||||
textual_theme = config_data.get("textual_theme")
|
||||
if not textual_theme:
|
||||
return
|
||||
|
||||
if textual_theme == TERMINAL_THEME_NAME:
|
||||
if self._terminal_theme:
|
||||
self.theme = TERMINAL_THEME_NAME
|
||||
else:
|
||||
self.theme = textual_theme
|
||||
def on_mount(self) -> None:
|
||||
self.theme = "textual-ansi"
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield TrustFolderDialog(self.folder_path)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Screen {
|
||||
align: center middle;
|
||||
background: $background 80%;
|
||||
background: transparent 80%;
|
||||
}
|
||||
|
||||
#trust-dialog {
|
||||
|
|
@ -8,8 +8,8 @@ Screen {
|
|||
max-width: 90%;
|
||||
min-width: 50;
|
||||
height: auto;
|
||||
border: round $foreground-muted;
|
||||
background: $background;
|
||||
border: round ansi_bright_black;
|
||||
background: transparent;
|
||||
padding: 1;
|
||||
}
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ Screen {
|
|||
width: 100%;
|
||||
height: auto;
|
||||
text-style: bold;
|
||||
color: $warning;
|
||||
color: ansi_yellow;
|
||||
text-align: center;
|
||||
margin-bottom: 1;
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ Screen {
|
|||
#trust-dialog-path {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
color: $primary;
|
||||
color: ansi_blue;
|
||||
text-align: center;
|
||||
text-wrap: wrap;
|
||||
margin-bottom: 1;
|
||||
|
|
@ -34,7 +34,7 @@ Screen {
|
|||
#trust-dialog-message {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
color: $foreground;
|
||||
color: ansi_default;
|
||||
text-align: center;
|
||||
text-wrap: wrap;
|
||||
margin-bottom: 1;
|
||||
|
|
@ -50,24 +50,24 @@ Screen {
|
|||
.trust-option {
|
||||
height: auto;
|
||||
width: auto;
|
||||
color: $foreground;
|
||||
color: ansi_default;
|
||||
margin: 0 3;
|
||||
content-align: center middle;
|
||||
}
|
||||
|
||||
.trust-cursor-selected {
|
||||
color: $foreground;
|
||||
color: ansi_default;
|
||||
text-style: bold;
|
||||
}
|
||||
|
||||
.trust-option-selected {
|
||||
color: $foreground;
|
||||
color: ansi_default;
|
||||
}
|
||||
|
||||
.trust-dialog-help {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
color: $text-muted;
|
||||
color: ansi_bright_black;
|
||||
text-align: center;
|
||||
margin-bottom: 1;
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ Screen {
|
|||
.trust-dialog-save-info {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
color: $text-muted;
|
||||
color: ansi_bright_black;
|
||||
text-align: center;
|
||||
text-style: italic;
|
||||
text-wrap: wrap;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue