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>
63 lines
2.6 KiB
TypeScript
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>
|
|
)
|
|
}
|