403Webshell
Server IP : 3.147.158.171  /  Your IP : 216.73.216.88
Web Server : Apache/2.4.67 (Amazon Linux) OpenSSL/3.5.5
System : Linux ip-172-31-2-178.us-east-2.compute.internal 6.1.172-216.329.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Wed May 20 06:31:34 UTC 2026 x86_64
User : ec2-user ( 1000)
PHP Version : 8.4.21
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /tsai/www/html/api.turmansolutions.ai/app/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /tsai/www/html/api.turmansolutions.ai/app/appTypes.py
from pydantic import BaseModel, ConfigDict, Field, AliasChoices
from enum import Enum
from typing import Optional, List, Any


class ContentSchedule(BaseModel):
    """Calendar-based scheduling for articles and comments."""
    daysOfWeek: Optional[List[int]] = None  # 0=Sunday, 1=Monday, etc.
    daysOfMonth: Optional[List[int]] = None  # 1-31
    preferredHours: Optional[List[int]] = None  # 0-23


class ArticleResponse(BaseModel):
    title: str
    body: str
    category: str
    tags: List[str]
    featured_image_description: Optional[str] = None
    secondary_image_description: Optional[str] = None
    featured_image_caption: Optional[str] = Field(
        default=None,
        description="A short, single-sentence caption (max ~15 words) shown under the featured image. Reader-facing prose, not an image-generation prompt. No surrounding quotes."
    )
    secondary_image_caption: Optional[str] = Field(
        default=None,
        description="A short, single-sentence caption (max ~15 words) shown under the secondary image. Reader-facing prose, not an image-generation prompt. No surrounding quotes."
    )
    series_summary: Optional[str] = None  # AI summary for series continuity


class CorrectionResponse(BaseModel):
    edits_needed: bool
    revised_body: Optional[str] = None  # full corrected HTML article body; set only when edits_needed
    comment: str                        # markdown documenting the corrections (or why none were needed)


class RevisionResponse(BaseModel):
    revised_body: str  # full revised HTML article body
    summary: str       # one or two sentences describing what changed


class ArticleProgressEvent(BaseModel):
    status: str
    progress: int
    message: str
    article: Optional[ArticleResponse] = None
    article_id: Optional[int] = None
    error: Optional[str] = None


class ArticleCreationResult(BaseModel):
    article: ArticleResponse
    post_id: int
    actual_model: "LLModel"
    permalink: Optional[str] = None
    featured_image_url: Optional[str] = None
    secondary_image_url: Optional[str] = None
    search_results: Optional[List[Any]] = None
    fact_check_posted: bool = False
    corrections_posted: bool = False
    series_term_id: Optional[int] = None
    series_part_number: Optional[int] = None


class PostingRecord(BaseModel):
    """A single record of a cron posting attempt (article or comment).

    Persisted append-only to posting_history_{env}.jsonl and read back by the
    schedule calendar to render concrete "what was posted" events with links.
    """
    ts: str                       # ISO-8601 local timestamp of the attempt
    date: str                     # YYYY-MM-DD (for cheap day-window filtering)
    site: str                     # site slug
    kind: str                     # "article" | "comment"
    character: str                # character display name
    status: str                   # "success" | "failed"
    character_slug: Optional[str] = None
    storage_id: Optional[str] = None
    post_id: Optional[int] = None
    url: Optional[str] = None
    title: Optional[str] = None
    excerpt: Optional[str] = None
    model: Optional[str] = None
    error: Optional[str] = None


class UsageRecord(BaseModel):
    """A single AI-provider usage event, keyed by site slug.

    Persisted append-only to usage_{env}.jsonl (see services/usage_history.py)
    and aggregated by the /usage endpoint into per-model / per-provider totals.
    Token counts apply to OpenAI/Claude; tavily_credits applies to Tavily search.
    """
    ts: str                       # ISO-8601 local timestamp
    date: str                     # YYYY-MM-DD (for cheap day-window filtering)
    site: str                     # site slug
    provider: str                 # "OpenAI" | "Claude" | "Tavily"
    model: str                    # model name (Tavily: "search"; images: "gpt-image-2")
    operation: str                # "article" | "comment" | "fact_check" | "corrections" | "research" | "chat" | "image_studio" | "article_image" | ...
    input_tokens: int = 0
    output_tokens: int = 0
    tavily_credits: int = 0
    # Image generation is priced per image (by size/quality), not by tokens.
    images: int = 0
    image_size: Optional[str] = None      # e.g. "1536x1024" (resolved from 'auto')
    image_quality: Optional[str] = None   # "low" | "medium" | "high"


class ModelUsage(BaseModel):
    """Per-(provider, model) usage roll-up with estimated cost, camelCase for the UI."""
    model_config = ConfigDict(populate_by_name=True)
    provider: str
    model: str
    input_tokens: int = Field(0, serialization_alias="inputTokens")
    output_tokens: int = Field(0, serialization_alias="outputTokens")
    tavily_credits: int = Field(0, serialization_alias="tavilyCredits")
    images: int = Field(0, serialization_alias="imagesCount")
    estimated_cost: float = Field(0.0, serialization_alias="estimatedCost")
    price_known: bool = Field(True, serialization_alias="priceKnown")


class ProviderUsage(BaseModel):
    """All models for one provider plus the provider cost subtotal."""
    model_config = ConfigDict(populate_by_name=True)
    provider: str
    models: List[ModelUsage]
    subtotal_cost: float = Field(0.0, serialization_alias="subtotalCost")


class UsageSummaryResponse(BaseModel):
    """Aggregated usage for a single site over a trailing window."""
    model_config = ConfigDict(populate_by_name=True)
    slug: str
    since_days: int = Field(30, serialization_alias="sinceDays")
    providers: List[ProviderUsage]
    total_cost: float = Field(0.0, serialization_alias="totalCost")
    currency: str = "USD"
    generated_at: str = Field('', serialization_alias="generatedAt")


class DailyUsage(BaseModel):
    """Estimated spend for a single calendar day (for the spend-over-time view)."""
    model_config = ConfigDict(populate_by_name=True)
    date: str                     # YYYY-MM-DD
    cost: float = 0.0


class OperationUsage(BaseModel):
    """Estimated spend rolled up by operation type (article, comment, image, ...)."""
    model_config = ConfigDict(populate_by_name=True)
    operation: str
    cost: float = 0.0
    input_tokens: int = Field(0, serialization_alias="inputTokens")
    output_tokens: int = Field(0, serialization_alias="outputTokens")
    images: int = Field(0, serialization_alias="imagesCount")
    tavily_credits: int = Field(0, serialization_alias="tavilyCredits")


class UsageDetailResponse(BaseModel):
    """Detailed usage for a single site: per-provider/model plus daily and per-operation
    breakdowns. Powers the Creator app's detailed Usage & Cost page."""
    model_config = ConfigDict(populate_by_name=True)
    slug: str
    since_days: int = Field(30, serialization_alias="sinceDays")
    providers: List[ProviderUsage]
    daily: List[DailyUsage]
    operations: List[OperationUsage]
    total_cost: float = Field(0.0, serialization_alias="totalCost")
    currency: str = "USD"
    generated_at: str = Field('', serialization_alias="generatedAt")


class CharacterHistoryEvent(BaseModel):
    timestamp: str
    description: str


class Character(BaseModel):
    slug: Optional[str] = None
    name: str
    email: str
    description: str

    enabled: bool

    date: str
    siteId: str = ''

    storageId: Optional[str] = None
    wp_user: Optional["WpUser"] = None
    articles: Optional[List[Any]] = None
    model: Optional["LLModel"] = None
    researchCurrentEvents: Optional[bool] = False
    createFeaturedImage: Optional[bool] = False
    createSecondaryImage: Optional[bool] = False
    enableFactChecking: Optional[bool] = False
    enableCorrections: Optional[bool] = False
    includePreviousArticles: Optional[bool] = True
    searchQuery: Optional[str] = None
    searchDomainsIncluded: Optional[List[str]] = None
    searchDomainsExcluded: Optional[List[str]] = None
    searchTopicType: Optional[str] = 'general'
    searchTimeRange: Optional[str] = None
    maxSearchResults: Optional[int] = 5
    searchDepth: Optional[str] = 'basic'
    chunksPerSource: Optional[int] = 3
    # Calendar-based scheduling
    articleSchedule: Optional[ContentSchedule] = None
    commentSchedule: Optional[ContentSchedule] = None
    shared: Optional[bool] = True
    systemWide: Optional[bool] = False
    history: Optional[List["CharacterHistoryEvent"]] = None


class Constants(Enum):
    NEW_LINE = "\n"
    CB_NEW_LINE = "__chatbot_br__"
    CB_END_STREAM = "__chatbot_end__"


class CreateArticleRequest(BaseModel):
    character: Character
    character_description_override: Optional[str] = ''
    research_current_events: Optional[bool] = False
    enable_fact_checking: Optional[bool] = True
    enable_corrections: Optional[bool] = False
    generate_featured_image: Optional[bool] = True
    generate_secondary_image: Optional[bool] = True
    include_previous_articles: Optional[bool] = True
    series_name: Optional[str] = None        # Series name (creates new if doesn't exist)
    continue_series_id: Optional[int] = None  # WP taxonomy term ID to continue
    seed_image_url: Optional[str] = None      # Reference image URL for the featured image (single article or series Part 1 only)
    seed_image_instructions: Optional[str] = None  # Free-text guidance on how the seed image should be used


class CreateCommentRequest(BaseModel):
    url: str
    character: Character
    user_instructions: Optional[str] = ''


class CreateCharacterRequest(BaseModel):
    character: Character


class DrupalLogin(BaseModel):
    username: str
    password: str


class CreateDrupalUserRequest(BaseModel):
    """Request model for creating a Drupal user."""
    username: str
    email_domain: str = "turmansolutions.ai"


class CreateDrupalUserResponse(BaseModel):
    """Response model for Drupal user creation."""
    success: bool
    uuid: str
    username: str
    email: str
    password: str
    message: str


# class DeleteCharacterRequest(BaseModel):
#     id: str

class LLMProvider(str, Enum):
    OPEN_AI = 'OpenAI'
    CLAUDE = 'Claude'


class LLModel(BaseModel):
    # Accept both snake_case (stored character bodies) and camelCase (frontend
    # `maxTokens`) on input, and serialize as camelCase so the Angular frontend
    # actually receives the value. The Python attribute stays `max_tokens`.
    model_config = ConfigDict(populate_by_name=True)
    provider: LLMProvider
    name: str
    temperature: Optional[float] = 0.7
    max_tokens: Optional[int] = Field(
        default=None,
        validation_alias=AliasChoices("max_tokens", "maxTokens"),
        serialization_alias="maxTokens",
    )
    stream: Optional[bool] = True


class LoginRequest(BaseModel):
    username: str
    password: str
    site: str


class ServiceLoginRequest(BaseModel):
    site: str


class LoginResponse(BaseModel):
    wp_token: Any


class LogoutRequest(BaseModel):
    # username: str
    token: str


class LogoutResponse(BaseModel):
    detail: str


class RegisterRequest(BaseModel):
    username: str
    email: str
    password: str
    site: str
    first_name: Optional[str] = None
    last_name: Optional[str] = None
    roles: Optional[List[str]] = None
    character_uuid: Optional[str] = None


class RegisterResponse(BaseModel):
    success: bool
    user_id: Optional[int] = None
    username: Optional[str] = None
    email: Optional[str] = None
    message: str


class ImageCompressionRequest(BaseModel):
    url: str
    compression_level: int


class Message(BaseModel):
    userId: int
    body: str
    instructions: Optional[str] = 'You are a helpful assistant.'
    conversation: Optional[List['Message']] = None
    model: Optional['LLModel'] = None
    use_structured_output: Optional[bool] = False
    response_schema: Optional[dict] = None
    images: Optional[List[str]] = None
    site: Optional['SiteConfig'] = None
    # WordPress JWT of the logged-in user. Only the Article Studio requires it:
    # publishing writes to a live public site, so Origin-based site scoping alone
    # is not enough. Validated against the site's WP install before the agent runs.
    wp_token: Optional[str] = None


class PatchCharacterRequest(BaseModel):
    character: Character


class CharacterResponse(BaseModel):
    data: Character


class CharactersListResponse(BaseModel):
    data: List[Character]
    count: int
    # Slug of the requesting site, so clients (e.g. the WP schedule page) can
    # build the slug-scoped calendar subscribe URL. Optional for back-compat.
    site_slug: Optional[str] = None


class Widget(BaseModel):
    storageId: Optional[str] = None
    siteId: str = ''
    id: str = ''
    name: str = ''
    category: str = ''
    date: str = ''
    enabled: bool = False
    hidden: bool = False
    config: dict = {}
    shared: Optional[bool] = True
    systemWide: Optional[bool] = False


class CreateWidgetRequest(BaseModel):
    widget: Widget


class PatchWidgetRequest(BaseModel):
    widget: Widget


class WidgetResponse(BaseModel):
    data: dict


class WidgetListResponse(BaseModel):
    data: List[dict]
    count: int


class PathSegment(Enum):
    TMP_DIR = '/fastapi/tmp/'
    WP_FILES = '/wp-content/uploads'
    DRUPAL_WEB = '/drupal/web'
    DRUPAL_FILES = '/sites/default/files'
    CHATBOT_CREATE = '/create'
    CHATBOT_EDIT = '/edit'
    CHATBOT_UPLOAD = '/upload'
    CHATBOT_VARIANT = '/variant'


class RunAgentsRequest(BaseModel):
    slug: str

class SiteConfig(BaseModel):
    cb_host: str
    api_host: str
    wp_host: str
    wp_dir: str
    drupal_host: str
    drupal_api: str
    slug: str
    drupal_uid: Optional[str] = None
    drupal_target_id: Optional[int] = None
    drupal_node_id: Optional[str] = None

class IndexedSite(BaseModel):
    """Site data gathered by the site indexing service"""
    slug: str
    wp_host: str = Field(serialization_alias="wpHost")
    name: str
    tagline: str = ""
    link: str
    chat_url: str = Field(serialization_alias="chatUrl")
    logo_full: str = Field("", serialization_alias="logoFull")
    logo_72: str = Field("", serialization_alias="logo72")
    logo_150: str = Field("", serialization_alias="logo150")
    logo_300: str = Field("", serialization_alias="logo300")
    indexed_at: str = Field(serialization_alias="indexedAt")
    promoted: bool = True


class LeaderboardEntry(BaseModel):
    """A single aggregated quiz leaderboard row from a WordPress site's live
    /wp-json/tsai/v1/quiz/leaderboard endpoint."""
    # The WP plugin deliberately withholds identity_key (internal grouping key),
    # so default it — the UI keys on display_name/rank, never identity_key.
    identity_key: str = Field("", serialization_alias="identityKey")
    display_name: str = Field("", serialization_alias="displayName")
    total_points: int = Field(0, serialization_alias="totalPoints")
    quizzes_completed: int = Field(0, serialization_alias="quizzesCompleted")


class SiteRequest(BaseModel):
    data: str


class UploadRequest(BaseModel):
    imgPath: str


class WpUser(BaseModel):
    id: str
    name: str


class StructuredResponse(BaseModel):
    content: str
    structured_data: Optional[dict] = None


class ModelInfo(BaseModel):
    provider: str
    name: str
    description: Optional[str] = None
    max_tokens: Optional[int] = None
    supports_streaming: bool = True
    supports_structured_output: bool = False
    supports_vision: bool = False


class ResponsesImageRequest(BaseModel):
    """Request model for OpenAI Responses API image generation"""
    prompt: str
    user: str
    model: str = "gpt-4.1-mini"
    stream: bool = True


class GptImageSize(str, Enum):
    _1024x1024 = '1024x1024'
    _1536x1024 = '1536x1024'
    _1024x1536 = '1024x1536'
    auto = 'auto'


class GptImageQuality(str, Enum):
    low = 'low'
    medium = 'medium'
    high = 'high'


class GptImageFormat(str, Enum):
    png = 'png'
    jpeg = 'jpeg'
    webp = 'webp'


class GptImageInputFidelity(str, Enum):
    low = 'low'
    high = 'high'


class GptImage2Background(str, Enum):
    transparent = 'transparent'
    opaque = 'opaque'
    auto = 'auto'


class GptImage2Request(BaseModel):
    """Request model for gpt-image-2 generation via the Responses API image_generation tool.

    gpt-image-2 is reachable only via responses.create with
    tools=[{type:"image_generation", ...}], driven by a text model
    (driver_model). The underlying image model is selected by OpenAI.
    """
    prompt: str
    user: str
    driver_model: str = "gpt-5"
    stream: bool = True
    size: GptImageSize = GptImageSize.auto
    quality: GptImageQuality = GptImageQuality.medium
    output_format: Optional[GptImageFormat] = None
    background: Optional[GptImage2Background] = None
    input_fidelity: Optional[GptImageInputFidelity] = None
    partial_images: Optional[int] = None
    input_images: Optional[List[str]] = None
    previous_response_id: Optional[str] = None


class BrowseImageItem(BaseModel):
    """Single image item returned by the browse endpoint"""
    filename: str
    type: str
    thumb_url: str
    full_url: str
    modified: float


class BrowseImagesResponse(BaseModel):
    """Response from the image browse endpoint"""
    images: List[BrowseImageItem]
    next_cursor: Optional[str] = None
    total: int

Youez - 2016 - github.com/yon3zu
LinuXploit