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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user