feat: Wave 3 — plugin loader + Flet GUI (parity with Rust fork)

Plugin loader (mirrors Rust #29):
- plugins_store.py walks %APPDATA%\com.priler.jarvis\plugins\<name>\
  for commands.yaml fragments and merges them into VA_CMD_LIST at
  startup. Same schema as the project root commands.yaml. Per-pack
  `disabled` flag file skips a pack without deletion. Built-in ids
  win conflicts so a malicious pack can't silently override
  open_browser. 9 unit tests (parity with the Rust tests).
- main.py: plugin merge runs after the root yaml load; logs the
  number of extra commands picked up.

Flet GUI (#27 — first parity with the Rust Tauri GUI):
- New gui/ package: __main__.py launches via `python -m gui` (or
  gui.bat). app.py routes between 7 pages (Главная / Команды /
  Макросы / Расписание / Память / Плагины / Настройки) via
  NavigationRail; dark theme matching the Tauri look.
- gui/services.py centralises every store-module call the pages
  share — single place to swap implementations later.
- One file per page in gui/pages/, all expose `build(page)` and use
  the same card layout idiom. Pages mirror the Tauri ones feature-
  for-feature: home (status + counters + launcher), commands
  (filterable list of all VA_CMD_LIST entries), macros (list +
  delete + recording banner), scheduler (list + remove), memory
  (add/forget/search), plugins (toggle/open folder), settings
  (profile + LLM hot-swap).
- requirements.txt: flet>=0.85 (optional; assistant runs without).
- 3 smoke tests verify imports + build callables; skip cleanly when
  Flet isn't installed.

README: documents both the GUI and the plugin layout in user-facing
terms. Tests: 42 → 54 (+9 plugins +3 GUI smoke).
This commit is contained in:
Bossiara13 2026-05-16 13:27:04 +03:00
parent 2bef1d5ea7
commit 59a1258dcc
19 changed files with 1233 additions and 0 deletions

81
gui/app.py Normal file
View file

@ -0,0 +1,81 @@
"""Flet GUI router and shell.
Provides:
- Top navigation bar with the 6 pages (Главная / Команды / Макросы / Расписание / Память / Плагины / Настройки)
- Page swap on click, no full reload
- Dark theme matching the Tauri GUI vibe
Each page lives in `gui/pages/<name>.py` and exposes `build(page) -> Control`.
"""
import flet as ft
from gui.pages import home, commands, macros, scheduler, memory, plugins, settings
PAGES = [
("Главная", home.build),
("Команды", commands.build),
("Макросы", macros.build),
("Расписание", scheduler.build),
("Память", memory.build),
("Плагины", plugins.build),
("Настройки", settings.build),
]
def _make_app(page: ft.Page):
page.title = "J.A.R.V.I.S. — Python GUI"
page.theme_mode = ft.ThemeMode.DARK
page.window.width = 940
page.window.height = 720
page.padding = 0
page.bgcolor = "#0a1419"
content_holder = ft.Container(expand=True, padding=20)
def go(idx: int):
nav.selected_index = idx
title, builder = PAGES[idx]
try:
content_holder.content = builder(page)
except (RuntimeError, ValueError, OSError) as exc:
content_holder.content = ft.Text(
f"Не удалось открыть «{title}»:\n{exc}",
color="red",
)
page.update()
nav = ft.NavigationRail(
selected_index=0,
label_type=ft.NavigationRailLabelType.ALL,
min_width=80,
min_extended_width=180,
bgcolor="#0d1b21",
destinations=[
ft.NavigationRailDestination(icon=ft.Icons.HOME, label="Главная"),
ft.NavigationRailDestination(icon=ft.Icons.LIST_ALT, label="Команды"),
ft.NavigationRailDestination(icon=ft.Icons.PLAY_CIRCLE_OUTLINE, label="Макросы"),
ft.NavigationRailDestination(icon=ft.Icons.SCHEDULE, label="Расписание"),
ft.NavigationRailDestination(icon=ft.Icons.MEMORY, label="Память"),
ft.NavigationRailDestination(icon=ft.Icons.EXTENSION, label="Плагины"),
ft.NavigationRailDestination(icon=ft.Icons.SETTINGS, label="Настройки"),
],
on_change=lambda e: go(int(e.control.selected_index)),
)
layout = ft.Row(
controls=[
nav,
ft.VerticalDivider(width=1, color="#1f3540"),
content_holder,
],
expand=True,
)
page.add(layout)
go(0)
def run():
ft.app(target=_make_app)