feat(ai-session): add FlowPilot AI-powered troubleshooting sessions
Implements Phase 1 of the FlowPilot-First pivot — the core AI session experience where engineers describe a problem and FlowPilot guides them through structured diagnosis with selectable options, free-text escape hatches, and auto-generated documentation on resolution. Backend: AISession + AISessionStep models, FlowPilot Engine (LLM orchestration with structured JSON output), Flow Matching Engine v1 (semantic + keyword + recency scoring), 8 API endpoints with auth, rate limiting, and AI quota enforcement. Frontend: Intake screen, conversational session view with sidebar, step cards with options/actions/resolution suggestions, resolve/escalate modals, documentation view with rating, session history integration, and /pilot route with sidebar navigation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
209
frontend/src/hooks/useFlowPilotSession.ts
Normal file
209
frontend/src/hooks/useFlowPilotSession.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import { useState, useCallback } from 'react'
|
||||
import { aiSessionsApi } from '@/api'
|
||||
import type {
|
||||
AISessionCreateRequest,
|
||||
AISessionCreateResponse,
|
||||
AISessionDetail,
|
||||
AISessionStepResponse,
|
||||
StepResponseRequest,
|
||||
StepResponseResponse,
|
||||
ResolveSessionRequest,
|
||||
EscalateSessionRequest,
|
||||
SessionDocumentation,
|
||||
} from '@/types/ai-session'
|
||||
import { toast } from '@/lib/toast'
|
||||
|
||||
export interface UseFlowPilotSession {
|
||||
// State
|
||||
session: AISessionDetail | null
|
||||
currentStep: AISessionStepResponse | null
|
||||
allSteps: AISessionStepResponse[]
|
||||
isLoading: boolean
|
||||
isProcessing: boolean
|
||||
error: string | null
|
||||
|
||||
// Actions
|
||||
startSession: (intake: AISessionCreateRequest) => Promise<void>
|
||||
respondToStep: (response: StepResponseRequest) => Promise<void>
|
||||
resolveSession: (data: ResolveSessionRequest) => Promise<SessionDocumentation>
|
||||
escalateSession: (data: EscalateSessionRequest) => Promise<SessionDocumentation>
|
||||
rateSession: (rating: number, feedback?: string) => Promise<void>
|
||||
loadSession: (sessionId: string) => Promise<void>
|
||||
|
||||
// Derived
|
||||
isActive: boolean
|
||||
canResolve: boolean
|
||||
canEscalate: boolean
|
||||
|
||||
// Post-close
|
||||
documentation: SessionDocumentation | null
|
||||
}
|
||||
|
||||
export function useFlowPilotSession(): UseFlowPilotSession {
|
||||
const [session, setSession] = useState<AISessionDetail | null>(null)
|
||||
const [currentStep, setCurrentStep] = useState<AISessionStepResponse | null>(null)
|
||||
const [allSteps, setAllSteps] = useState<AISessionStepResponse[]>([])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isProcessing, setIsProcessing] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [documentation, setDocumentation] = useState<SessionDocumentation | null>(null)
|
||||
|
||||
const startSession = useCallback(async (intake: AISessionCreateRequest) => {
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const result: AISessionCreateResponse = await aiSessionsApi.createSession(intake)
|
||||
const firstStep = result.first_step
|
||||
|
||||
setSession({
|
||||
id: result.session_id,
|
||||
status: result.status,
|
||||
intake_type: intake.intake_type,
|
||||
intake_content: intake.intake_content,
|
||||
problem_summary: result.problem_summary,
|
||||
problem_domain: result.problem_domain,
|
||||
confidence_tier: result.confidence_tier,
|
||||
step_count: 1,
|
||||
session_rating: null,
|
||||
created_at: new Date().toISOString(),
|
||||
resolved_at: null,
|
||||
matched_flow_id: result.matched_flow_id,
|
||||
match_score: result.match_score,
|
||||
resolution_summary: null,
|
||||
resolution_action: null,
|
||||
escalation_reason: null,
|
||||
session_feedback: null,
|
||||
steps: [firstStep],
|
||||
})
|
||||
setAllSteps([firstStep])
|
||||
setCurrentStep(firstStep)
|
||||
} catch (e: unknown) {
|
||||
const message = e instanceof Error ? e.message : 'Failed to start session'
|
||||
setError(message)
|
||||
toast.error(message)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const respondToStep = useCallback(async (response: StepResponseRequest) => {
|
||||
if (!session) return
|
||||
setIsProcessing(true)
|
||||
setError(null)
|
||||
try {
|
||||
const result: StepResponseResponse = await aiSessionsApi.respondToStep(session.id, response)
|
||||
|
||||
setSession(prev => prev ? {
|
||||
...prev,
|
||||
status: result.status,
|
||||
confidence_tier: result.confidence_tier,
|
||||
step_count: prev.step_count + 1,
|
||||
} : null)
|
||||
|
||||
if (result.next_step) {
|
||||
setAllSteps(prev => [...prev, result.next_step!])
|
||||
setCurrentStep(result.next_step)
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
const message = e instanceof Error ? e.message : 'Failed to process response'
|
||||
setError(message)
|
||||
toast.error(message)
|
||||
} finally {
|
||||
setIsProcessing(false)
|
||||
}
|
||||
}, [session])
|
||||
|
||||
const resolveSession = useCallback(async (data: ResolveSessionRequest): Promise<SessionDocumentation> => {
|
||||
if (!session) throw new Error('No active session')
|
||||
setIsProcessing(true)
|
||||
try {
|
||||
const result = await aiSessionsApi.resolveSession(session.id, data)
|
||||
setSession(prev => prev ? { ...prev, status: 'resolved' } : null)
|
||||
setDocumentation(result.documentation)
|
||||
setCurrentStep(null)
|
||||
toast.success('Session resolved')
|
||||
return result.documentation
|
||||
} catch (e: unknown) {
|
||||
const message = e instanceof Error ? e.message : 'Failed to resolve session'
|
||||
toast.error(message)
|
||||
throw e
|
||||
} finally {
|
||||
setIsProcessing(false)
|
||||
}
|
||||
}, [session])
|
||||
|
||||
const escalateSession = useCallback(async (data: EscalateSessionRequest): Promise<SessionDocumentation> => {
|
||||
if (!session) throw new Error('No active session')
|
||||
setIsProcessing(true)
|
||||
try {
|
||||
const result = await aiSessionsApi.escalateSession(session.id, data)
|
||||
setSession(prev => prev ? { ...prev, status: 'escalated' } : null)
|
||||
setDocumentation(result.documentation)
|
||||
setCurrentStep(null)
|
||||
toast.success('Session escalated')
|
||||
return result.documentation
|
||||
} catch (e: unknown) {
|
||||
const message = e instanceof Error ? e.message : 'Failed to escalate session'
|
||||
toast.error(message)
|
||||
throw e
|
||||
} finally {
|
||||
setIsProcessing(false)
|
||||
}
|
||||
}, [session])
|
||||
|
||||
const rateSession = useCallback(async (rating: number, feedback?: string) => {
|
||||
if (!session) return
|
||||
try {
|
||||
await aiSessionsApi.rateSession(session.id, { rating, feedback })
|
||||
setSession(prev => prev ? { ...prev, session_rating: rating } : null)
|
||||
toast.success('Thanks for your feedback!')
|
||||
} catch {
|
||||
toast.error('Failed to submit rating')
|
||||
}
|
||||
}, [session])
|
||||
|
||||
const loadSession = useCallback(async (sessionId: string) => {
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const detail = await aiSessionsApi.getSession(sessionId)
|
||||
setSession(detail)
|
||||
setAllSteps(detail.steps)
|
||||
setCurrentStep(detail.status === 'active' ? detail.steps[detail.steps.length - 1] ?? null : null)
|
||||
|
||||
if (detail.status === 'resolved' || detail.status === 'escalated') {
|
||||
const doc = await aiSessionsApi.getDocumentation(sessionId)
|
||||
setDocumentation(doc)
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
const message = e instanceof Error ? e.message : 'Failed to load session'
|
||||
setError(message)
|
||||
toast.error(message)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const isActive = session?.status === 'active'
|
||||
const canResolve = isActive && allSteps.length >= 1
|
||||
const canEscalate = isActive && allSteps.length >= 1
|
||||
|
||||
return {
|
||||
session,
|
||||
currentStep,
|
||||
allSteps,
|
||||
isLoading,
|
||||
isProcessing,
|
||||
error,
|
||||
startSession,
|
||||
respondToStep,
|
||||
resolveSession,
|
||||
escalateSession,
|
||||
rateSession,
|
||||
loadSession,
|
||||
isActive,
|
||||
canResolve,
|
||||
canEscalate,
|
||||
documentation,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user