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:
2026-03-18 14:27:36 +00:00
parent 44eb48e457
commit 5494816b06
29 changed files with 3647 additions and 5 deletions

View File

@@ -0,0 +1,83 @@
import { useEffect } from 'react'
import { useParams } from 'react-router-dom'
import { Sparkles } from 'lucide-react'
import { useFlowPilotSession } from '@/hooks/useFlowPilotSession'
import { FlowPilotIntake, FlowPilotSession } from '@/components/flowpilot'
export default function FlowPilotSessionPage() {
const { sessionId } = useParams<{ sessionId?: string }>()
const fp = useFlowPilotSession()
// Load existing session if ID in URL
useEffect(() => {
if (sessionId && !fp.session) {
fp.loadSession(sessionId)
}
}, [sessionId]) // eslint-disable-line react-hooks/exhaustive-deps
// Error state
if (fp.error && !fp.session) {
return (
<div className="flex items-center justify-center min-h-[50vh]">
<div className="glass-card-static p-6 text-center max-w-md">
<p className="text-sm text-rose-400">{fp.error}</p>
<button
onClick={() => window.location.reload()}
className="mt-3 text-xs text-muted-foreground hover:text-foreground transition-colors"
>
Try again
</button>
</div>
</div>
)
}
// Intake screen (no session yet)
if (!fp.session) {
return (
<div className="h-full p-6">
<FlowPilotIntake onSubmit={fp.startSession} isLoading={fp.isLoading} />
</div>
)
}
// Active/completed session
return (
<div className="flex h-full flex-col">
{/* Header */}
<div
className="flex items-center gap-3 border-b px-5 py-3 shrink-0"
style={{ borderColor: 'var(--glass-border)' }}
>
<span className="flex h-7 w-7 items-center justify-center rounded-lg bg-primary/10">
<Sparkles size={14} className="text-primary" />
</span>
<div className="flex-1 min-w-0">
<h1 className="font-heading text-sm font-semibold text-foreground truncate">
{fp.session.problem_summary || 'FlowPilot Session'}
</h1>
</div>
<span className="font-label rounded-md bg-card px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-muted-foreground border border-border">
{fp.session.status}
</span>
</div>
{/* Session content */}
<div className="flex-1 min-h-0">
<FlowPilotSession
session={fp.session}
allSteps={fp.allSteps}
currentStep={fp.currentStep}
isProcessing={fp.isProcessing}
canResolve={fp.canResolve}
canEscalate={fp.canEscalate}
documentation={fp.documentation}
onRespond={fp.respondToStep}
onResolve={fp.resolveSession}
onEscalate={fp.escalateSession}
onRate={fp.rateSession}
/>
</div>
</div>
)
}