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

@@ -27,6 +27,7 @@ export function SessionHistoryPage() {
const aiSearchTimeout = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
const [aiFilters, setAiFilters] = useState({
q: '',
session_type: '',
problem_domain: '',
confidence_tier: '',
date_from: '',
@@ -176,6 +177,7 @@ export function SessionHistoryPage() {
const data = await aiSessionsApi.listSessions({
limit: 50,
q: aiFilters.q || undefined,
session_type: aiFilters.session_type || undefined,
problem_domain: aiFilters.problem_domain || undefined,
confidence_tier: aiFilters.confidence_tier || undefined,
date_from: aiFilters.date_from || undefined,
@@ -267,7 +269,7 @@ export function SessionHistoryPage() {
return labels[outcome] ?? outcome
}
const hasAiFiltersActive = !!(aiSearchInput || aiFilters.q || aiFilters.problem_domain || aiFilters.confidence_tier || aiFilters.date_from || aiFilters.date_to)
const hasAiFiltersActive = !!(aiSearchInput || aiFilters.q || aiFilters.session_type || aiFilters.problem_domain || aiFilters.confidence_tier || aiFilters.date_from || aiFilters.date_to)
const hasFlowFiltersActive = !!(filters.ticketNumber || filters.clientName || filters.treeName || filters.dateRange?.from)
// Determine section visibility
@@ -314,7 +316,7 @@ export function SessionHistoryPage() {
{/* FlowPilot Sessions Section */}
{showAiSection && (
<>
<h2 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-3">FlowPilot Sessions</h2>
<h2 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-3">AI Sessions</h2>
{/* AI Session Filter Bar */}
<div className="card-flat p-3 mb-4">
@@ -331,6 +333,24 @@ export function SessionHistoryPage() {
/>
</div>
{/* Session type pills */}
<div className="flex gap-1">
{(['', 'guided', 'chat'] as const).map((t) => (
<button
key={t}
onClick={() => setAiFilters((f) => ({ ...f, session_type: t }))}
className={cn(
'rounded-full border px-3 py-1 text-xs font-sans transition-colors',
aiFilters.session_type === t
? 'bg-accent-dim text-foreground border-primary/30'
: 'bg-card text-muted-foreground border-border hover:text-foreground hover:border-[rgba(255,255,255,0.12)]'
)}
>
{t === '' ? 'All' : t === 'guided' ? 'Guided' : 'Chat'}
</button>
))}
</div>
{/* Problem domain dropdown */}
<select
value={aiFilters.problem_domain}
@@ -393,7 +413,7 @@ export function SessionHistoryPage() {
<button
onClick={() => {
setAiSearchInput('')
setAiFilters({ q: '', problem_domain: '', confidence_tier: '', date_from: '', date_to: '' })
setAiFilters({ q: '', session_type: '', problem_domain: '', confidence_tier: '', date_from: '', date_to: '' })
}}
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
>
@@ -415,7 +435,7 @@ export function SessionHistoryPage() {
<button
onClick={() => {
setAiSearchInput('')
setAiFilters({ q: '', problem_domain: '', confidence_tier: '', date_from: '', date_to: '' })
setAiFilters({ q: '', session_type: '', problem_domain: '', confidence_tier: '', date_from: '', date_to: '' })
}}
className="text-foreground hover:underline text-sm"
>