feat(knowledge-flywheel): add Phase 3 Knowledge Flywheel — AI analysis, review queue, analytics
Phase 3 implementation: - AI session analysis service that generates flow proposals from resolved sessions - APScheduler job for batch processing pending analyses (max_instances=1) - Knowledge gap detection (weak options, high escalation signals) - Flow proposals CRUD with team admin review workflow (approve/edit/dismiss/reject) - FlowPilot analytics dashboard with confidence tiers, PSA metrics, knowledge gaps - In-session script generator component - Review queue page with filtering and proposal detail panel Bug fixes from review (12 total): - Fix "Edit & Publish" navigating to non-existent /editor/new route - Hide Approve button for enhancement proposals (require Edit & Publish) - Add max_instances=1 to scheduler to prevent TOCTOU race - Fix eventual_success case() double-counting failed retries - Add tree_structure validation before creating tree from proposal - Simplify script generator rendering condition - Add severity style fallback, toFixed on rates, Link instead of <a href> - Add toast.warning on dismiss failure, fix dedup for domain-less sessions - Cast Decimal to int in knowledge gap evidence dicts Also updates CLAUDE.md with lessons 67-71 and Phase 3 project structure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
62
frontend/src/components/flowpilot/ProposalCard.tsx
Normal file
62
frontend/src/components/flowpilot/ProposalCard.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { GitBranch, ArrowUpRight, Sparkles, Clock, Hash } from 'lucide-react'
|
||||
import type { FlowProposalSummary } from '@/types/flow-proposal'
|
||||
|
||||
const TYPE_CONFIG = {
|
||||
new_flow: { label: 'New Flow', color: 'text-emerald-400 bg-emerald-400/10 border-emerald-400/20', icon: Sparkles },
|
||||
enhancement: { label: 'Enhancement', color: 'text-amber-400 bg-amber-400/10 border-amber-400/20', icon: ArrowUpRight },
|
||||
branch_addition: { label: 'Branch', color: 'text-blue-400 bg-blue-400/10 border-blue-400/20', icon: GitBranch },
|
||||
auto_reinforced: { label: 'Reinforced', color: 'text-muted-foreground bg-card border-border', icon: Sparkles },
|
||||
} as const
|
||||
|
||||
interface ProposalCardProps {
|
||||
proposal: FlowProposalSummary
|
||||
isSelected: boolean
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
export function ProposalCard({ proposal, isSelected, onClick }: ProposalCardProps) {
|
||||
const typeConfig = TYPE_CONFIG[proposal.proposal_type] || TYPE_CONFIG.new_flow
|
||||
const TypeIcon = typeConfig.icon
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`w-full text-left rounded-xl border p-3 space-y-2 transition-all ${
|
||||
isSelected
|
||||
? 'border-primary/30 bg-primary/5'
|
||||
: 'border-[rgba(255,255,255,0.06)] bg-[rgba(24,26,31,0.55)] hover:border-[rgba(255,255,255,0.12)]'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p className="text-sm font-semibold text-foreground line-clamp-2">{proposal.title}</p>
|
||||
<span className={`shrink-0 flex items-center gap-1 rounded-md border px-1.5 py-0.5 font-label text-[0.5625rem] uppercase tracking-wider ${typeConfig.color}`}>
|
||||
<TypeIcon size={10} />
|
||||
{typeConfig.label}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{proposal.description && (
|
||||
<p className="text-xs text-muted-foreground line-clamp-2">{proposal.description}</p>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
|
||||
{proposal.problem_domain && (
|
||||
<span className="font-label rounded-md bg-primary/10 px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-primary">
|
||||
{proposal.problem_domain}
|
||||
</span>
|
||||
)}
|
||||
<span className="flex items-center gap-1">
|
||||
<Hash size={10} />
|
||||
{proposal.supporting_session_count} session{proposal.supporting_session_count !== 1 ? 's' : ''}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Clock size={10} />
|
||||
{new Date(proposal.created_at).toLocaleDateString()}
|
||||
</span>
|
||||
<span className="text-primary">
|
||||
{Math.round(proposal.confidence_score * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user