image input pipeline: content-hash cache, data-url vision, picture_edit real

This commit is contained in:
Oleksandr Kozachuk
2026-07-13 16:33:48 +02:00
parent 7e6eae10ee
commit e21c262299
8 changed files with 496 additions and 10 deletions
+54 -1
View File
@@ -13,7 +13,7 @@ from contextlib import closing
from pathlib import Path
from typing import Any, Dict, List, Optional
SCHEMA_VERSION = 3
SCHEMA_VERSION = 4
class PersistentStore:
@@ -60,6 +60,12 @@ class PersistentStore:
)
# Legacy single-string memories carry over as one episode each (MEM-08)
conn.execute("INSERT INTO episodes (channel, summary) SELECT channel, content FROM memory")
if version < 4:
conn.execute(
"CREATE TABLE IF NOT EXISTS images (id INTEGER PRIMARY KEY, sha256 TEXT UNIQUE NOT NULL, channel TEXT NOT NULL,"
" user TEXT NOT NULL, message_id TEXT, ext TEXT NOT NULL, bytes INTEGER NOT NULL,"
" created_at TEXT NOT NULL DEFAULT (datetime('now')))"
)
if version < SCHEMA_VERSION:
conn.execute(f"PRAGMA user_version = {SCHEMA_VERSION}")
os.chmod(self.db_path, 0o600) # conversation data (PER-04)
@@ -189,6 +195,53 @@ class PersistentStore:
)
return cursor.rowcount
# --- image cache index (SPEC-004, FDB-010) ---
def image_add(self, sha256: str, channel: str, user: str, message_id: Optional[str], ext: str, nbytes: int) -> None:
with closing(self._connect()) as conn, conn:
conn.execute(
"INSERT OR IGNORE INTO images (sha256, channel, user, message_id, ext, bytes) VALUES (?, ?, ?, ?, ?, ?)",
(sha256, channel, user, message_id, ext, nbytes),
)
def images_recent(self, channel: str, count: int) -> List[Dict[str, Any]]:
with closing(self._connect()) as conn:
rows = conn.execute(
"SELECT sha256, user, ext FROM images WHERE channel = ? ORDER BY id DESC LIMIT ?", (channel, count)
).fetchall()
return [{"sha256": row[0], "user": row[1], "ext": row[2]} for row in rows]
def images_total_bytes(self) -> int:
with closing(self._connect()) as conn:
row = conn.execute("SELECT COALESCE(SUM(bytes), 0) FROM images").fetchone()
return int(row[0])
def images_oldest(self, count: int) -> List[Dict[str, Any]]:
with closing(self._connect()) as conn:
rows = conn.execute("SELECT sha256, ext, bytes FROM images ORDER BY id LIMIT ?", (count,)).fetchall()
return [{"sha256": row[0], "ext": row[1], "bytes": row[2]} for row in rows]
def images_expired(self, ttl_days: int) -> List[Dict[str, Any]]:
with closing(self._connect()) as conn:
rows = conn.execute(
"SELECT sha256, ext FROM images WHERE created_at < datetime('now', ?)", (f"-{int(ttl_days)} days",)
).fetchall()
return [{"sha256": row[0], "ext": row[1]} for row in rows]
def images_delete(self, sha256: str) -> None:
with closing(self._connect()) as conn, conn:
conn.execute("DELETE FROM images WHERE sha256 = ?", (sha256,))
def images_for_user(self, user: str) -> List[Dict[str, Any]]:
with closing(self._connect()) as conn:
rows = conn.execute("SELECT sha256, ext FROM images WHERE user = ?", (user,)).fetchall()
return [{"sha256": row[0], "ext": row[1]} for row in rows]
def images_for_message(self, message_id: str) -> List[Dict[str, Any]]:
with closing(self._connect()) as conn:
rows = conn.execute("SELECT sha256, ext FROM images WHERE message_id = ?", (message_id,)).fetchall()
return [{"sha256": row[0], "ext": row[1]} for row in rows]
def purge_user_memory(self, user: str) -> int:
"""Facts, observations and episode traces of one user (MEM-09)."""
removed = 0