feat: implement TTS, Document processing, and Memory Service /facts API

- TTS: xtts-v2 integration with voice cloning support
- Document: docling integration for PDF/DOCX/PPTX processing
- Memory Service: added /facts/upsert, /facts/{key}, /facts endpoints
- Added required dependencies (TTS, docling)
This commit is contained in:
Apple
2026-01-17 08:16:37 -08:00
parent a9fcadc6e2
commit 5290287058
121 changed files with 17071 additions and 436 deletions

View File

@@ -406,6 +406,117 @@ class Database:
""", thread_id)
return dict(row) if row else None
# ========================================================================
# FACTS (Simple Key-Value storage)
# ========================================================================
async def ensure_facts_table(self):
"""Create facts table if not exists"""
async with self.pool.acquire() as conn:
await conn.execute("""
CREATE TABLE IF NOT EXISTS user_facts (
fact_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id TEXT NOT NULL,
team_id TEXT,
fact_key TEXT NOT NULL,
fact_value TEXT,
fact_value_json JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, team_id, fact_key)
);
CREATE INDEX IF NOT EXISTS idx_user_facts_user_id ON user_facts(user_id);
CREATE INDEX IF NOT EXISTS idx_user_facts_team_id ON user_facts(team_id);
""")
async def upsert_fact(
self,
user_id: str,
fact_key: str,
fact_value: Optional[str] = None,
fact_value_json: Optional[dict] = None,
team_id: Optional[str] = None
) -> Dict[str, Any]:
"""Create or update a user fact"""
async with self.pool.acquire() as conn:
row = await conn.fetchrow("""
INSERT INTO user_facts (user_id, team_id, fact_key, fact_value, fact_value_json)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (user_id, team_id, fact_key)
DO UPDATE SET
fact_value = EXCLUDED.fact_value,
fact_value_json = EXCLUDED.fact_value_json,
updated_at = NOW()
RETURNING *
""", user_id, team_id, fact_key, fact_value, fact_value_json)
return dict(row) if row else {}
async def get_fact(
self,
user_id: str,
fact_key: str,
team_id: Optional[str] = None
) -> Optional[Dict[str, Any]]:
"""Get a specific fact"""
async with self.pool.acquire() as conn:
if team_id:
row = await conn.fetchrow("""
SELECT * FROM user_facts
WHERE user_id = $1 AND fact_key = $2 AND team_id = $3
""", user_id, fact_key, team_id)
else:
row = await conn.fetchrow("""
SELECT * FROM user_facts
WHERE user_id = $1 AND fact_key = $2 AND team_id IS NULL
""", user_id, fact_key)
return dict(row) if row else None
async def list_facts(
self,
user_id: str,
team_id: Optional[str] = None
) -> List[Dict[str, Any]]:
"""List all facts for a user"""
async with self.pool.acquire() as conn:
if team_id:
rows = await conn.fetch("""
SELECT * FROM user_facts
WHERE user_id = $1 AND team_id = $2
ORDER BY fact_key
""", user_id, team_id)
else:
rows = await conn.fetch("""
SELECT * FROM user_facts
WHERE user_id = $1
ORDER BY fact_key
""", user_id)
return [dict(row) for row in rows]
async def delete_fact(
self,
user_id: str,
fact_key: str,
team_id: Optional[str] = None
) -> bool:
"""Delete a fact"""
async with self.pool.acquire() as conn:
if team_id:
result = await conn.execute("""
DELETE FROM user_facts
WHERE user_id = $1 AND fact_key = $2 AND team_id = $3
""", user_id, fact_key, team_id)
else:
result = await conn.execute("""
DELETE FROM user_facts
WHERE user_id = $1 AND fact_key = $2 AND team_id IS NULL
""", user_id, fact_key)
return "DELETE 1" in result
# ========================================================================
# STATS
# ========================================================================
@@ -418,11 +529,18 @@ class Database:
memories = await conn.fetchval("SELECT COUNT(*) FROM long_term_memory_items WHERE valid_to IS NULL")
summaries = await conn.fetchval("SELECT COUNT(*) FROM thread_summaries")
# Add facts count safely
try:
facts = await conn.fetchval("SELECT COUNT(*) FROM user_facts")
except:
facts = 0
return {
"threads": threads,
"events": events,
"active_memories": memories,
"summaries": summaries
"summaries": summaries,
"facts": facts
}