* chore: update Google Fonts to Bricolage Grotesque, IBM Plex Sans, JetBrains Mono Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: update Tailwind config to Slate & Ice theme colors and fonts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: update CSS variables and glass-card utilities for Slate & Ice theme - Replace all color variables with Slate & Ice palette - Add glass system vars (--glass-bg, --glass-blur, --shadow-float) - Replace legacy glass-card with new variable-driven glass classes - Add breatheGlow, bellWobble, slideDown, fadeInRight keyframes - Update font references to IBM Plex Sans and Bricolage Grotesque Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: recolor BrandLogo to cyan gradient, split BrandWordmark for gradient Flow text Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: update TopBar with glassmorphism backdrop and cyan accent styling Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: update Sidebar with glassmorphism backdrop Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add ambient atmosphere gradient orbs behind app shell Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: update QuickStats and SessionsPanel with glass-card styling Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add WeeklyCalendar, QuickActions, OpenSessions, RecentActivity dashboard components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: redesign dashboard layout with calendar, open sessions, and glass-card panels New layout: greeting → calendar+actions → sessions+stats → activity Replaces old QuickStats and SessionsPanel with new dashboard components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: replace remaining purple hex references with ice-cyan accent Sweep of hardcoded purple hex values (#818cf8, #6366f1) replaced with new cyan accent (#06b6d4) in QuickActions, RecentActivity, QuickLaunch, and SVG brand assets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: update CLAUDE.md branding and design system for Slate & Ice Modern Updated Last Updated date, branding section (fonts, colors, glass utilities, atmosphere orbs), component styling rules, and Design System section to reflect the new ice-cyan glassmorphism theme. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add Slate & Ice Modern design doc and implementation plan Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: redesign login page with Slate & Ice Modern design system Apply glassmorphism styling, atmosphere orbs, branded wordmark, and consistent design tokens to match the updated app shell aesthetic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: raise TopBar z-index so profile dropdown renders above main content Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add AI assistant with in-session copilot and standalone chat with RAG Implements three-phase AI assistant feature: - Phase 0: RAG infrastructure with pgvector embeddings, Voyage AI integration, tree chunking service, and semantic search over team's flow library - Phase 1: In-session copilot panel during flow navigation with contextual AI help, current step awareness, and suggested related flows - Phase 2: Standalone AI chat page with persistent conversation history, pin/delete, and configurable retention policies (account-level) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add account management, email verification, AI fixes, and user guides - Profile settings, account transfer, delete/leave account flows - Email verification with JWT tokens and Resend integration - AI assistant/copilot fixes: markdown rendering, shared RAG helpers, token tracking, input refocus, model_validate usage - User guides hub + detail pages with 13 topic guides - Sidebar and top bar navigation for guides Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: prevent stale chunk errors after deployments - Set Cache-Control no-cache on index.html in nginx so browsers always fetch fresh chunk references after a deploy - Auto-reload on chunk load failures (stale deploy detection) with loop prevention via sessionStorage - Show friendly "App Updated" message if auto-reload doesn't resolve it Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add email verification toggle to admin settings Adds platform-level toggle to enable/disable email verification. When disabled, the verification banner is hidden and the send endpoint returns 403. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
"""Chat retention cleanup job.
|
|
|
|
Runs daily via APScheduler to enforce account-level retention settings:
|
|
- Delete non-pinned chats older than chat_retention_days
|
|
- Delete oldest non-pinned chats when count exceeds chat_retention_max_count
|
|
"""
|
|
import logging
|
|
from datetime import datetime, timezone, timedelta
|
|
|
|
from sqlalchemy import select, delete, func
|
|
|
|
from app.core.database import async_session_maker
|
|
from app.models.account import Account
|
|
from app.models.assistant_chat import AssistantChat
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def cleanup_expired_chats() -> None:
|
|
"""Enforce chat retention policies for all accounts."""
|
|
async with async_session_maker() as db:
|
|
try:
|
|
result = await db.execute(select(Account))
|
|
accounts = result.scalars().all()
|
|
|
|
total_deleted = 0
|
|
for account in accounts:
|
|
deleted = await _cleanup_account_chats(account, db)
|
|
total_deleted += deleted
|
|
|
|
await db.commit()
|
|
if total_deleted > 0:
|
|
logger.info("[retention] Cleaned up %d expired chats", total_deleted)
|
|
except Exception as e:
|
|
logger.error("[retention] Chat cleanup failed: %s", e)
|
|
await db.rollback()
|
|
|
|
|
|
async def _cleanup_account_chats(account: Account, db) -> int:
|
|
"""Enforce retention for a single account. Returns count deleted."""
|
|
deleted = 0
|
|
|
|
# Age-based retention
|
|
if account.chat_retention_days:
|
|
cutoff = datetime.now(timezone.utc) - timedelta(days=account.chat_retention_days)
|
|
result = await db.execute(
|
|
delete(AssistantChat)
|
|
.where(
|
|
AssistantChat.account_id == account.id,
|
|
AssistantChat.pinned == False, # noqa: E712
|
|
AssistantChat.updated_at < cutoff,
|
|
)
|
|
.returning(AssistantChat.id)
|
|
)
|
|
deleted += len(result.all())
|
|
|
|
# Count-based retention
|
|
if account.chat_retention_max_count:
|
|
total = await db.scalar(
|
|
select(func.count(AssistantChat.id)).where(
|
|
AssistantChat.account_id == account.id,
|
|
)
|
|
) or 0
|
|
|
|
if total > account.chat_retention_max_count:
|
|
excess = total - account.chat_retention_max_count
|
|
# Get oldest non-pinned chat IDs
|
|
oldest = await db.execute(
|
|
select(AssistantChat.id)
|
|
.where(
|
|
AssistantChat.account_id == account.id,
|
|
AssistantChat.pinned == False, # noqa: E712
|
|
)
|
|
.order_by(AssistantChat.updated_at.asc())
|
|
.limit(excess)
|
|
)
|
|
ids_to_delete = [row[0] for row in oldest.all()]
|
|
if ids_to_delete:
|
|
await db.execute(
|
|
delete(AssistantChat).where(AssistantChat.id.in_(ids_to_delete))
|
|
)
|
|
deleted += len(ids_to_delete)
|
|
|
|
return deleted
|