from datetime import datetime from decimal import Decimal from typing import Optional, Literal from uuid import UUID from pydantic import BaseModel, Field class StepCommand(BaseModel): """A command that can be run as part of a step.""" label: str command: str command_type: Optional[str] = None # e.g., 'powershell', 'cmd', 'bash' class StepContent(BaseModel): """Content structure for step library entries (stored as JSONB).""" instructions: str = Field(..., min_length=1) help_text: Optional[str] = None commands: Optional[list[StepCommand]] = None group_label: Optional[str] = None # Section header this step belongs to (for flow-synced steps) # Base schemas class StepLibraryBase(BaseModel): title: str = Field(..., min_length=1, max_length=255) step_type: Literal['decision', 'action', 'solution'] content: StepContent category_id: Optional[UUID] = None tags: list[str] = Field(default_factory=list) visibility: Literal['private', 'team', 'public'] = 'private' class StepLibraryCreate(StepLibraryBase): account_id: Optional[UUID] = None class StepLibraryUpdate(BaseModel): title: Optional[str] = Field(None, min_length=1, max_length=255) step_type: Optional[Literal['decision', 'action', 'solution']] = None content: Optional[StepContent] = None category_id: Optional[UUID] = None tags: Optional[list[str]] = None visibility: Optional[Literal['private', 'team', 'public']] = None class StepLibraryResponse(StepLibraryBase): id: UUID created_by: UUID account_id: Optional[UUID] = None usage_count: int rating_average: Decimal rating_count: int helpful_yes: int helpful_no: int is_featured: bool is_verified: bool is_active: bool created_at: datetime updated_at: datetime # Computed fields (populated by API) category_name: Optional[str] = None author_name: Optional[str] = None is_flow_synced: bool = False source_tree_name: Optional[str] = None class Config: from_attributes = True class StepLibraryListResponse(BaseModel): id: UUID title: str step_type: str visibility: str category_id: Optional[UUID] = None category_name: Optional[str] = None tags: list[str] usage_count: int rating_average: Decimal rating_count: int is_featured: bool created_by: UUID author_name: Optional[str] = None created_at: datetime is_flow_synced: bool = False source_tree_name: Optional[str] = None class Config: from_attributes = True # Rating schemas class StepRatingBase(BaseModel): rating: int = Field(..., ge=1, le=5) was_helpful: Optional[bool] = None review_text: Optional[str] = Field(None, max_length=500) class StepRatingCreate(StepRatingBase): session_id: Optional[UUID] = None # For verified use tracking class StepRatingUpdate(BaseModel): rating: Optional[int] = Field(None, ge=1, le=5) was_helpful: Optional[bool] = None review_text: Optional[str] = Field(None, max_length=500) class StepRatingResponse(StepRatingBase): id: UUID step_id: UUID user_id: UUID is_verified_use: bool session_id: Optional[UUID] = None is_visible: bool created_at: datetime updated_at: datetime # Computed user_name: Optional[str] = None class Config: from_attributes = True # Search and filter schemas class StepSearchParams(BaseModel): q: Optional[str] = None # Full-text search query category_id: Optional[UUID] = None tags: Optional[list[str]] = None min_rating: Optional[float] = Field(None, ge=0, le=5) step_type: Optional[Literal['decision', 'action', 'solution']] = None visibility: Optional[Literal['private', 'team', 'public']] = None sort_by: Literal['recent', 'popular', 'highest_rated', 'most_used'] = 'recent' limit: int = Field(20, ge=1, le=100) offset: int = Field(0, ge=0) class PopularTagResponse(BaseModel): tag: str count: int