All checks were successful
Mirror to GitHub / mirror (push) Successful in 3s
Collapses the pre-existing dual-surface setup (AssistantChatPage at /assistant, FlowPilotSessionPage at /pilot) into a single chat-primary surface per architectural claim #1 of FLOWPILOT-MIGRATION.md. Router changes (frontend/src/router.tsx): - /pilot and /pilot/:sessionId now render AssistantChatPage. - /assistant redirects permanently to /pilot via <Navigate replace>. - /assistant/:sessionId redirects to /pilot/:sessionId preserving the ID via an AssistantSessionRedirect helper that reads the param. - FlowPilotSessionPage is no longer imported or mounted. Per the beta-history-disposable decision, the file stays on disk for reference but is unreachable; delete once nothing else in the tree imports it. Dispatcher de-branching — previously these sites routed by session_type (chat -> /assistant, otherwise -> /pilot). All now unconditionally go to /pilot/:id since session_type is no longer used for frontend routing: - components/dashboard/ActiveFlowPilotSessions.tsx - components/dashboard/RecentFlowPilotSessions.tsx - components/flowpilot/AISessionListItem.tsx (keeps isChat for icon selection, but linkTo is unconditional) User-facing label + navigation updates: - components/layout/CommandPalette.tsx: "AI Assistant" palette entry becomes "FlowPilot" pointing to /pilot; the sparkles quick-action also routes to /pilot. - components/dashboard/StartSessionInput.tsx: both navigate() call sites now go to /pilot instead of /assistant. - lib/routePrefetch.ts: prefetch entry for AssistantChatPage keyed to /pilot (the real surface) rather than /assistant (now redirect-only). Preserved intentionally (not user-facing routes): - Backend /assistant/retention API path and the assistantChatApi module name — those are internal API and module identifiers, not SPA routes. - src/components/assistant/* and src/types/assistant-chat — TypeScript module paths, not routes. - Sidebar.tsx — no top-level AI entry existed to rename; /pilot is already in the History group's matchPaths. Whether FlowPilot deserves its own rail entry is a future UX decision, not Phase 1 scope. - FlowPilotAnalyticsPage at /analytics/flowpilot — analytics for the unified product, not guided-only, per the agreed Q16 interpretation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
86 lines
3.4 KiB
TypeScript
86 lines
3.4 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { Link, useNavigate } from 'react-router-dom'
|
|
import { CheckCircle, AlertTriangle, XCircle, ArrowRight, MessageCircle } from 'lucide-react'
|
|
import { aiSessionsApi } from '@/api/aiSessions'
|
|
import type { AISessionSummary } from '@/types/ai-session'
|
|
import { timeAgo } from '@/lib/timeAgo'
|
|
|
|
const STATUS_CONFIG: Record<string, { icon: typeof CheckCircle; color: string }> = {
|
|
resolved: { icon: CheckCircle, color: '#34d399' },
|
|
escalated: { icon: AlertTriangle, color: '#fbbf24' },
|
|
abandoned: { icon: XCircle, color: '#8891a0' },
|
|
}
|
|
|
|
export function RecentFlowPilotSessions({ hideHeader = false }: { hideHeader?: boolean }) {
|
|
const [sessions, setSessions] = useState<AISessionSummary[]>([])
|
|
const navigate = useNavigate()
|
|
|
|
useEffect(() => {
|
|
Promise.all([
|
|
aiSessionsApi.listSessions({ status: 'resolved', limit: 5 }).catch(() => []),
|
|
aiSessionsApi.listSessions({ status: 'escalated', limit: 3 }).catch(() => []),
|
|
]).then(([resolved, escalated]) => {
|
|
const all = [...resolved, ...escalated]
|
|
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
|
|
.slice(0, 5)
|
|
setSessions(all)
|
|
})
|
|
}, [])
|
|
|
|
if (sessions.length === 0) return null
|
|
|
|
return (
|
|
<div className="card-flat">
|
|
{!hideHeader && (
|
|
<div
|
|
className="flex items-center justify-between px-5 py-3"
|
|
style={{ borderBottom: '1px solid var(--color-border-default)' }}
|
|
>
|
|
<h3 className="font-heading text-sm font-bold text-foreground">Recent Sessions</h3>
|
|
<Link
|
|
to="/sessions"
|
|
className="flex items-center gap-1 text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors"
|
|
>
|
|
History <ArrowRight size={10} />
|
|
</Link>
|
|
</div>
|
|
)}
|
|
<div>
|
|
{sessions.map((session, i) => {
|
|
const config = STATUS_CONFIG[session.status] || STATUS_CONFIG.abandoned
|
|
const StatusIcon = config.icon
|
|
return (
|
|
<button
|
|
key={session.id}
|
|
onClick={() => navigate(`/pilot/${session.id}`)}
|
|
className="flex w-full items-center gap-3 px-5 py-3 text-left hover:bg-[rgba(255,255,255,0.02)] transition-colors"
|
|
style={{
|
|
borderBottom: i < sessions.length - 1 ? '1px solid var(--color-border-default)' : undefined,
|
|
}}
|
|
>
|
|
{session.session_type === 'chat' ? (
|
|
<MessageCircle size={14} className="shrink-0 text-violet-400" />
|
|
) : (
|
|
<StatusIcon size={14} style={{ color: config.color }} className="shrink-0" />
|
|
)}
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-sm font-medium text-foreground truncate">
|
|
{session.session_type === 'chat'
|
|
? (session.title || session.problem_summary || 'Chat')
|
|
: (session.problem_summary || 'Session')}
|
|
</p>
|
|
{session.problem_domain && (
|
|
<p className="text-[0.625rem] text-muted-foreground mt-0.5 truncate">{session.problem_domain}</p>
|
|
)}
|
|
</div>
|
|
<span className="shrink-0 font-sans text-xs text-muted-foreground">
|
|
{timeAgo(session.resolved_at || session.created_at)}
|
|
</span>
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|