J.A.R.V.I.S-py/gui/pages/memory.py

75 lines
2.6 KiB
Python
Raw Normal View History

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).
2026-05-16 13:27:04 +03:00
"""Long-term memory page — list facts, add new ones, search, forget."""
import flet as ft
from gui import services as svc
def build(page: ft.Page) -> ft.Control:
key_input = ft.TextField(label="Ключ", expand=True)
val_input = ft.TextField(label="Значение", expand=True)
search_input = ft.TextField(label="Поиск", expand=True)
list_view = ft.Column(spacing=6, expand=True, scroll=ft.ScrollMode.AUTO)
def render(query: str = ""):
list_view.controls.clear()
facts = svc.memory_search(query, limit=100) if query.strip() else svc.memory_all()
if not facts:
list_view.controls.append(ft.Text("Нет фактов. Скажи «Запомни что моя любимая еда — суши».",
color="#8aa0a8"))
for f in sorted(facts, key=lambda x: x.get("key", "")):
list_view.controls.append(_fact_row(f, on_forget=lambda k=f["key"]: do_forget(k)))
page.update()
def do_add(_):
k = key_input.value.strip()
v = val_input.value.strip()
if not k or not v:
return
svc.memory_remember(k, v)
key_input.value = ""
val_input.value = ""
render(search_input.value or "")
def do_forget(k: str):
svc.memory_forget(k)
render(search_input.value or "")
search_input.on_change = lambda e: render(e.control.value)
render()
return ft.Column(
[
ft.Text("Долговременная память", size=24, weight=ft.FontWeight.W_700),
ft.Text(
"Ключ-значение, которые Jarvis передаёт LLM как контекст пользователя.",
color="#8aa0a8", size=12,
),
ft.Row([key_input, val_input, ft.FilledButton(text="Добавить", on_click=do_add)]),
search_input,
list_view,
],
spacing=10,
expand=True,
)
def _fact_row(f: dict, on_forget) -> ft.Control:
return ft.Container(
content=ft.Row([
ft.Column([
ft.Text(f.get("key", ""), weight=ft.FontWeight.W_600, size=13),
ft.Text(f.get("value", ""), size=12, color="#a8b8c0"),
], spacing=2, expand=True),
ft.IconButton(
icon=ft.Icons.DELETE_FOREVER,
tooltip="Забыть",
on_click=lambda e: on_forget(),
),
]),
bgcolor="#15252b",
border_radius=8,
padding=ft.padding.symmetric(horizontal=12, vertical=8),
)