feat: unified sessions — merge assistant chat into ai_sessions table

Add session_type ('guided'|'chat') and title columns to ai_sessions,
enabling both FlowPilot guided sessions and assistant chat sessions to
live in a single table. This is the foundation for a unified session
history and consistent UX across both interaction modes.

Backend:
- Migration 066: session_type + title columns
- unified_chat_service: chat sessions on ai_sessions with same AI/RAG
- POST /ai-sessions supports session_type='chat' creation
- POST /ai-sessions/{id}/chat for chat messages
- DELETE /ai-sessions/{id} for session deletion
- session_type filter on GET /ai-sessions

Frontend:
- AssistantChatPage rewired to aiSessionsApi (no more assistantChatApi)
- /assistant/:sessionId route for deep-linking
- Session history: type filter pills (All/Guided/Chat), type icons
- Dashboard: both types shown with correct routing and icons
- Fixed glass-border → border-default in dashboard components

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 17:29:25 +00:00
parent 72678e7f26
commit b414502062
15 changed files with 685 additions and 88 deletions

View File

@@ -1,5 +1,5 @@
import { Link } from 'react-router-dom'
import { Clock, CheckCircle2, ArrowUpRight, AlertCircle, Pause } from 'lucide-react'
import { Clock, CheckCircle2, ArrowUpRight, AlertCircle, Pause, Route, MessageCircle } from 'lucide-react'
import { cn } from '@/lib/utils'
import type { AISessionSummary } from '@/types/ai-session'
@@ -18,17 +18,31 @@ const STATUS_CONFIG = {
export function AISessionListItem({ session }: AISessionListItemProps) {
const config = STATUS_CONFIG[session.status as keyof typeof STATUS_CONFIG] ?? STATUS_CONFIG.active
const StatusIcon = config.icon
const isChat = session.session_type === 'chat'
const TypeIcon = isChat ? MessageCircle : Route
const linkTo = isChat ? `/assistant/${session.id}` : `/pilot/${session.id}`
const displayTitle = isChat
? (session.title || session.problem_summary || 'Untitled chat')
: (session.problem_summary || 'Untitled session')
return (
<Link
to={`/pilot/${session.id}`}
to={linkTo}
className="card-interactive block p-4 transition-all"
>
<div className="flex items-start justify-between gap-3">
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-foreground truncate">
{session.problem_summary || 'Untitled session'}
</p>
<div className="flex items-center gap-2">
<span className={cn(
'flex items-center justify-center w-5 h-5 rounded',
isChat ? 'text-violet-400' : 'text-primary'
)}>
<TypeIcon size={14} />
</span>
<p className="text-sm font-medium text-foreground truncate">
{displayTitle}
</p>
</div>
<div className="mt-1.5 flex items-center gap-3 flex-wrap">
{session.problem_domain && (
<span className="font-sans text-xs rounded-md bg-accent-dim px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-primary">
@@ -40,7 +54,7 @@ export function AISessionListItem({ session }: AISessionListItemProps) {
{config.label}
</span>
<span className="text-xs text-muted-foreground">
{session.step_count} steps
{session.step_count} {isChat ? 'messages' : 'steps'}
</span>
<span className="text-xs text-text-muted">
{new Date(session.created_at).toLocaleDateString(undefined, {