120 lines
3.9 KiB
Python
120 lines
3.9 KiB
Python
|
|
"""Home page — quick launcher + active backends + counters."""
|
|||
|
|
|
|||
|
|
import flet as ft
|
|||
|
|
|
|||
|
|
from gui import services as svc
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _stat_card(title: str, value: str, color: str = "#33d3a4") -> ft.Container:
|
|||
|
|
return ft.Container(
|
|||
|
|
content=ft.Column(
|
|||
|
|
[
|
|||
|
|
ft.Text(title, size=11, color="#8aa0a8"),
|
|||
|
|
ft.Text(value, size=22, weight=ft.FontWeight.W_600, color=color),
|
|||
|
|
],
|
|||
|
|
spacing=2,
|
|||
|
|
),
|
|||
|
|
bgcolor="#15252b",
|
|||
|
|
border_radius=10,
|
|||
|
|
padding=14,
|
|||
|
|
expand=True,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def build(page: ft.Page) -> ft.Control:
|
|||
|
|
running = svc.assistant_is_running()
|
|||
|
|
try:
|
|||
|
|
llm_b, llm_m = svc.llm_active()
|
|||
|
|
except (RuntimeError, OSError, ValueError) as exc:
|
|||
|
|
llm_b, llm_m = "—", str(exc)
|
|||
|
|
|
|||
|
|
counters = ft.Row(
|
|||
|
|
controls=[
|
|||
|
|
_stat_card("Команды", str(_safe_count(svc, "commands"))),
|
|||
|
|
_stat_card("Макросы", str(len(svc.macros_all()))),
|
|||
|
|
_stat_card("Расписание", str(len(svc.scheduler_all()))),
|
|||
|
|
_stat_card("Память (фактов)", str(len(svc.memory_all()))),
|
|||
|
|
_stat_card("Плагины", str(len(svc.plugins_all()))),
|
|||
|
|
],
|
|||
|
|
spacing=10,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
status_chip = ft.Container(
|
|||
|
|
content=ft.Text(
|
|||
|
|
"АССИСТЕНТ РАБОТАЕТ" if running else "АССИСТЕНТ ОСТАНОВЛЕН",
|
|||
|
|
size=12,
|
|||
|
|
weight=ft.FontWeight.W_700,
|
|||
|
|
color="#13191c",
|
|||
|
|
),
|
|||
|
|
bgcolor="#33d3a4" if running else "#d8d8d8",
|
|||
|
|
padding=ft.padding.symmetric(horizontal=12, vertical=6),
|
|||
|
|
border_radius=20,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
start_btn = ft.FilledButton(
|
|||
|
|
text="Запустить J.A.R.V.I.S.",
|
|||
|
|
icon=ft.Icons.PLAY_ARROW,
|
|||
|
|
on_click=lambda e: _launch(page, e),
|
|||
|
|
disabled=running,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
backends_card = ft.Container(
|
|||
|
|
content=ft.Column([
|
|||
|
|
ft.Text("Активный LLM", size=11, color="#8aa0a8"),
|
|||
|
|
ft.Text(f"{llm_b} · {llm_m}", size=14, weight=ft.FontWeight.W_500),
|
|||
|
|
], spacing=2),
|
|||
|
|
bgcolor="#15252b",
|
|||
|
|
border_radius=10,
|
|||
|
|
padding=14,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return ft.Column(
|
|||
|
|
controls=[
|
|||
|
|
ft.Row([
|
|||
|
|
ft.Text("J.A.R.V.I.S.", size=28, weight=ft.FontWeight.W_700),
|
|||
|
|
status_chip,
|
|||
|
|
], alignment=ft.MainAxisAlignment.SPACE_BETWEEN),
|
|||
|
|
ft.Text(
|
|||
|
|
"Голосовой ассистент — Python-форк. Эта панель управляет состоянием, "
|
|||
|
|
"запускает main.py и переключает движки.",
|
|||
|
|
color="#8aa0a8",
|
|||
|
|
size=12,
|
|||
|
|
),
|
|||
|
|
ft.Divider(color="#1f3540"),
|
|||
|
|
backends_card,
|
|||
|
|
counters,
|
|||
|
|
ft.Divider(color="#1f3540"),
|
|||
|
|
start_btn,
|
|||
|
|
],
|
|||
|
|
spacing=14,
|
|||
|
|
expand=True,
|
|||
|
|
scroll=ft.ScrollMode.AUTO,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _launch(page: ft.Page, _e) -> None:
|
|||
|
|
ok = svc.assistant_run()
|
|||
|
|
page.open(
|
|||
|
|
ft.SnackBar(
|
|||
|
|
content=ft.Text(
|
|||
|
|
"Запускаю main.py" if ok else "Не удалось запустить main.py",
|
|||
|
|
color="white",
|
|||
|
|
),
|
|||
|
|
bgcolor="#33d3a4" if ok else "#d8584f",
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _safe_count(svc_module, kind: str) -> int:
|
|||
|
|
"""Counts come from disk; if a store fails we display 0 rather than crash."""
|
|||
|
|
try:
|
|||
|
|
if kind == "commands":
|
|||
|
|
# Don't import the full VA_CMD_LIST machinery — read yaml directly.
|
|||
|
|
import os, yaml # pylint: disable=import-outside-toplevel
|
|||
|
|
here = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|||
|
|
with open(os.path.join(here, "commands.yaml"), "rt", encoding="utf8") as f:
|
|||
|
|
return len(yaml.safe_load(f) or {})
|
|||
|
|
except (OSError, ValueError):
|
|||
|
|
return 0
|
|||
|
|
return 0
|