feat(search): add similar sessions UI in FlowPilot sidebar

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-20 03:52:19 +00:00
parent e356103408
commit 5e009fd752
4 changed files with 118 additions and 0 deletions

View File

@@ -11,6 +11,7 @@ import type {
AISessionSummary,
AISessionDetail,
AISessionSearchResult,
SimilarSession,
PickupSessionRequest,
} from '@/types/ai-session'
@@ -122,6 +123,13 @@ export const aiSessionsApi = {
})
return response.data
},
async getSimilar(sessionId: string, limit: number = 5): Promise<SimilarSession[]> {
const response = await apiClient.get<SimilarSession[]>(`/ai-sessions/${sessionId}/similar`, {
params: { limit },
})
return response.data
},
}
export default aiSessionsApi

View File

@@ -13,6 +13,7 @@ import { FlowPilotStepCard } from './FlowPilotStepCard'
import { FlowPilotActionBar } from './FlowPilotActionBar'
import { SessionDocView } from './SessionDocView'
import { SessionTicketCard } from './SessionTicketCard'
import { SimilarSessions } from './SimilarSessions'
import { TicketPickerModal } from '@/components/session/TicketPickerModal'
import { aiSessionsApi } from '@/api'
import { toast } from '@/lib/toast'
@@ -185,6 +186,7 @@ export function FlowPilotSession({
</span>
</div>
)}
<SimilarSessions sessionId={session.id} />
</div>
)}
</div>
@@ -291,6 +293,9 @@ export function FlowPilotSession({
</span>
</div>
</div>
{/* Similar sessions */}
<SimilarSessions sessionId={session.id} />
</div>
</div>
</div>

View File

@@ -0,0 +1,95 @@
import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { Loader2 } from 'lucide-react'
import { aiSessionsApi } from '@/api'
import type { SimilarSession } from '@/types/ai-session'
import { cn } from '@/lib/utils'
interface SimilarSessionsProps {
sessionId: string
}
export function SimilarSessions({ sessionId }: SimilarSessionsProps) {
const [sessions, setSessions] = useState<SimilarSession[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
let cancelled = false
setLoading(true)
aiSessionsApi
.getSimilar(sessionId, 5)
.then((data) => {
if (!cancelled) setSessions(data)
})
.catch(() => {
// Silently ignore errors — don't clutter the UI
})
.finally(() => {
if (!cancelled) setLoading(false)
})
return () => {
cancelled = true
}
}, [sessionId])
if (loading) {
return (
<div className="flex items-center gap-1.5 py-1">
<Loader2 size={10} className="animate-spin text-muted-foreground" />
<span className="text-[0.625rem] text-muted-foreground font-label">Loading similar sessions</span>
</div>
)
}
if (sessions.length === 0) {
return null
}
return (
<div className="space-y-2">
<h4 className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
Similar Past Sessions
</h4>
{sessions.map((session) => (
<Link
key={session.id}
to={`/pilot/${session.id}`}
className="glass-card p-3 block hover:border-[rgba(255,255,255,0.12)] transition-all"
>
<div className="flex items-start justify-between gap-2">
<p className="text-xs text-foreground line-clamp-2">
{session.problem_summary || 'Untitled session'}
</p>
<span className="text-[0.625rem] font-label text-primary shrink-0">
{Math.round(session.similarity * 100)}%
</span>
</div>
{session.resolution_summary && (
<p className="text-[0.625rem] text-muted-foreground mt-1 line-clamp-1">
{session.resolution_summary}
</p>
)}
<div className="flex items-center gap-2 mt-1.5">
{session.problem_domain && (
<span className="text-[0.5rem] font-label uppercase tracking-wider text-muted-foreground/70">
{session.problem_domain}
</span>
)}
<span
className={cn(
'text-[0.5rem] font-label uppercase',
session.status === 'resolved'
? 'text-emerald-400'
: session.status === 'escalated'
? 'text-amber-400'
: 'text-muted-foreground'
)}
>
{session.status}
</span>
</div>
</Link>
))}
</div>
)
}

View File

@@ -149,3 +149,13 @@ export interface AISessionSearchResult {
status: string
created_at: string
}
export interface SimilarSession {
id: string
problem_summary: string | null
problem_domain: string | null
status: string
resolution_summary: string | null
created_at: string | null
similarity: number
}