feat: add backup CLI commands, AI correction/modifier services, and timezone-aware date display
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:
2026-06-16 03:32:19 +00:00
parent e59bc2d34e
commit 15bd4ad87c
115 changed files with 5839 additions and 187 deletions
+39 -19
View File
@@ -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: