Co-authored-by: Clément Drouin <clement.drouin@mistral.ai> Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai> Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com> Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai> Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com> Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai> Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
117 lines
4.1 KiB
Python
117 lines
4.1 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import ClassVar
|
|
|
|
from textual.app import ComposeResult
|
|
from textual.binding import Binding, BindingType
|
|
from textual.containers import Center, Vertical
|
|
from textual.timer import Timer
|
|
from textual.widgets import Static
|
|
|
|
from vibe.cli.textual_ui.widgets.no_markup_static import NoMarkupStatic
|
|
from vibe.setup.onboarding.base import OnboardingScreen
|
|
from vibe.setup.onboarding.gradient_text import GRADIENT_COLORS, gradient_markup
|
|
|
|
WELCOME_PREFIX = "Welcome to "
|
|
WELCOME_HIGHLIGHT = "Mistral Vibe"
|
|
WELCOME_SUFFIX = " - Let's get you started!"
|
|
WELCOME_TEXT = WELCOME_PREFIX + WELCOME_HIGHLIGHT + WELCOME_SUFFIX
|
|
|
|
HIGHLIGHT_START = len(WELCOME_PREFIX)
|
|
HIGHLIGHT_END = HIGHLIGHT_START + len(WELCOME_HIGHLIGHT)
|
|
|
|
BUTTON_TEXT = "Press Enter ↵"
|
|
|
|
|
|
class WelcomeScreen(OnboardingScreen):
|
|
BINDINGS: ClassVar[list[BindingType]] = [
|
|
Binding("enter", "next", "Next", show=False, priority=True),
|
|
Binding("ctrl+c", "cancel", "Cancel", show=False),
|
|
Binding("escape", "cancel", "Cancel", show=False),
|
|
]
|
|
|
|
NEXT_SCREEN = "theme_selection"
|
|
|
|
def __init__(self, next_screen: str = "theme_selection") -> None:
|
|
super().__init__()
|
|
self.NEXT_SCREEN = next_screen
|
|
self._char_index = 0
|
|
self._gradient_offset = 0
|
|
self._typing_done = False
|
|
self._paused = False
|
|
self._typing_timer: Timer | None = None
|
|
self._button_char_index = 0
|
|
self._button_typing_timer: Timer | None = None
|
|
self._welcome_text: Static
|
|
self._enter_hint: Static
|
|
|
|
def compose(self) -> ComposeResult:
|
|
with Vertical(id="welcome-container"):
|
|
with Center():
|
|
yield Static("", id="welcome-text")
|
|
with Center():
|
|
yield NoMarkupStatic("", id="enter-hint", classes="hidden")
|
|
|
|
def on_mount(self) -> None:
|
|
self._welcome_text = self.query_one("#welcome-text", Static)
|
|
self._enter_hint = self.query_one("#enter-hint", Static)
|
|
self._typing_timer = self.set_interval(0.04, self._type_next_char)
|
|
self.focus()
|
|
|
|
def _render_text(self, length: int) -> str:
|
|
text = WELCOME_TEXT[:length]
|
|
|
|
if length <= HIGHLIGHT_START:
|
|
return text
|
|
|
|
prefix = text[:HIGHLIGHT_START]
|
|
highlight_len = min(length, HIGHLIGHT_END) - HIGHLIGHT_START
|
|
highlight = gradient_markup(
|
|
WELCOME_HIGHLIGHT[:highlight_len], self._gradient_offset
|
|
)
|
|
|
|
if length > HIGHLIGHT_END:
|
|
suffix = text[HIGHLIGHT_END:]
|
|
return prefix + highlight + suffix
|
|
return prefix + highlight
|
|
|
|
def _type_next_char(self) -> None:
|
|
if self._char_index >= len(WELCOME_TEXT):
|
|
if not self._typing_done:
|
|
self._typing_done = True
|
|
self.set_timer(0.5, self._show_button)
|
|
return
|
|
|
|
if self._char_index == HIGHLIGHT_END and not self._paused:
|
|
self._paused = True
|
|
if self._typing_timer:
|
|
self._typing_timer.stop()
|
|
self.set_interval(0.08, self._animate_gradient)
|
|
self.set_timer(1.4, self._resume_typing)
|
|
return
|
|
|
|
self._char_index += 1
|
|
self._welcome_text.update(self._render_text(self._char_index))
|
|
|
|
def _resume_typing(self) -> None:
|
|
self._typing_timer = self.set_interval(0.03, self._type_next_char)
|
|
|
|
def _show_button(self) -> None:
|
|
self._enter_hint.remove_class("hidden")
|
|
self._button_typing_timer = self.set_interval(0.03, self._type_button_char)
|
|
|
|
def _type_button_char(self) -> None:
|
|
if self._button_char_index >= len(BUTTON_TEXT):
|
|
if self._button_typing_timer:
|
|
self._button_typing_timer.stop()
|
|
return
|
|
self._button_char_index += 1
|
|
self._enter_hint.update(BUTTON_TEXT[: self._button_char_index])
|
|
|
|
def _animate_gradient(self) -> None:
|
|
self._gradient_offset = (self._gradient_offset + 1) % len(GRADIENT_COLORS)
|
|
self._welcome_text.update(self._render_text(self._char_index))
|
|
|
|
def action_next(self) -> None:
|
|
if self._typing_done:
|
|
super().action_next()
|