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,
|
AISessionSummary,
|
||||||
AISessionDetail,
|
AISessionDetail,
|
||||||
AISessionSearchResult,
|
AISessionSearchResult,
|
||||||
|
SimilarSession,
|
||||||
PickupSessionRequest,
|
PickupSessionRequest,
|
||||||
} from '@/types/ai-session'
|
} from '@/types/ai-session'
|
||||||
|
|
||||||
@@ -122,6 +123,13 @@ export const aiSessionsApi = {
|
|||||||
})
|
})
|
||||||
return response.data
|
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
|
export default aiSessionsApi
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { FlowPilotStepCard } from './FlowPilotStepCard'
|
|||||||
import { FlowPilotActionBar } from './FlowPilotActionBar'
|
import { FlowPilotActionBar } from './FlowPilotActionBar'
|
||||||
import { SessionDocView } from './SessionDocView'
|
import { SessionDocView } from './SessionDocView'
|
||||||
import { SessionTicketCard } from './SessionTicketCard'
|
import { SessionTicketCard } from './SessionTicketCard'
|
||||||
|
import { SimilarSessions } from './SimilarSessions'
|
||||||
import { TicketPickerModal } from '@/components/session/TicketPickerModal'
|
import { TicketPickerModal } from '@/components/session/TicketPickerModal'
|
||||||
import { aiSessionsApi } from '@/api'
|
import { aiSessionsApi } from '@/api'
|
||||||
import { toast } from '@/lib/toast'
|
import { toast } from '@/lib/toast'
|
||||||
@@ -185,6 +186,7 @@ export function FlowPilotSession({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<SimilarSessions sessionId={session.id} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -291,6 +293,9 @@ export function FlowPilotSession({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Similar sessions */}
|
||||||
|
<SimilarSessions sessionId={session.id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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
|
status: string
|
||||||
created_at: 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