|
|
|
|
@@ -161,6 +161,7 @@ class GenerateRequest(BaseModel):
|
|
|
|
|
|
|
|
|
|
class ConfirmRequest(BaseModel):
|
|
|
|
|
approved: bool
|
|
|
|
|
script: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
class App:
|
|
|
|
|
"""The main ImGui interface orchestrator for Manual Slop."""
|
|
|
|
|
@@ -460,6 +461,21 @@ class App:
|
|
|
|
|
return header_key
|
|
|
|
|
raise HTTPException(status_code=403, detail="Could not validate API Key")
|
|
|
|
|
|
|
|
|
|
@api.get("/health")
|
|
|
|
|
def health() -> dict[str, str]:
|
|
|
|
|
"""Returns the health status of the API."""
|
|
|
|
|
return {"status": "ok"}
|
|
|
|
|
|
|
|
|
|
@api.get("/status", dependencies=[Depends(get_api_key)])
|
|
|
|
|
def status() -> dict[str, Any]:
|
|
|
|
|
"""Returns the current status of the application."""
|
|
|
|
|
return {
|
|
|
|
|
"provider": self.current_provider,
|
|
|
|
|
"model": self.current_model,
|
|
|
|
|
"status": self.ai_status,
|
|
|
|
|
"usage": self.session_usage
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@api.post("/api/v1/generate", dependencies=[Depends(get_api_key)])
|
|
|
|
|
def generate(req: GenerateRequest) -> dict[str, Any]:
|
|
|
|
|
"""Triggers an AI generation request using the current project context."""
|
|
|
|
|
@@ -523,6 +539,77 @@ class App:
|
|
|
|
|
"""Placeholder for streaming AI generation responses (Not yet implemented)."""
|
|
|
|
|
raise HTTPException(status_code=501, detail="Streaming endpoint (/api/v1/stream) is not yet supported in this version.")
|
|
|
|
|
|
|
|
|
|
@api.get("/api/v1/pending_actions", dependencies=[Depends(get_api_key)])
|
|
|
|
|
def pending_actions() -> list[dict[str, Any]]:
|
|
|
|
|
"""Lists all pending PowerShell scripts awaiting confirmation."""
|
|
|
|
|
with self._pending_dialog_lock:
|
|
|
|
|
return [
|
|
|
|
|
{"action_id": uid, "script": diag._script, "base_dir": diag._base_dir}
|
|
|
|
|
for uid, diag in self._pending_actions.items()
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
@api.post("/api/v1/confirm/{action_id}", dependencies=[Depends(get_api_key)])
|
|
|
|
|
def confirm_action(action_id: str, req: ConfirmRequest) -> dict[str, str]:
|
|
|
|
|
"""Approves or rejects a pending action."""
|
|
|
|
|
with self._pending_dialog_lock:
|
|
|
|
|
if action_id not in self._pending_actions:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Action not found")
|
|
|
|
|
dialog = self._pending_actions.pop(action_id)
|
|
|
|
|
if req.script is not None:
|
|
|
|
|
dialog._script = req.script
|
|
|
|
|
with dialog._condition:
|
|
|
|
|
dialog._approved = req.approved
|
|
|
|
|
dialog._done = True
|
|
|
|
|
dialog._condition.notify_all()
|
|
|
|
|
return {"status": "confirmed" if req.approved else "rejected"}
|
|
|
|
|
|
|
|
|
|
@api.get("/api/v1/sessions", dependencies=[Depends(get_api_key)])
|
|
|
|
|
def list_sessions() -> list[str]:
|
|
|
|
|
"""Lists all session log files."""
|
|
|
|
|
log_dir = Path("logs")
|
|
|
|
|
if not log_dir.exists():
|
|
|
|
|
return []
|
|
|
|
|
return [f.name for f in log_dir.glob("*.log")]
|
|
|
|
|
|
|
|
|
|
@api.get("/api/v1/sessions/{session_id}", dependencies=[Depends(get_api_key)])
|
|
|
|
|
def get_session(session_id: str) -> dict[str, Any]:
|
|
|
|
|
"""Returns the content of a specific session log."""
|
|
|
|
|
log_path = Path("logs") / session_id
|
|
|
|
|
if not log_path.exists():
|
|
|
|
|
raise HTTPException(status_code=404, detail="Session log not found")
|
|
|
|
|
return {"id": session_id, "content": log_path.read_text(encoding="utf-8", errors="replace")}
|
|
|
|
|
|
|
|
|
|
@api.delete("/api/v1/sessions/{session_id}", dependencies=[Depends(get_api_key)])
|
|
|
|
|
def delete_session(session_id: str) -> dict[str, str]:
|
|
|
|
|
"""Deletes a specific session log."""
|
|
|
|
|
log_path = Path("logs") / session_id
|
|
|
|
|
if not log_path.exists():
|
|
|
|
|
raise HTTPException(status_code=404, detail="Session log not found")
|
|
|
|
|
log_path.unlink()
|
|
|
|
|
return {"status": "deleted"}
|
|
|
|
|
|
|
|
|
|
@api.get("/api/v1/context", dependencies=[Depends(get_api_key)])
|
|
|
|
|
def get_context() -> dict[str, Any]:
|
|
|
|
|
"""Returns the current aggregated project context."""
|
|
|
|
|
try:
|
|
|
|
|
md, path, file_items, stable_md, disc_text = self._do_generate()
|
|
|
|
|
# Pull current screenshots if available in project
|
|
|
|
|
screenshots = self.project.get("screenshots", {}).get("paths", [])
|
|
|
|
|
return {
|
|
|
|
|
"files": [f.get("path") if isinstance(f, dict) else str(f) for f in file_items],
|
|
|
|
|
"screenshots": screenshots,
|
|
|
|
|
"files_base_dir": self.ui_files_base_dir,
|
|
|
|
|
"markdown": md,
|
|
|
|
|
"discussion": disc_text
|
|
|
|
|
}
|
|
|
|
|
except Exception as e:
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Context aggregation failure: {e}")
|
|
|
|
|
|
|
|
|
|
@api.get("/api/v1/token_stats", dependencies=[Depends(get_api_key)])
|
|
|
|
|
def token_stats() -> dict[str, Any]:
|
|
|
|
|
"""Returns current token usage and budget statistics."""
|
|
|
|
|
return self._token_stats
|
|
|
|
|
|
|
|
|
|
return api
|
|
|
|
|
# ---------------------------------------------------------------- project loading
|
|
|
|
|
|
|
|
|
|
@@ -1290,6 +1377,9 @@ class App:
|
|
|
|
|
self.session_usage["last_latency"] = payload["latency"]
|
|
|
|
|
self._recalculate_session_usage()
|
|
|
|
|
|
|
|
|
|
if md_content is not None:
|
|
|
|
|
self._token_stats = ai_client.get_token_stats(md_content)
|
|
|
|
|
|
|
|
|
|
cache_stats = payload.get("cache_stats")
|
|
|
|
|
if cache_stats:
|
|
|
|
|
count = cache_stats.get("cache_count", 0)
|
|
|
|
|
|