NoteStack-Test / backend /app /services /artifacts.py
HumeraR's picture
feat: add MemoryNotes component to Settings page and enhance UI styles
b74c6ae
Raw History Blame
2.93 kB
"""Artifacts are every generated thing (summaries, audio, video, launch kits, stills). Creating one always
means: artifact row + queued job, returned together so the UI can stream progress."""
import uuid
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.models import Artifact, Job
from app.services.jobs import create_job, serialize_job
from app.services.storage import storage
TYPE_LABELS = {
"summary": "Summary",
"audio_overview": "Audio overview",
"video": "Video",
"quote_card": "Quote card",
"carousel": "Carousel",
"launch_kit": "Launch Kit",
"mind_map": "Mind Constellation",
}
def start_artifact(
db: Session,
workspace_id: uuid.UUID,
type_: str,
*,
title: str,
notebook_id: uuid.UUID | None = None,
document_id: uuid.UUID | None = None,
content: dict | None = None,
job_kind: str | None = None,
params: dict | None = None,
max_attempts: int = 2,
) -> tuple[Artifact, Job]:
artifact = Artifact(workspace_id=workspace_id, type=type_, notebook_id=notebook_id, document_id=document_id,
status="pending", content_json={"title": title, **(content or {})})
db.add(artifact)
db.commit()
job = create_job(db, workspace_id, job_kind or type_, {"artifact_id": str(artifact.id), **(params or {})},
artifact_id=artifact.id, max_attempts=max_attempts)
return artifact, job
def latest_jobs(db: Session, artifact_ids: list[uuid.UUID]) -> dict[uuid.UUID, Job]:
if not artifact_ids:
return {}
jobs = db.scalars(select(Job).where(Job.artifact_id.in_(artifact_ids)).order_by(Job.created_at)).all()
return {j.artifact_id: j for j in jobs} # later rows win, so this is the newest job per artifact
def serialize_artifact(a: Artifact, job: Job | None = None) -> dict:
content = a.content_json or {}
slides = content.get("slide_keys") or []
return {
"id": str(a.id),
"type": a.type,
"type_label": TYPE_LABELS.get(a.type, a.type),
"title": content.get("title") or TYPE_LABELS.get(a.type, a.type),
"status": a.status,
"notebook_id": str(a.notebook_id) if a.notebook_id else None,
"document_id": str(a.document_id) if a.document_id else None,
"content": content,
"storage_key": a.storage_key,
"url": storage.presign_get(a.storage_key) if a.storage_key else None,
"download_url": storage.presign_get(a.storage_key, download_name=_download_name(a)) if a.storage_key else None,
"slide_urls": [storage.presign_get(k) for k in slides],
"created_at": a.created_at.isoformat() if a.created_at else None,
"job": serialize_job(job) if job else None,
}
def _download_name(a: Artifact) -> str:
title = (a.content_json or {}).get("title") or a.type
ext = (a.storage_key or "").rsplit(".", 1)[-1]
return f"{title[:60]}.{ext}"