forked from retoor/devplacepy
docs: document server-side rendering pipeline, response timing middleware, and Telegram pairing API
- Add comprehensive documentation for backend content rendering in AGENTS.md, detailing the new `render_content` and `render_title` Jinja globals built on mistune with media processing, emoji shortcodes, and XSS protection
- Document the `X-Response-Time` header and bottom-left render time indicator in README.md
- Update bot token pricing documentation to clarify fallback vs gateway cost headers
- Add `email_accounts` to soft-delete tables and `idx_users_role` composite index in database schema
- Implement `telegram_pairings` and `telegram_links` table creation with column migration and indexes
- Add `/profile/{username}/telegram` endpoint to docs API with request/unpair actions
- Register `TelegramService` in main.py lifespan and add `response_timing` middleware emitting `X-Response-Time` header
- Introduce `TelegramPairForm` model and `guard_public_host_sync` synchronous host validation function
This commit is contained in:
@@ -19,6 +19,7 @@ from devplacepy.utils import (
|
||||
_user_from_session,
|
||||
get_current_user,
|
||||
is_admin,
|
||||
is_primary_admin,
|
||||
require_user,
|
||||
)
|
||||
from devplacepy.services.audit import record as audit
|
||||
@@ -59,9 +60,10 @@ def _resolve_ws_owner(websocket: WebSocket):
|
||||
user.get("username", ""),
|
||||
user.get("api_key", ""),
|
||||
is_admin(user),
|
||||
is_primary_admin(user),
|
||||
)
|
||||
guest_id = websocket.cookies.get(GUEST_COOKIE) or uuid_utils.uuid7().hex
|
||||
return "guest", guest_id, "guest", "", False
|
||||
guest_id = websocket.cookies.get(GUEST_COOKIE) or ""
|
||||
return "guest", guest_id, "guest", "", False, False
|
||||
|
||||
|
||||
def _owner_from_request(request: Request):
|
||||
@@ -157,8 +159,9 @@ async def devii_usage(request: Request):
|
||||
owner_kind, owner_id, _ = _owner_from_request(request)
|
||||
if svc is None:
|
||||
return JSONResponse({"used_pct": 0.0, "turns_today": 0})
|
||||
viewer_is_admin = is_admin(get_current_user(request))
|
||||
spent = svc.spent_24h(owner_kind, owner_id) if owner_id else 0.0
|
||||
limit = svc.daily_limit_for(owner_kind)
|
||||
limit = svc.daily_limit_for(owner_kind, viewer_is_admin)
|
||||
turns = svc.hub().ledger.turns_24h(owner_kind, owner_id) if owner_id else 0
|
||||
used_pct = round(min(100.0, spent / limit * 100), 1) if limit > 0 else 0.0
|
||||
payload = {
|
||||
@@ -166,7 +169,7 @@ async def devii_usage(request: Request):
|
||||
"turns_today": turns,
|
||||
"owner_kind": owner_kind,
|
||||
}
|
||||
if is_admin(get_current_user(request)):
|
||||
if viewer_is_admin:
|
||||
payload["spent_24h"] = round(spent, 6)
|
||||
payload["limit"] = limit
|
||||
return JSONResponse(payload)
|
||||
@@ -212,8 +215,8 @@ async def devii_ws(websocket: WebSocket):
|
||||
channel = websocket.query_params.get("channel", "main")
|
||||
if channel not in CHANNELS:
|
||||
channel = "main"
|
||||
owner_kind, owner_id, username, api_key, owner_is_admin = _resolve_ws_owner(
|
||||
websocket
|
||||
owner_kind, owner_id, username, api_key, owner_is_admin, owner_is_primary_admin = (
|
||||
_resolve_ws_owner(websocket)
|
||||
)
|
||||
if owner_kind == "guest" and not svc.guests_enabled():
|
||||
await websocket.send_json(
|
||||
@@ -221,6 +224,12 @@ async def devii_ws(websocket: WebSocket):
|
||||
)
|
||||
await websocket.close(code=1008)
|
||||
return
|
||||
if owner_kind == "guest" and not owner_id:
|
||||
await websocket.send_json(
|
||||
{"type": "error", "text": "Open Devii from the page to start a session."}
|
||||
)
|
||||
await websocket.close(code=1008)
|
||||
return
|
||||
|
||||
session = svc.hub().get_or_create(
|
||||
owner_kind,
|
||||
@@ -229,6 +238,7 @@ async def devii_ws(websocket: WebSocket):
|
||||
api_key,
|
||||
svc.instance_base_url(),
|
||||
is_admin=owner_is_admin,
|
||||
is_primary_admin=owner_is_primary_admin,
|
||||
channel=channel,
|
||||
)
|
||||
session.attach(websocket)
|
||||
@@ -248,9 +258,8 @@ async def devii_ws(websocket: WebSocket):
|
||||
if command == "reset":
|
||||
await session.reset()
|
||||
continue
|
||||
limit = svc.daily_limit_for(owner_kind, owner_is_admin)
|
||||
spent = svc.spent_24h(owner_kind, owner_id)
|
||||
if limit > 0 and spent >= limit:
|
||||
if svc.quota_exceeded(owner_kind, owner_id, owner_is_admin):
|
||||
limit = svc.daily_limit_for(owner_kind, owner_is_admin)
|
||||
audit.record_system(
|
||||
"ai.quota.exceeded",
|
||||
actor_kind=owner_kind if owner_kind == "guest" else "user",
|
||||
@@ -283,6 +292,12 @@ async def devii_ws(websocket: WebSocket):
|
||||
bool(data.get("visible", True)),
|
||||
bool(data.get("focused", False)),
|
||||
)
|
||||
elif kind == "clientinfo":
|
||||
offset = data.get("offset")
|
||||
session.set_clientinfo(
|
||||
str(data.get("timezone", "") or "").strip(),
|
||||
int(offset) if isinstance(offset, (int, float)) else None,
|
||||
)
|
||||
elif kind in ("avatar_result", "client_result"):
|
||||
session.resolve_query(str(data.get("id", "")), data.get("result"))
|
||||
except WebSocketDisconnect:
|
||||
|
||||
Reference in New Issue
Block a user