feat: add backup CLI commands, AI correction/modifier services, and timezone-aware date display
DevPlace CI / test (push) Failing after 25m18s
DevPlace CI / test (push) Failing after 25m18s
- Add `devplace backups` CLI subcommands (list, run, prune, clear) with job enqueueing and orphan cleanup - Introduce `BACKUPS_DIR` and `BACKUP_STAGING_DIR` config paths for backup storage - Implement `schedule_correction` and `schedule_modification` calls in content creation, comment creation, and comment editing flows - Add `DEFAULT_CORRECTION_PROMPT` and `DEFAULT_MODIFIER_PROMPT` config constants for AI content processing - Document timezone-aware date display using `local_dt`/`dt_ago` Jinja globals with client-side `Intl` localization - Update README with AI content correction/modifier support in direct messages via `@ai` inline instructions - Add `track_action(user["uid"], "vote")` call on upvote in `apply_vote`
This commit is contained in:
@@ -20,6 +20,7 @@ from devplacepy.services.openai_gateway.usage import (
|
||||
extract_params,
|
||||
parse_context_map,
|
||||
pricing_from_cfg,
|
||||
usage_response_headers,
|
||||
)
|
||||
from devplacepy.services.openai_gateway.vision import VisionAugmenter, VisionCache
|
||||
|
||||
@@ -294,10 +295,12 @@ class GatewayRuntime:
|
||||
base["success"] = success
|
||||
base["error_category"] = category
|
||||
base["usage"] = usage
|
||||
self._ledger.record(base, pricing, context_map)
|
||||
return usage_response_headers(
|
||||
self._ledger.record(base, pricing, context_map)
|
||||
)
|
||||
|
||||
if timing["circuit_open"]:
|
||||
finalize(503, False, "circuit_open")
|
||||
resp_headers = finalize(503, False, "circuit_open")
|
||||
return JSONResponse(
|
||||
status_code=503,
|
||||
content={
|
||||
@@ -306,9 +309,10 @@ class GatewayRuntime:
|
||||
"type": "circuit_open",
|
||||
}
|
||||
},
|
||||
headers=resp_headers,
|
||||
)
|
||||
if exc is not None:
|
||||
finalize(502, False, classify_error(0, exc))
|
||||
resp_headers = finalize(502, False, classify_error(0, exc))
|
||||
return JSONResponse(
|
||||
status_code=502,
|
||||
content={
|
||||
@@ -317,9 +321,10 @@ class GatewayRuntime:
|
||||
"type": "upstream_error",
|
||||
}
|
||||
},
|
||||
headers=resp_headers,
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
finalize(
|
||||
resp_headers = finalize(
|
||||
resp.status_code,
|
||||
False,
|
||||
classify_error(resp.status_code, None, resp.text),
|
||||
@@ -328,12 +333,13 @@ class GatewayRuntime:
|
||||
return JSONResponse(
|
||||
status_code=resp.status_code,
|
||||
content={"error": {"message": resp.text, "type": "upstream_error"}},
|
||||
headers=resp_headers,
|
||||
)
|
||||
try:
|
||||
data = resp.json()
|
||||
except ValueError:
|
||||
self.errors += 1
|
||||
finalize(502, False, "gateway")
|
||||
resp_headers = finalize(502, False, "gateway")
|
||||
log("chat upstream returned 200 but body was not valid JSON")
|
||||
return JSONResponse(
|
||||
status_code=502,
|
||||
@@ -343,14 +349,17 @@ class GatewayRuntime:
|
||||
"type": "upstream_error",
|
||||
}
|
||||
},
|
||||
headers=resp_headers,
|
||||
)
|
||||
finalize(200, True, None, data.get("usage"))
|
||||
resp_headers = finalize(200, True, None, data.get("usage"))
|
||||
log(f"chat POST -> 200 ({timing['upstream_latency_ms']:.0f}ms)")
|
||||
if stream:
|
||||
return StreamingResponse(
|
||||
_fake_stream(data, model), media_type="text/event-stream"
|
||||
_fake_stream(data, model),
|
||||
media_type="text/event-stream",
|
||||
headers=resp_headers,
|
||||
)
|
||||
return JSONResponse(content=data)
|
||||
return JSONResponse(content=data, headers=resp_headers)
|
||||
|
||||
async def handle_embeddings(
|
||||
self, body: dict, cfg: dict, owner: tuple, user_agent: str, log=None
|
||||
@@ -447,10 +456,12 @@ class GatewayRuntime:
|
||||
base["success"] = success
|
||||
base["error_category"] = category
|
||||
base["usage"] = usage
|
||||
self._ledger.record(base, pricing, context_map)
|
||||
return usage_response_headers(
|
||||
self._ledger.record(base, pricing, context_map)
|
||||
)
|
||||
|
||||
if timing["circuit_open"]:
|
||||
finalize(503, False, "circuit_open")
|
||||
resp_headers = finalize(503, False, "circuit_open")
|
||||
return JSONResponse(
|
||||
status_code=503,
|
||||
content={
|
||||
@@ -459,9 +470,10 @@ class GatewayRuntime:
|
||||
"type": "circuit_open",
|
||||
}
|
||||
},
|
||||
headers=resp_headers,
|
||||
)
|
||||
if exc is not None:
|
||||
finalize(502, False, classify_error(0, exc))
|
||||
resp_headers = finalize(502, False, classify_error(0, exc))
|
||||
return JSONResponse(
|
||||
status_code=502,
|
||||
content={
|
||||
@@ -470,9 +482,10 @@ class GatewayRuntime:
|
||||
"type": "upstream_error",
|
||||
}
|
||||
},
|
||||
headers=resp_headers,
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
finalize(
|
||||
resp_headers = finalize(
|
||||
resp.status_code,
|
||||
False,
|
||||
classify_error(resp.status_code, None, resp.text),
|
||||
@@ -481,12 +494,13 @@ class GatewayRuntime:
|
||||
return JSONResponse(
|
||||
status_code=resp.status_code,
|
||||
content={"error": {"message": resp.text, "type": "upstream_error"}},
|
||||
headers=resp_headers,
|
||||
)
|
||||
try:
|
||||
data = resp.json()
|
||||
except ValueError:
|
||||
self.errors += 1
|
||||
finalize(502, False, "gateway")
|
||||
resp_headers = finalize(502, False, "gateway")
|
||||
log("embed upstream returned 200 but body was not valid JSON")
|
||||
return JSONResponse(
|
||||
status_code=502,
|
||||
@@ -496,11 +510,12 @@ class GatewayRuntime:
|
||||
"type": "upstream_error",
|
||||
}
|
||||
},
|
||||
headers=resp_headers,
|
||||
)
|
||||
self.embed_calls += 1
|
||||
finalize(200, True, None, data.get("usage"))
|
||||
resp_headers = finalize(200, True, None, data.get("usage"))
|
||||
log(f"embed POST -> 200 ({timing['upstream_latency_ms']:.0f}ms)")
|
||||
return JSONResponse(content=data)
|
||||
return JSONResponse(content=data, headers=resp_headers)
|
||||
|
||||
async def handle_passthrough(
|
||||
self,
|
||||
@@ -559,10 +574,12 @@ class GatewayRuntime:
|
||||
base["success"] = success
|
||||
base["error_category"] = category
|
||||
base["usage"] = usage
|
||||
self._ledger.record(base, pricing, context_map)
|
||||
return usage_response_headers(
|
||||
self._ledger.record(base, pricing, context_map)
|
||||
)
|
||||
|
||||
if timing["circuit_open"]:
|
||||
finalize(503, False, "circuit_open")
|
||||
resp_headers = finalize(503, False, "circuit_open")
|
||||
return JSONResponse(
|
||||
status_code=503,
|
||||
content={
|
||||
@@ -571,9 +588,10 @@ class GatewayRuntime:
|
||||
"type": "circuit_open",
|
||||
}
|
||||
},
|
||||
headers=resp_headers,
|
||||
)
|
||||
if exc is not None:
|
||||
finalize(502, False, classify_error(0, exc))
|
||||
resp_headers = finalize(502, False, classify_error(0, exc))
|
||||
return JSONResponse(
|
||||
status_code=502,
|
||||
content={
|
||||
@@ -582,6 +600,7 @@ class GatewayRuntime:
|
||||
"type": "upstream_error",
|
||||
}
|
||||
},
|
||||
headers=resp_headers,
|
||||
)
|
||||
usage = None
|
||||
if resp.status_code < 400 and "application/json" in (
|
||||
@@ -591,7 +610,7 @@ class GatewayRuntime:
|
||||
usage = resp.json().get("usage")
|
||||
except ValueError:
|
||||
usage = None
|
||||
finalize(
|
||||
resp_headers = finalize(
|
||||
resp.status_code,
|
||||
resp.status_code < 400,
|
||||
None
|
||||
@@ -606,6 +625,7 @@ class GatewayRuntime:
|
||||
content=resp.content,
|
||||
status_code=resp.status_code,
|
||||
media_type=resp.headers.get("content-type"),
|
||||
headers=resp_headers,
|
||||
)
|
||||
|
||||
def metrics(self) -> dict:
|
||||
|
||||
Reference in New Issue
Block a user