feat: add seo_meta service for AI-generated SEO metadata with CLI management and database layer
DevPlace CI / test (push) Failing after 2m13s

Implement a new `SeoMetaService` subservice that generates clean SEO title/description/keywords for published content items, distinct from the existing SEO diagnostics auditor. Add `seo_metadata` polymorphic table with soft-delete support, batch query methods, and usage tracking. Extend the CLI with `seo-meta prune` and `seo-meta clear` commands for job row lifecycle management. Wire `schedule_seo_meta_for_table` into content creation and editing flows in `content.py`. Document the new service in `AGENTS.md` and `README.md`, including the `extra_head` site setting for custom `<head>` injection.
This commit is contained in:
2026-06-19 20:15:22 +00:00
parent 426d3639c6
commit d10f1af118
51 changed files with 2262 additions and 93 deletions
@@ -92,6 +92,56 @@ def test_worker_run_produces_report(monkeypatch):
VectorStore("ds_worker_test_one").drop()
def test_index_chunks_emits_batch_progress(monkeypatch):
async def fake_embed_async(texts, api_key, **kwargs):
return local_embed(texts)
monkeypatch.setattr(worker_module, "embed_texts", fake_embed_async)
monkeypatch.setattr(worker_module, "EMBED_BATCH", 1)
frames = []
pages = [
CrawledPage(
url="https://example.com/a",
title="Page A",
text="The transistor was invented at Bell Labs. " * 30,
source="httpx",
status=200,
)
]
store = VectorStore("ds_index_chunks_test")
try:
chunk_count, backend = run_async(
worker_module._index_chunks(store, pages, "k", frames.append)
)
assert chunk_count > 0
assert backend in ("gateway", "local")
batch_frames = [f for f in frames if f.get("type") == "embed_batch"]
assert batch_frames
assert all(f["total"] == chunk_count for f in batch_frames)
assert max(f["done"] for f in batch_frames) == chunk_count
done_frames = [f for f in frames if f.get("type") == "embed_done"]
assert done_frames and done_frames[-1]["chunk_count"] == chunk_count
finally:
store.drop()
def test_index_chunks_empty_pages_emits_done(monkeypatch):
frames = []
store = VectorStore("ds_index_chunks_empty_test")
try:
chunk_count, backend = run_async(
worker_module._index_chunks(store, [], "k", frames.append)
)
assert chunk_count == 0
assert backend == "empty"
assert any(
f.get("type") == "embed_done" and f.get("chunk_count") == 0 for f in frames
)
finally:
store.drop()
def test_worker_control_cancel_stops(monkeypatch):
pages = [
CrawledPage(url="https://x.example", title="X", text="content " * 40, source="httpx", status=200)