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:
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { Sparkles, Clock, ArrowRight } from 'lucide-react'
|
||||
import { Clock, ArrowRight, Route, MessageCircle } from 'lucide-react'
|
||||
import { aiSessionsApi } from '@/api/aiSessions'
|
||||
import type { AISessionSummary } from '@/types/ai-session'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -30,7 +30,7 @@ export function ActiveFlowPilotSessions() {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="card-flat">
|
||||
<div className="px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
|
||||
<div className="px-5 py-3" style={{ borderBottom: '1px solid var(--color-border-default)' }}>
|
||||
<h3 className="font-heading text-sm font-bold text-foreground">Active Sessions</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 p-4">
|
||||
@@ -46,7 +46,7 @@ export function ActiveFlowPilotSessions() {
|
||||
<div className="card-flat">
|
||||
<div
|
||||
className="flex items-center justify-between px-5 py-3"
|
||||
style={{ borderBottom: '1px solid var(--glass-border)' }}
|
||||
style={{ borderBottom: '1px solid var(--color-border-default)' }}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-heading text-sm font-bold text-foreground">Active Sessions</h3>
|
||||
@@ -74,11 +74,15 @@ export function ActiveFlowPilotSessions() {
|
||||
{sessions.map((session) => (
|
||||
<button
|
||||
key={session.id}
|
||||
onClick={() => navigate(`/pilot/${session.id}`)}
|
||||
onClick={() => navigate(session.session_type === 'chat' ? `/assistant/${session.id}` : `/pilot/${session.id}`)}
|
||||
className="card-interactive p-4 text-left"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2 mb-2">
|
||||
<Sparkles size={14} className="shrink-0 text-primary mt-0.5" />
|
||||
{session.session_type === 'chat' ? (
|
||||
<MessageCircle size={14} className="shrink-0 text-violet-400 mt-0.5" />
|
||||
) : (
|
||||
<Route size={14} className="shrink-0 text-primary mt-0.5" />
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
'font-sans text-xs text-[0.5625rem] uppercase px-1.5 py-0.5 rounded',
|
||||
@@ -92,7 +96,9 @@ export function ActiveFlowPilotSessions() {
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm font-medium text-foreground truncate">
|
||||
{session.problem_summary || 'Session in progress'}
|
||||
{session.session_type === 'chat'
|
||||
? (session.title || session.problem_summary || 'Chat in progress')
|
||||
: (session.problem_summary || 'Session in progress')}
|
||||
</p>
|
||||
<div className="mt-2 flex items-center gap-2 text-[0.625rem] text-muted-foreground">
|
||||
<span className="flex items-center gap-1">
|
||||
@@ -100,7 +106,7 @@ export function ActiveFlowPilotSessions() {
|
||||
{timeAgo(session.created_at)}
|
||||
</span>
|
||||
<span>·</span>
|
||||
<span>{session.step_count} steps</span>
|
||||
<span>{session.step_count} {session.session_type === 'chat' ? 'messages' : 'steps'}</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { CheckCircle, AlertTriangle, XCircle, ArrowRight } from 'lucide-react'
|
||||
import { CheckCircle, AlertTriangle, XCircle, ArrowRight, MessageCircle } from 'lucide-react'
|
||||
import { aiSessionsApi } from '@/api/aiSessions'
|
||||
import type { AISessionSummary } from '@/types/ai-session'
|
||||
|
||||
@@ -44,7 +44,7 @@ export function RecentFlowPilotSessions() {
|
||||
<div className="card-flat">
|
||||
<div
|
||||
className="flex items-center justify-between px-5 py-3"
|
||||
style={{ borderBottom: '1px solid var(--glass-border)' }}
|
||||
style={{ borderBottom: '1px solid var(--color-border-default)' }}
|
||||
>
|
||||
<h3 className="font-heading text-sm font-bold text-foreground">Recent Sessions</h3>
|
||||
<Link
|
||||
@@ -61,16 +61,22 @@ export function RecentFlowPilotSessions() {
|
||||
return (
|
||||
<button
|
||||
key={session.id}
|
||||
onClick={() => navigate(`/pilot/${session.id}`)}
|
||||
onClick={() => navigate(session.session_type === 'chat' ? `/assistant/${session.id}` : `/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(--glass-border)' : undefined,
|
||||
borderBottom: i < sessions.length - 1 ? '1px solid var(--color-border-default)' : undefined,
|
||||
}}
|
||||
>
|
||||
<StatusIcon size={14} style={{ color: config.color }} className="shrink-0" />
|
||||
{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 text-foreground truncate">
|
||||
{session.problem_summary || 'Session'}
|
||||
{session.session_type === 'chat'
|
||||
? (session.title || session.problem_summary || 'Chat')
|
||||
: (session.problem_summary || 'Session')}
|
||||
</p>
|
||||
</div>
|
||||
<span className="shrink-0 font-sans text-xs text-muted-foreground">
|
||||
|
||||
@@ -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, {
|
||||
|
||||
Reference in New Issue
Block a user