feat: chat improvements, file viewer, usage fixes, and agent filesystem awareness
This commit is contained in:
@@ -909,17 +909,6 @@ async def get_usage(
|
||||
params: list = []
|
||||
n = 1
|
||||
|
||||
# Exclude email handler agents
|
||||
handler_ids_rows = await pool.fetch(
|
||||
"SELECT agent_id FROM email_accounts WHERE agent_id IS NOT NULL"
|
||||
)
|
||||
handler_ids = [str(r["agent_id"]) for r in handler_ids_rows]
|
||||
if handler_ids:
|
||||
placeholders = ", ".join(f"${n + i}" for i in range(len(handler_ids)))
|
||||
clauses.append(f"ar.agent_id NOT IN ({placeholders})")
|
||||
params.extend(handler_ids)
|
||||
n += len(handler_ids)
|
||||
|
||||
if since_dt:
|
||||
clauses.append(f"ar.started_at >= ${n}"); params.append(since_dt); n += 1
|
||||
if end:
|
||||
@@ -1039,6 +1028,20 @@ async def get_usage(
|
||||
}
|
||||
|
||||
|
||||
# ── Usage: clear cost data ────────────────────────────────────────────────────
|
||||
|
||||
@router.delete("/usage/cost")
|
||||
async def clear_cost_data(request: Request):
|
||||
"""Delete all agent_runs and null out conversation cost_usd. Admin only."""
|
||||
_require_admin(request)
|
||||
from ..database import get_pool as _gp
|
||||
pool = await _gp()
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute("DELETE FROM agent_runs")
|
||||
await conn.execute("UPDATE conversations SET cost_usd = NULL WHERE cost_usd IS NOT NULL")
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# ── Inbox triggers ────────────────────────────────────────────────────────────
|
||||
|
||||
class InboxTriggerIn(BaseModel):
|
||||
@@ -2857,6 +2860,38 @@ async def download_my_file(request: Request, path: str):
|
||||
)
|
||||
|
||||
|
||||
_FB_TEXT_EXTS = {
|
||||
".md", ".txt", ".json", ".xml", ".yaml", ".yml", ".csv",
|
||||
".html", ".htm", ".css", ".js", ".ts", ".jsx", ".tsx",
|
||||
".py", ".sh", ".bash", ".zsh", ".log", ".sql", ".toml",
|
||||
".ini", ".conf", ".cfg", ".env", ".gitignore", ".dockerfile",
|
||||
".rst", ".tex", ".diff", ".patch", ".nfo", ".tsv",
|
||||
}
|
||||
|
||||
|
||||
@router.get("/my/files/view")
|
||||
async def view_my_file(request: Request, path: str):
|
||||
user = _require_auth(request)
|
||||
from ..users import get_user_folder
|
||||
base = await get_user_folder(user.id)
|
||||
if not base:
|
||||
raise HTTPException(status_code=404, detail="No files folder configured")
|
||||
target = _resolve_user_path(base, path)
|
||||
if not _os.path.isfile(target):
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
ext = _os.path.splitext(target)[1].lower()
|
||||
if ext not in _FB_TEXT_EXTS:
|
||||
raise HTTPException(status_code=415, detail="File type not supported for viewing")
|
||||
size = _os.path.getsize(target)
|
||||
_MAX_VIEW = 512 * 1024 # 512 KB
|
||||
try:
|
||||
with open(target, "r", encoding="utf-8", errors="replace") as fh:
|
||||
content = fh.read(_MAX_VIEW)
|
||||
except OSError as exc:
|
||||
raise HTTPException(status_code=500, detail=str(exc))
|
||||
return {"content": content, "size": size, "truncated": size > _MAX_VIEW}
|
||||
|
||||
|
||||
@router.get("/my/files/download-zip")
|
||||
async def download_my_zip(request: Request, path: str = ""):
|
||||
user = _require_auth(request)
|
||||
|
||||
Reference in New Issue
Block a user