ppt-tool/backend/models/sql/ai_usage.py
Vadym Samoilenko d3d1667a79 Phase 2: Admin panel, analytics, storage, template pipeline, multi-provider LLM
- Fix admin sidebar: remove duplicate Teams, add Storage nav item
- Analytics: client-scoped queries, super_admin sees all (including NULL client_id)
- Storage management: list/download/delete presentations with file metadata
- Settings page with brand config router
- AI usage tracking: new AIUsageModel, ai_usage_service, analytics endpoint
- Master deck → template bridge: _register_as_template creates TemplateModel
  + PresentationLayoutCodeModel so parsed layouts appear in template picker
- Multi-provider LLM vision in parser: Anthropic/Google/OpenAI with asyncio.to_thread
- Fix PPTX upload 400: accept by .pptx extension (browser sends octet-stream)
- Fix reparse FK violation: presentation_id=None for parse_master_deck jobs
- Worker job_timeout increased to 1800s for LLM-heavy master deck parsing
- PYTHONUNBUFFERED=1 in docker-compose worker for real-time log output
- Auth: clientId in /me response, dev-login cookie improvements
- Frontend: auth slice clientId, master-deck thumbnails, storage page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 23:39:34 +00:00

36 lines
1.5 KiB
Python

"""AI usage tracking model for analytics."""
import uuid
from datetime import datetime
from typing import Optional
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String
from sqlmodel import Field, SQLModel
from utils.datetime_utils import get_current_utc_datetime
class AIUsageModel(SQLModel, table=True):
__tablename__ = "ai_usage_logs"
id: uuid.UUID = Field(primary_key=True, default_factory=uuid.uuid4)
user_id: Optional[uuid.UUID] = Field(
sa_column=Column(ForeignKey("users.id"), nullable=True), default=None
)
client_id: Optional[uuid.UUID] = Field(
sa_column=Column(ForeignKey("clients.id"), nullable=True), default=None
)
presentation_id: Optional[uuid.UUID] = Field(
sa_column=Column(ForeignKey("presentations.id"), nullable=True), default=None
)
provider: str = Field(sa_column=Column(String, nullable=False))
model: str = Field(sa_column=Column(String, nullable=False))
call_type: str = Field(sa_column=Column(String, nullable=False))
input_tokens: Optional[int] = Field(sa_column=Column(Integer, nullable=True), default=None)
output_tokens: Optional[int] = Field(sa_column=Column(Integer, nullable=True), default=None)
total_tokens: Optional[int] = Field(sa_column=Column(Integer, nullable=True), default=None)
duration_ms: Optional[int] = Field(sa_column=Column(Integer, nullable=True), default=None)
created_at: datetime = Field(
sa_column=Column(
DateTime(timezone=True), nullable=False, default=get_current_utc_datetime
),
)