J.A.R.V.I.S-py/tools/command_builder/app.py

158 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import json
import os
import sys
import threading
from pathlib import Path
import webview
REPO_ROOT = Path(__file__).resolve().parents[2]
WEB_DIR = Path(__file__).resolve().parent / "web"
COMMANDS_YAML = REPO_ROOT / "commands.yaml"
CUSTOM_COMMANDS_DIR = REPO_ROOT / "custom-commands"
SOUND_DIR = REPO_ROOT / "sound"
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
from tools.command_builder.schema import (
CommandEntry,
SchemaError,
VALID_SOUND_NAMES,
action_from_dict,
command_from_dict,
slugify,
)
from tools.command_builder import yaml_writer
class Bridge:
def list_existing_commands(self):
try:
return yaml_writer.list_command_ids(COMMANDS_YAML)
except Exception as ex:
return {"error": str(ex)}
def list_exes(self):
if not CUSTOM_COMMANDS_DIR.exists():
return []
names = []
for p in sorted(CUSTOM_COMMANDS_DIR.iterdir()):
if p.suffix.lower() == ".exe":
names.append(p.stem)
return names
def list_sound_names(self):
return list(VALID_SOUND_NAMES)
def preview_sound(self, name):
if name not in VALID_SOUND_NAMES:
return {"ok": False, "error": "unknown sound"}
candidates = []
if name in ("ok", "greet"):
for i in (1, 2, 3):
candidates.append(SOUND_DIR / f"{name}{i}.wav")
candidates.append(SOUND_DIR / f"{name}.wav")
target = next((c for c in candidates if c.exists()), None)
if target is None:
return {"ok": False, "error": f"file not found for {name}"}
def _play():
try:
import simpleaudio as sa
wave_obj = sa.WaveObject.from_wave_file(str(target))
play_obj = wave_obj.play()
play_obj.wait_done()
except Exception:
pass
threading.Thread(target=_play, daemon=True).start()
return {"ok": True}
def slugify(self, phrase):
return slugify(phrase or "")
def validate_draft(self, draft_json):
try:
data = json.loads(draft_json) if isinstance(draft_json, str) else draft_json
cmd_id = data.get("cmd_id") or ""
if not cmd_id.strip():
return {"ok": False, "error": "Не указан идентификатор команды"}
entry = command_from_dict(cmd_id, {
"phrases": data.get("phrases") or [],
"action": data.get("action") or {},
**({"confirm_sound": data["confirm_sound"]} if data.get("confirm_sound") else {}),
})
return {"ok": True, "id": entry.cmd_id}
except SchemaError as ex:
return {"ok": False, "error": str(ex)}
except Exception as ex:
return {"ok": False, "error": f"Внутренняя ошибка: {ex}"}
def save_command(self, draft_json):
try:
data = json.loads(draft_json) if isinstance(draft_json, str) else draft_json
cmd_id = (data.get("cmd_id") or "").strip()
if not cmd_id:
return {"ok": False, "error": "Не указан идентификатор команды"}
entry = command_from_dict(cmd_id, {
"phrases": data.get("phrases") or [],
"action": data.get("action") or {},
**({"confirm_sound": data["confirm_sound"]} if data.get("confirm_sound") else {}),
})
existing = set(yaml_writer.list_command_ids(COMMANDS_YAML))
if entry.cmd_id in existing and not data.get("overwrite"):
return {"ok": False, "error": f"Команда {entry.cmd_id} уже существует"}
if data.get("overwrite"):
yaml_writer.upsert_command(entry, path=COMMANDS_YAML)
else:
yaml_writer.append_command(entry, path=COMMANDS_YAML)
return {"ok": True, "id": entry.cmd_id}
except SchemaError as ex:
return {"ok": False, "error": str(ex)}
except Exception as ex:
return {"ok": False, "error": f"Не удалось сохранить: {ex}"}
def llm_suggest(self, prompt):
try:
from tools.command_builder import llm_suggest as ls
return ls.suggest(prompt or "")
except Exception as ex:
return {"ok": False, "error": f"LLM недоступен: {ex}"}
def _check_yaml_health():
if not COMMANDS_YAML.exists():
return
try:
yaml_writer.load_yaml(COMMANDS_YAML)
except Exception as ex:
bak = COMMANDS_YAML.with_suffix(COMMANDS_YAML.suffix + ".bak")
msg = f"[command_builder] Не удалось разобрать {COMMANDS_YAML.name}: {ex}"
if bak.exists():
msg += f"\n[command_builder] Резервная копия доступна: {bak} — можно восстановить вручную."
print(msg, file=sys.stderr)
def main():
_check_yaml_health()
bridge = Bridge()
index_path = WEB_DIR / "index.html"
if not index_path.exists():
raise SystemExit(f"web/index.html not found at {index_path}")
webview.create_window(
title="J.A.R.V.I.S — конструктор команд",
url=str(index_path),
js_api=bridge,
width=900,
height=720,
min_size=(720, 560),
background_color="#0a1214",
)
webview.start()
if __name__ == "__main__":
main()