Files
resolutionflow/frontend/src/components/flowpilot/ProposalCard.tsx
chihlasm 9bad49d568 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>
2026-03-19 05:12:10 +00:00

63 lines
2.6 KiB
TypeScript

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>
)
}