feat: AI-assisted flow builder with 4-stage wizard

Implements the complete AI flow builder feature using a guided 4-stage
wizard (Foundation → Scaffold → Branch Detail → Review & Assemble).
AI assists at bounded points using Claude Haiku for cost-efficient
structured JSON generation (~$0.01-0.03/flow).

Backend: new models (ai_conversations, ai_usage), Alembic migration,
quota enforcement with billing anchor, Anthropic API integration with
prompt caching, tree validation, conversation CRUD with 24h TTL,
APScheduler cleanup job, 5 API endpoints, Pydantic schemas.

Frontend: TypeScript types, API client, Zustand store for wizard state,
7 components (modal, step indicator, foundation form, branch selector,
branch detail view, tree preview, quota display), MyTreesPage integration
with "Build with AI" button (hidden when AI not configured).

Tests: 14 validator unit tests + 11 endpoint integration tests with
mocked Anthropic (zero real API spend). All 25 tests passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-20 08:07:08 -05:00
parent aef40078d0
commit 44432413c2
35 changed files with 3662 additions and 5 deletions

View File

@@ -5,6 +5,11 @@ from .session import SessionCreate, SessionUpdate, SessionResponse, SessionExpor
from .category import CategoryCreate, CategoryUpdate, CategoryResponse, CategoryListResponse
from .tag import TagCreate, TagResponse, TagListResponse, TagAssignment
from .folder import FolderCreate, FolderUpdate, FolderResponse, FolderListResponse, FolderReorderRequest, FolderTreeRequest
from .ai_builder import (
AIStartRequest, AIScaffoldRequest, AIBranchDetailRequest, AIAssembleRequest,
AIStartResponse, AIScaffoldResponse, AIBranchDetailResponse, AIAssembleResponse,
AIQuotaStatusResponse,
)
__all__ = [
# User
@@ -21,4 +26,8 @@ __all__ = [
"TagCreate", "TagResponse", "TagListResponse", "TagAssignment",
# Folder
"FolderCreate", "FolderUpdate", "FolderResponse", "FolderListResponse", "FolderReorderRequest", "FolderTreeRequest",
# AI Builder
"AIStartRequest", "AIScaffoldRequest", "AIBranchDetailRequest", "AIAssembleRequest",
"AIStartResponse", "AIScaffoldResponse", "AIBranchDetailResponse", "AIAssembleResponse",
"AIQuotaStatusResponse",
]

View File

@@ -0,0 +1,116 @@
"""Pydantic schemas for the AI Flow Builder wizard."""
from typing import Any, Literal, Optional
from uuid import UUID
from pydantic import BaseModel, Field
# ── 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)
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