Replace hardcoded Tailwind color utilities with semantic CSS variable tokens across 31 files in the FlowPilot, Assistant Chat, and Script Builder feature communities — the areas graphify identified as design-system-free. - text-blue-400 → text-accent, bg-blue-500/10 → bg-accent-dim, border-blue-500/20 → border-accent/20 - text-amber-400 → text-warning, bg-amber-400/10 → bg-warning-dim, border-l-amber-500 → border-l-warning - text-rose-400/500 → text-danger, bg-rose-500/10 → bg-danger-dim - text-emerald-400 → text-success, bg-emerald-500/10 → bg-success-dim, border-l-emerald-500 → border-l-success - bg-white/[0.08] → bg-elevated (opacity hack → semantic surface token) - bg-gradient-to-r from-blue-500 to-blue-400 → bg-accent (no gradient surfaces) - bg-[#60a5fa] → bg-accent (hard-coded hex removed) Also adds graphify-out/ to .gitignore. Theme resilience: accent color has changed twice in 5 weeks. Semantic tokens mean the next change is a 1-line edit in index.css, not 110 grep-and-replace. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
97 lines
3.1 KiB
TypeScript
97 lines
3.1 KiB
TypeScript
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
|
|
// eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: syncs derived state from props
|
|
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-sans text-xs">Loading similar sessions…</span>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (sessions.length === 0) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-2">
|
|
<h4 className="font-sans text-xs 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="card-interactive 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-sans text-xs 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-sans text-xs uppercase tracking-wider text-muted-foreground/70">
|
|
{session.problem_domain}
|
|
</span>
|
|
)}
|
|
<span
|
|
className={cn(
|
|
'text-[0.5rem] font-sans text-xs uppercase',
|
|
session.status === 'resolved'
|
|
? 'text-success'
|
|
: session.status === 'escalated'
|
|
? 'text-warning'
|
|
: 'text-muted-foreground'
|
|
)}
|
|
>
|
|
{session.status}
|
|
</span>
|
|
</div>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|