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:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
95
frontend/src/components/flowpilot/SimilarSessions.tsx
Normal file
95
frontend/src/components/flowpilot/SimilarSessions.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user