from datetime import datetime from enum import Enum from typing import Any from pydantic import BaseModel, Field, field_validator class ReasoningType(str, Enum): CHAIN = "chain" IDEA = "idea" CONTEXT = "context" REFLECTION = "reflection" class DocumentCreate(BaseModel): title: str content: str = "" folder_id: str | None = None class DocumentUpdate(BaseModel): title: str | None = None folder_id: str | None = None class DocumentContentUpdate(BaseModel): content: str = Field(..., max_length=1_000_000) # 1MB limit # Phase 2: TipTap Content Update - Union to support both legacy string and new TipTap/Markdown formats class TipTapContentUpdate(BaseModel): content: dict[str, Any] | str # TipTap JSON object or Markdown string format: str = "tiptap" @field_validator("format") @classmethod def validate_format(cls, v: str) -> str: if v not in ("tiptap", "markdown"): raise ValueError("format must be 'tiptap' or 'markdown'") return v class TagInfo(BaseModel): id: str name: str color: str model_config = {"from_attributes": True} # Phase 2: Reasoning Step Schema class ReasoningStep(BaseModel): step: int thought: str = Field(..., max_length=2000) conclusion: str | None = Field(None, max_length=2000) # Phase 2: Reasoning Metadata Schema class ReasoningMetadata(BaseModel): reasoning_type: ReasoningType | None = None confidence: float | None = None reasoning_steps: list[ReasoningStep] = [] model_source: str | None = None model_config = {"protected_namespaces": ()} @field_validator("confidence") @classmethod def validate_confidence(cls, v: float | None) -> float | None: if v is not None and (v < 0.0 or v > 1.0): raise ValueError("confidence must be between 0.0 and 1.0") return v # Phase 2: Reasoning Update Schema class ReasoningUpdate(BaseModel): reasoning_type: ReasoningType | None = None confidence: float | None = None reasoning_steps: list[ReasoningStep] | None = None model_source: str | None = None model_config = {"protected_namespaces": ()} @field_validator("confidence") @classmethod def validate_confidence(cls, v: float | None) -> float | None: if v is not None and (v < 0.0 or v > 1.0): raise ValueError("confidence must be between 0.0 and 1.0") return v # Phase 2: Add Reasoning Step Schema class ReasoningStepAdd(BaseModel): thought: str = Field(..., min_length=1, max_length=2000) conclusion: str | None = Field(None, max_length=2000) # Phase 2: TipTap Content Response class TipTapContentResponse(BaseModel): content: dict[str, Any] | str format: str # Phase 2: Reasoning Panel Schema class ReasoningPanel(BaseModel): document_id: str has_reasoning: bool reasoning: ReasoningMetadata | None = None editable: bool = True class DocumentResponse(BaseModel): id: str title: str content: str project_id: str folder_id: str | None path: str tags: list[TagInfo] = [] created_at: datetime updated_at: datetime # Phase 2: new fields reasoning_type: ReasoningType | None = None confidence: float | None = None reasoning_steps: list[ReasoningStep] = [] model_source: str | None = None tiptap_content: dict[str, Any] | None = None model_config = {"from_attributes": True, "protected_namespaces": ()} class DocumentBriefResponse(BaseModel): """Brief document for list views without content.""" id: str title: str project_id: str folder_id: str | None path: str tags: list[TagInfo] = [] created_at: datetime updated_at: datetime model_config = {"from_attributes": True} class DocumentListResponse(BaseModel): documents: list[DocumentBriefResponse]