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

159 lines
5.5 KiB
Python
Raw Permalink Normal View History

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()