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,120 @@
import { FileText, Clock, CheckCircle2, ArrowUpRight, Star } from 'lucide-react'
import type { SessionDocumentation } from '@/types/ai-session'
interface SessionDocViewProps {
documentation: SessionDocumentation
onRate?: (rating: number) => void
currentRating?: number | null
}
export function SessionDocView({ documentation, onRate, currentRating }: SessionDocViewProps) {
return (
<div className="space-y-5">
{/* Header */}
<div className="glass-card-static p-5">
<div className="flex items-start gap-3">
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
<FileText size={16} />
</span>
<div className="flex-1">
<h3 className="font-heading text-lg font-semibold text-foreground">Session Documentation</h3>
<p className="mt-1 text-sm text-muted-foreground">{documentation.problem_summary}</p>
<div className="mt-2 flex items-center gap-3 flex-wrap">
{documentation.problem_domain && (
<span className="font-label rounded-md bg-primary/10 px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-primary">
{documentation.problem_domain}
</span>
)}
{documentation.duration_display && (
<span className="flex items-center gap-1 text-xs text-muted-foreground">
<Clock size={12} />
{documentation.duration_display}
</span>
)}
<span className="text-xs text-muted-foreground">
{documentation.total_steps} steps
</span>
</div>
</div>
</div>
</div>
{/* Outcome */}
{(documentation.resolution_summary || documentation.escalation_reason) && (
<div className={`glass-card-static p-4 border-l-2 ${documentation.resolution_summary ? 'border-l-emerald-500' : 'border-l-amber-500'}`}>
<div className="flex items-center gap-2 mb-1">
{documentation.resolution_summary ? (
<CheckCircle2 size={14} className="text-emerald-400" />
) : (
<ArrowUpRight size={14} className="text-amber-400" />
)}
<span className="font-label text-[0.625rem] uppercase tracking-wider text-muted-foreground">
{documentation.resolution_summary ? 'Resolved' : 'Escalated'}
</span>
</div>
<p className="text-sm text-foreground">
{documentation.resolution_summary || documentation.escalation_reason}
</p>
</div>
)}
{/* Intake summary */}
<div className="glass-card-static p-4">
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-muted-foreground mb-2">
Original intake
</h4>
<p className="text-sm text-foreground whitespace-pre-wrap">{documentation.intake_summary}</p>
</div>
{/* Diagnostic steps */}
<div className="space-y-2">
<h4 className="font-label text-[0.625rem] uppercase tracking-wider text-muted-foreground px-1">
Diagnostic trail
</h4>
{documentation.diagnostic_steps.map((step) => (
<div key={step.step_number} className="glass-card-static p-4">
<div className="flex items-start gap-3">
<span className="font-label flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-card text-[0.625rem] text-muted-foreground border border-border">
{step.step_number}
</span>
<div className="flex-1">
<p className="text-sm text-foreground">{step.description}</p>
{step.engineer_response && (
<p className="mt-1 text-xs text-primary"> {step.engineer_response}</p>
)}
{step.outcome && (
<p className="mt-1 text-xs text-muted-foreground">Outcome: {step.outcome}</p>
)}
</div>
</div>
</div>
))}
</div>
{/* Rating */}
{onRate && (
<div className="glass-card-static p-4 text-center">
<p className="text-sm text-muted-foreground mb-2">How helpful was this session?</p>
<div className="flex items-center justify-center gap-1">
{[1, 2, 3, 4, 5].map((star) => (
<button
key={star}
onClick={() => onRate(star)}
className="p-1 transition-colors"
>
<Star
size={20}
className={
(currentRating ?? 0) >= star
? 'fill-amber-400 text-amber-400'
: 'text-muted-foreground hover:text-amber-400'
}
/>
</button>
))}
</div>
</div>
)}
</div>
)
}