- 3-column CSS masonry grid (2 on tablet, 1 on mobile) - Link cards show screenshot thumbnails - Note cards show content body inline - Tags as pills, folder/date in meta footer - Screenshot serving endpoint added to brain API - Auto-polling for pending items (3s interval) - Detail sheet shows raw_content for notes - Warm frosted glass card styling matching Atelier design Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
112 lines
2.5 KiB
Python
112 lines
2.5 KiB
Python
"""Pydantic schemas for API request/response."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
# ── Request schemas ──
|
|
|
|
class ItemCreate(BaseModel):
|
|
type: str = "link"
|
|
url: Optional[str] = None
|
|
raw_content: Optional[str] = None
|
|
title: Optional[str] = None
|
|
folder: Optional[str] = None
|
|
tags: Optional[list[str]] = None
|
|
|
|
|
|
class ItemUpdate(BaseModel):
|
|
title: Optional[str] = None
|
|
folder: Optional[str] = None
|
|
tags: Optional[list[str]] = None
|
|
raw_content: Optional[str] = None
|
|
|
|
|
|
class SearchQuery(BaseModel):
|
|
q: str
|
|
folder: Optional[str] = None
|
|
tags: Optional[list[str]] = None
|
|
type: Optional[str] = None
|
|
limit: int = Field(default=20, le=100)
|
|
offset: int = 0
|
|
|
|
|
|
class SemanticSearchQuery(BaseModel):
|
|
q: str
|
|
folder: Optional[str] = None
|
|
type: Optional[str] = None
|
|
limit: int = Field(default=20, le=100)
|
|
|
|
|
|
class HybridSearchQuery(BaseModel):
|
|
q: str
|
|
folder: Optional[str] = None
|
|
tags: Optional[list[str]] = None
|
|
type: Optional[str] = None
|
|
limit: int = Field(default=20, le=100)
|
|
|
|
|
|
# ── Response schemas ──
|
|
|
|
class AssetOut(BaseModel):
|
|
id: str
|
|
asset_type: str
|
|
filename: str
|
|
content_type: Optional[str] = None
|
|
size_bytes: Optional[int] = None
|
|
created_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class ItemOut(BaseModel):
|
|
id: str
|
|
type: str
|
|
title: Optional[str] = None
|
|
url: Optional[str] = None
|
|
raw_content: Optional[str] = None
|
|
extracted_text: Optional[str] = None
|
|
folder: Optional[str] = None
|
|
tags: Optional[list[str]] = None
|
|
summary: Optional[str] = None
|
|
confidence: Optional[float] = None
|
|
processing_status: str
|
|
processing_error: Optional[str] = None
|
|
metadata_json: Optional[dict] = None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
assets: list[AssetOut] = []
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class ItemList(BaseModel):
|
|
items: list[ItemOut]
|
|
total: int
|
|
|
|
|
|
class SearchResult(BaseModel):
|
|
items: list[ItemOut]
|
|
total: int
|
|
query: str
|
|
|
|
|
|
class ConfigOut(BaseModel):
|
|
folders: list[str]
|
|
tags: list[str]
|
|
|
|
|
|
# ── OpenAI classification schema ──
|
|
|
|
class ClassificationResult(BaseModel):
|
|
"""What the AI returns for each item."""
|
|
folder: str
|
|
tags: list[str] = Field(min_length=2, max_length=3)
|
|
title: str
|
|
summary: str
|
|
confidence: float = Field(ge=0.0, le=1.0)
|