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:
119
frontend/src/components/flowpilot/FlowPilotIntake.tsx
Normal file
119
frontend/src/components/flowpilot/FlowPilotIntake.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import { useState } from 'react'
|
||||
import { Sparkles, FileText, Terminal } from 'lucide-react'
|
||||
import type { AISessionCreateRequest } from '@/types/ai-session'
|
||||
|
||||
interface FlowPilotIntakeProps {
|
||||
onSubmit: (request: AISessionCreateRequest) => void
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export function FlowPilotIntake({ onSubmit, isLoading }: FlowPilotIntakeProps) {
|
||||
const [text, setText] = useState('')
|
||||
const [showLogs, setShowLogs] = useState(false)
|
||||
const [logContent, setLogContent] = useState('')
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!text.trim() && !logContent.trim()) return
|
||||
|
||||
const intake_content: Record<string, unknown> = {}
|
||||
if (text.trim()) intake_content.text = text.trim()
|
||||
if (logContent.trim()) intake_content.log_content = logContent.trim()
|
||||
|
||||
const intake_type = logContent.trim()
|
||||
? text.trim() ? 'combined' : 'log_paste'
|
||||
: 'free_text'
|
||||
|
||||
onSubmit({ intake_type, intake_content })
|
||||
}
|
||||
|
||||
const hasContent = text.trim() || logContent.trim()
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[50vh]">
|
||||
<div className="text-center">
|
||||
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10">
|
||||
<Sparkles size={24} className="text-primary animate-pulse" />
|
||||
</div>
|
||||
<p className="text-sm font-medium text-foreground">Analyzing your issue...</p>
|
||||
<p className="mt-1 text-xs text-muted-foreground">FlowPilot is classifying the problem and searching for relevant flows</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-start justify-center pt-[10vh]">
|
||||
<div className="w-full max-w-2xl">
|
||||
<div className="text-center mb-6">
|
||||
<h1 className="font-heading text-2xl font-bold tracking-tight text-foreground">
|
||||
What are you troubleshooting?
|
||||
</h1>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
Describe the issue, paste an error message, or paste log output
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="glass-card-static p-5 space-y-4">
|
||||
{/* Main text area */}
|
||||
<textarea
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
placeholder="e.g. User can't access shared drive after password reset, getting 'Access Denied' in Event Viewer..."
|
||||
className="w-full rounded-lg border border-border bg-card px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-none resize-none"
|
||||
rows={5}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
{/* Input type toggles */}
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setShowLogs(!showLogs)}
|
||||
className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||
showLogs
|
||||
? 'bg-primary/10 text-primary border border-primary/20'
|
||||
: 'bg-card/50 text-muted-foreground border border-border hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
<Terminal size={12} />
|
||||
Paste Logs
|
||||
</button>
|
||||
<button
|
||||
disabled
|
||||
className="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium bg-card/50 text-[#5a6170] border border-border opacity-50 cursor-not-allowed"
|
||||
title="Coming in Phase 2"
|
||||
>
|
||||
<FileText size={12} />
|
||||
Pull from Ticket
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Log paste area */}
|
||||
{showLogs && (
|
||||
<textarea
|
||||
value={logContent}
|
||||
onChange={(e) => setLogContent(e.target.value)}
|
||||
placeholder="Paste log output, error messages, or Event Viewer entries here..."
|
||||
className="w-full rounded-lg border border-border bg-card px-4 py-3 font-mono text-xs text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-none resize-none"
|
||||
rows={6}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Submit */}
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-[0.6875rem] text-[#5a6170]">
|
||||
FlowPilot will analyze your input and guide you through diagnosis
|
||||
</p>
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={!hasContent}
|
||||
className="rounded-lg bg-gradient-brand px-5 py-2.5 text-sm font-semibold text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] disabled:opacity-40 disabled:shadow-none transition-all"
|
||||
>
|
||||
Start Session
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user