import React, { useState } from 'react'
import { createFileRoute } from '@tanstack/react-router'
import { SubBar } from '../shell/SubBar'
import { Tag } from '../primitives'
import { useSession } from '../features/session/useSession'
import { TurnList } from '../features/session/TurnList'
import { Transcript } from '../features/session/Transcript'
import { AuthError, APIError } from '../api/client'
import '../styles/session.css'
export const Route = createFileRoute('/session/$tool/$host/$id')({
component: SessionRoute,
})
function SessionRoute(): React.JSX.Element {
const { tool, host, id } = Route.useParams()
const { data: session, isLoading, error } = useSession(tool, host, id)
const firstSeq = session?.turns[0]?.i ?? 0
const [selected, setSelected] = useState<number>(firstSeq)
// When turns load and selected hasn't been set yet (still at 0), set to first
const effectiveSelected =
session != null && selected === 0 && session.turns.length > 0
? session.turns[0].i
: selected
function handleSelect(seq: number): void {
setSelected(seq)
const el = document.getElementById(`turn-${seq}`)
if (el != null) {
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}
if (isLoading) {
return (
<>
<SubBar>
<Tag kind="tool">{tool}</Tag>
<Tag kind="host">{host}</Tag>
</SubBar>
<div
className="body body-pad"
style={{ display: 'flex', justifyContent: 'center', paddingTop: 40 }}
>
<span className="mono muted">loading session…</span>
</div>
</>
)
}
if (error != null) {
if (error instanceof AuthError) {
return (
<>
<SubBar>
<Tag kind="tool">{tool}</Tag>
<Tag kind="host">{host}</Tag>
</SubBar>
<div
className="body body-pad"
style={{
display: 'flex',
justifyContent: 'center',
paddingTop: 60,
}}
>
<div className="card" style={{ padding: '24px 32px', textAlign: 'center' }}>
<div className="uppercase-mono" style={{ marginBottom: 8 }}>
not authenticated
</div>
<div className="muted">Sign in to view this session.</div>
</div>
</div>
</>
)
}
const detail = error instanceof APIError ? error.message : String(error)
return (
<>
<SubBar>
<Tag kind="tool">{tool}</Tag>
<Tag kind="host">{host}</Tag>
</SubBar>
<div
className="body body-pad"
style={{
display: 'flex',
justifyContent: 'center',
paddingTop: 60,
}}
>
<div className="card" style={{ padding: '24px 32px', textAlign: 'center' }}>
<div className="uppercase-mono" style={{ marginBottom: 8 }}>
error
</div>
<div className="muted">{detail}</div>
</div>
</div>
</>
)
}
if (session == null) {
return <></>
}
const summaryPreview =
session.summary.length > 56
? session.summary.slice(0, 56) + '…'
: session.summary
return (
<>
<SubBar>
{session.cwd !== '' && (
<span className="mono muted truncate" style={{ maxWidth: 200 }}>
{session.cwd}
</span>
)}
<span style={{ fontWeight: 600 }}>{summaryPreview}</span>
<Tag kind="tool">{tool}</Tag>
<Tag kind="host">{host}</Tag>
{session.model != null && (
<Tag kind="neutral">{session.model}</Tag>
)}
<span style={{ flex: 1 }} />
<span className="mono muted">
{session.turns.length} turns
</span>
</SubBar>
<div
style={{
flex: 1,
display: 'flex',
overflow: 'hidden',
minHeight: 0,
}}
>
<TurnList
turns={session.turns}
selected={effectiveSelected}
onSelect={handleSelect}
/>
<Transcript
turns={session.turns}
selected={effectiveSelected}
onSelect={handleSelect}
/>
</div>
</>
)
}