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

@@ -0,0 +1,61 @@
import { apiClient } from './client'
import type {
AIQuotaStatus,
AIStartResponse,
AIScaffoldResponse,
AIBranchDetailResponse,
AIAssembleResponse,
} from '@/types'
export const aiBuilderApi = {
getQuota: async (): Promise<AIQuotaStatus> => {
const { data } = await apiClient.get('/ai/quota')
return data
},
start: async (params: {
flow_type: 'troubleshooting' | 'procedural'
name: string
description: string
environment_tags?: string[]
category_id?: string
}): Promise<AIStartResponse> => {
const { data } = await apiClient.post('/ai/start', params)
return data
},
scaffold: async (conversationId: string): Promise<AIScaffoldResponse> => {
const { data } = await apiClient.post('/ai/scaffold', {
conversation_id: conversationId,
})
return data
},
branchDetail: async (
conversationId: string,
branchName: string
): Promise<AIBranchDetailResponse> => {
const { data } = await apiClient.post('/ai/branch-detail', {
conversation_id: conversationId,
branch_name: branchName,
})
return data
},
assemble: async (
conversationId: string,
selectedBranches: Array<{
name: string
description: string
steps?: Record<string, unknown>
}>
): Promise<AIAssembleResponse> => {
const { data } = await apiClient.post('/ai/assemble', {
conversation_id: conversationId,
selected_branches: selectedBranches,
})
return data
},
}
export default aiBuilderApi

View File

@@ -16,3 +16,4 @@ export { default as analyticsApi } from './analytics'
export { targetListsApi } from './targetLists'
export { maintenanceSchedulesApi, batchLaunchApi } from './maintenanceSchedules'
export { default as feedbackApi } from './feedback'
export { default as aiBuilderApi } from './aiBuilder'