Files
resolutionflow/backend/app/schemas/ai_builder.py
chihlasm 37e1202f46 fix: code review fixes — date calc, input validation, rate limits, shared components
- Fix monthly_reset_at crash when billing anchor day exceeds next month's length
- Add environment_tags sanitization (max 20 tags, 100 chars each) to prevent prompt injection
- Add @limiter.limit("10/minute") rate limiting to all AI endpoints
- Use getTreeNavigatePath() routing helper instead of hardcoded paths
- Extract shared CreateFlowDropdown component from QuickStartPage and TreeLibraryPage
- Clear useCachedQuota on logout to prevent stale data across user sessions
- Add useRef guard to scaffold useEffect to prevent potential double-fire
- Use node.id as React key instead of array index in BranchDetailView
- Remove redundant dead logic in ai_tree_validator

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 01:32:38 -05:00

127 lines
3.0 KiB
Python

"""Pydantic schemas for the AI Flow Builder wizard."""
from typing import Any, Literal, Optional
from uuid import UUID
from pydantic import BaseModel, Field, field_validator
# ── Requests ──
class AIStartRequest(BaseModel):
"""Stage 1: Foundation — engineer provides flow metadata."""
flow_type: Literal["troubleshooting", "procedural"] = Field(
..., description="Type of flow to generate"
)
category_id: Optional[UUID] = None
name: str = Field(..., min_length=1, max_length=255)
description: str = Field("", max_length=2000)
environment_tags: list[str] = Field(default_factory=list, max_length=20)
@field_validator("environment_tags")
@classmethod
def validate_tags(cls, v: list[str]) -> list[str]:
for tag in v:
if len(tag) > 100:
raise ValueError("Each environment tag must be 100 characters or fewer")
if not tag.strip():
raise ValueError("Environment tags must not be empty")
return v
class AIScaffoldRequest(BaseModel):
"""Stage 2: Request AI-generated branch suggestions."""
conversation_id: UUID
class AIBranchDetailRequest(BaseModel):
"""Stage 3: Request AI-generated detail for one branch."""
conversation_id: UUID
branch_name: str = Field(..., min_length=1, max_length=255)
class AIBranchUpdate(BaseModel):
"""A branch with optional user edits for assembly."""
name: str
description: str = ""
steps: Optional[dict[str, Any]] = None
class AIAssembleRequest(BaseModel):
"""Stage 4: Assemble selected branches into a complete tree."""
conversation_id: UUID
selected_branches: list[AIBranchUpdate] = Field(..., min_length=2)
# ── Responses ──
class AIStartResponse(BaseModel):
"""Response after creating a conversation."""
conversation_id: UUID
status: str
class AIBranchSuggestion(BaseModel):
"""A single branch suggestion from the AI."""
name: str
description: str
class AIScaffoldResponse(BaseModel):
"""Response with AI-suggested branches."""
conversation_id: UUID
branches: list[AIBranchSuggestion]
status: str
class AIBranchDetailResponse(BaseModel):
"""Response with AI-generated detail for one branch."""
conversation_id: UUID
branch_name: str
steps: dict[str, Any]
status: str
class AITreeSummary(BaseModel):
"""Summary statistics for an assembled tree."""
node_count: int
decision_count: int
action_count: int
solution_count: int
depth: int
class AIAssembleResponse(BaseModel):
"""Response with the fully assembled tree."""
tree_structure: dict[str, Any]
suggested_name: str
suggested_description: str
summary: AITreeSummary
status: str
class AIQuotaStatusResponse(BaseModel):
"""Current user's AI quota status."""
plan: str
monthly_used: int
monthly_limit: Optional[int]
monthly_reset_at: str
daily_used: int
daily_limit: Optional[int]
daily_reset_at: str
allowed: bool
ai_enabled: bool