feat: web UI chat render, panels, presence + analytics

This commit is contained in:
hh committed 2026-05-31 19:41:37 +02:00
1 parent 75425d1bee
commit ed469ba8dd
83 files changed
+6034 -136

No files matched your search

+69
View File
@@ -0,0 +1,69 @@
from datetime import UTC, datetime, timedelta
import asyncpg
from utils.read.accounts import self_user_id
from utils.read.models import ResponseStats, VolumeBucket
async def message_volume(
pool: asyncpg.Pool, account_id: int, chat_id: int, *, days: int = 90
) -> list[VolumeBucket]:
self_id = await self_user_id(pool, account_id)
rows = await pool.fetch(
"SELECT date_trunc('day', date) AS bucket, count(*) AS total, "
"count(*) FILTER (WHERE sender_id = $3) AS outgoing "
"FROM messages "
"WHERE account_id = $1 AND chat_id = $2 AND date >= $4 "
"GROUP BY bucket ORDER BY bucket",
account_id,
chat_id,
self_id,
datetime.now(UTC) - timedelta(days=days),
)
return [
VolumeBucket(
bucket=row["bucket"],
total=row["total"],
outgoing=row["outgoing"],
incoming=row["total"] - row["outgoing"],
)
for row in rows
]
async def response_stats(
pool: asyncpg.Pool, account_id: int, chat_id: int
) -> ResponseStats:
self_id = await self_user_id(pool, account_id)
rows = await pool.fetch(
"WITH ordered AS ("
"SELECT sender_id, date, "
"lag(sender_id) OVER w AS prev_sender, "
"lag(date) OVER w AS prev_date "
"FROM messages "
"WHERE account_id = $1 AND chat_id = $2 AND sender_id IS NOT NULL "
"WINDOW w AS (ORDER BY date, message_id)), "
"resp AS ("
"SELECT (sender_id = $3) AS is_mine, "
"EXTRACT(EPOCH FROM (date - prev_date)) AS secs "
"FROM ordered "
"WHERE prev_sender IS NOT NULL AND prev_sender <> sender_id) "
"SELECT is_mine, count(*) AS n, "
"percentile_cont(0.5) WITHIN GROUP (ORDER BY secs) AS median_secs "
"FROM resp GROUP BY is_mine",
account_id,
chat_id,
self_id,
)
stats = ResponseStats(
mine_median_seconds=None, mine_count=0, their_median_seconds=None, their_count=0
)
for row in rows:
if row["is_mine"]:
stats.mine_median_seconds = row["median_secs"]
stats.mine_count = row["n"]
else:
stats.their_median_seconds = row["median_secs"]
stats.their_count = row["n"]
return stats