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
+14 -2
View File
@@ -16,6 +16,7 @@ logger = logging.getLogger(__name__)
EMBED_TIMEOUT_SECONDS = 60.0
LOCAL_EMBED_DIMS = 256
EMBED_CACHE_MAX = 5000
TOKEN_PATTERN = re.compile(r"[a-z0-9]+")
@@ -26,10 +27,18 @@ class EmbedResult:
latency_ms: int = 0
cache_hits: int = 0
@property
def dims(self) -> int:
for vector in self.vectors:
if vector:
return len(vector)
return 0
@dataclass
class EmbeddingCache:
store: dict[str, list[float]] = field(default_factory=dict)
max_entries: int = EMBED_CACHE_MAX
def key(self, text: str) -> str:
return hashlib.sha1((text or "").encode("utf-8")).hexdigest()
@@ -38,8 +47,11 @@ class EmbeddingCache:
return self.store.get(self.key(text))
def put(self, text: str, vector: list[float]) -> None:
if vector:
self.store[self.key(text)] = vector
if not vector:
return
if len(self.store) >= self.max_entries:
self.store.pop(next(iter(self.store)), None)
self.store[self.key(text)] = vector
def _local_vector(text: str) -> list[float]: