From 80233bbce39e79dcecafb5c5dd22a1eee20887dd Mon Sep 17 00:00:00 2001 From: haikdc Date: Sun, 5 Apr 2026 11:38:46 -0700 Subject: [PATCH] [Haik]: ckpt, modes subapp is done and satisfactory for now, altho future touch ups may be done --- .../modes/{builtin.py => BUILTIN_MODES.py} | 12 +++- backend/apps/modes/Mode.py | 16 +++++ backend/apps/modes/models.py | 38 ---------- backend/apps/modes/modes.py | 70 +++++++++++-------- 4 files changed, 67 insertions(+), 69 deletions(-) rename backend/apps/modes/{builtin.py => BUILTIN_MODES.py} (94%) create mode 100644 backend/apps/modes/Mode.py delete mode 100644 backend/apps/modes/models.py diff --git a/backend/apps/modes/builtin.py b/backend/apps/modes/BUILTIN_MODES.py similarity index 94% rename from backend/apps/modes/builtin.py rename to backend/apps/modes/BUILTIN_MODES.py index a7f3a0f7..84b4a38e 100644 --- a/backend/apps/modes/builtin.py +++ b/backend/apps/modes/BUILTIN_MODES.py @@ -3,10 +3,16 @@ Separated from models.py to keep schema classes small and data separate. """ -from backend.apps.modes.models import Mode -from backend.config.paths import OUTPUTS_WORKSPACE_DIR as OUTPUTS_WORKSPACE, SKILLS_WORKSPACE_DIR as SKILLS_WORKSPACE +from backend.apps.modes.Mode import Mode +from typing import List +from backend.config.paths import DB_ROOT +import os -BUILTIN_MODES: list[Mode] = [ +# NOTE: When the skills and outputs subapps are implemented, we will need to update these paths to import from the subapps. +SKILLS_WORKSPACE: str = os.path.join(DB_ROOT, "skills") +OUTPUTS_WORKSPACE: str = os.path.join(DB_ROOT, "outputs") + +BUILTIN_MODES: List[Mode] = [ Mode( id="agent", name="Agent", diff --git a/backend/apps/modes/Mode.py b/backend/apps/modes/Mode.py new file mode 100644 index 00000000..6b44c6c4 --- /dev/null +++ b/backend/apps/modes/Mode.py @@ -0,0 +1,16 @@ +from pydantic import BaseModel, Field +from typing import Optional +from uuid import uuid4 + + +class Mode(BaseModel): + id: str = Field(default_factory=lambda: uuid4().hex) + name: str + description: str = "" + system_prompt: Optional[str] = None + tools: Optional[list[str]] = None + default_next_mode: Optional[str] = None + is_builtin: bool = False + icon: str = "smart_toy" + color: str = "#818cf8" + default_folder: Optional[str] = None \ No newline at end of file diff --git a/backend/apps/modes/models.py b/backend/apps/modes/models.py deleted file mode 100644 index f63b155d..00000000 --- a/backend/apps/modes/models.py +++ /dev/null @@ -1,38 +0,0 @@ -from pydantic import BaseModel, Field -from typing import Optional -from uuid import uuid4 - - -class Mode(BaseModel): - id: str = Field(default_factory=lambda: uuid4().hex) - name: str - description: str = "" - system_prompt: Optional[str] = None - tools: Optional[list[str]] = None - default_next_mode: Optional[str] = None - is_builtin: bool = False - icon: str = "smart_toy" - color: str = "#818cf8" - default_folder: Optional[str] = None - - -class ModeCreate(BaseModel): - name: str - description: str = "" - system_prompt: Optional[str] = None - tools: Optional[list[str]] = None - default_next_mode: Optional[str] = None - icon: str = "smart_toy" - color: str = "#818cf8" - default_folder: Optional[str] = None - - -class ModeUpdate(BaseModel): - name: Optional[str] = None - description: Optional[str] = None - system_prompt: Optional[str] = None - tools: Optional[list[str]] = None - default_next_mode: Optional[str] = None - icon: Optional[str] = None - color: Optional[str] = None - default_folder: Optional[str] = None diff --git a/backend/apps/modes/modes.py b/backend/apps/modes/modes.py index b7c1a695..11058d06 100644 --- a/backend/apps/modes/modes.py +++ b/backend/apps/modes/modes.py @@ -1,50 +1,54 @@ -import json import os -import logging from contextlib import asynccontextmanager from fastapi import HTTPException +from pydantic import BaseModel +from typing import Optional from backend.config.Apps import SubApp -from backend.apps.common.json_store import JsonStore -from backend.apps.modes.models import Mode, ModeCreate, ModeUpdate -from backend.apps.modes.builtin import BUILTIN_MODES +from backend.core.db.PydanticStore import PydanticStore +from backend.apps.modes.Mode import Mode +from backend.apps.modes.BUILTIN_MODES import BUILTIN_MODES +from backend.config.paths import DB_ROOT -logger = logging.getLogger(__name__) - -from backend.config.paths import MODES_DIR as DATA_DIR +MODE_STORE: PydanticStore[Mode] = PydanticStore[Mode]( + model_cls=Mode, + data_dir=os.path.join(DB_ROOT, "modes"), + id_field="id", + dump_mode="json", + not_found_detail="Mode not found", +) @asynccontextmanager async def modes_lifespan(): - os.makedirs(DATA_DIR, exist_ok=True) for builtin in BUILTIN_MODES: - path = os.path.join(DATA_DIR, f"{builtin.id}.json") - if not os.path.exists(path): - _save(builtin) + MODE_STORE.save(builtin) yield modes = SubApp("modes", modes_lifespan) -_store = JsonStore(Mode, DATA_DIR, not_found_detail="Mode not found") - -_load_all = _store.load_all -_save = _store.save -_load = _store.load -load_mode = _store.load_or_none - - @modes.router.get("/list") async def list_modes(): builtin_defaults = {m.id: m.model_dump() for m in BUILTIN_MODES} - return {"modes": [m.model_dump() for m in _load_all()], "builtin_defaults": builtin_defaults} + return {"modes": [m.model_dump() for m in MODE_STORE.load_all()], "builtin_defaults": builtin_defaults} @modes.router.get("/{mode_id}") async def get_mode(mode_id: str): - return _load(mode_id).model_dump() + return MODE_STORE.load(mode_id).model_dump() +class ModeCreate(BaseModel): + name: str + description: str = "" + system_prompt: Optional[str] = None + tools: Optional[list[str]] = None + default_next_mode: Optional[str] = None + icon: str = "smart_toy" + color: str = "#818cf8" + default_folder: Optional[str] = None + @modes.router.post("/create") async def create_mode(body: ModeCreate): mode = Mode( @@ -58,16 +62,26 @@ async def create_mode(body: ModeCreate): default_folder=body.default_folder, is_builtin=False, ) - _save(mode) + MODE_STORE.save(mode) return {"ok": True, "mode": mode.model_dump()} +class ModeUpdate(BaseModel): + name: Optional[str] = None + description: Optional[str] = None + system_prompt: Optional[str] = None + tools: Optional[list[str]] = None + default_next_mode: Optional[str] = None + icon: Optional[str] = None + color: Optional[str] = None + default_folder: Optional[str] = None + @modes.router.put("/{mode_id}") async def update_mode(mode_id: str, body: ModeUpdate): - mode = _load(mode_id) + mode = MODE_STORE.load(mode_id) for k, v in body.model_dump(exclude_unset=True).items(): setattr(mode, k, v) - _save(mode) + MODE_STORE.save(mode) return {"ok": True, "mode": mode.model_dump()} @@ -77,14 +91,14 @@ async def reset_mode(mode_id: str): builtin = next((m for m in BUILTIN_MODES if m.id == mode_id), None) if not builtin: raise HTTPException(status_code=400, detail="Only built-in modes can be reset") - _save(builtin) + MODE_STORE.save(builtin) return {"ok": True, "mode": builtin.model_dump()} @modes.router.delete("/{mode_id}") async def delete_mode(mode_id: str): - mode = _load(mode_id) + mode = MODE_STORE.load(mode_id) if mode.is_builtin: raise HTTPException(status_code=403, detail="Cannot delete built-in modes") - _store.delete(mode_id) + MODE_STORE.delete(mode_id) return {"ok": True}