feat(ui): thread sessions in sidebar list

Show sessions as parent/child threads with expand/collapse and improved agent row styling. Keep a 5-session cache to avoid refetching messages when switching between recently visited sessions.
This commit is contained in:
Shantur Rathore
2026-01-09 16:02:53 +00:00
parent d76cf8a3f7
commit e50d9f461a
7 changed files with 306 additions and 284 deletions

View File

@@ -23,6 +23,12 @@ export interface SessionInfo {
contextAvailableTokens: number | null
}
export type SessionThread = {
parent: Session
children: Session[]
latestUpdated: number
}
const [sessions, setSessions] = createSignal<Map<string, Map<string, Session>>>(new Map())
const [activeSessionId, setActiveSessionId] = createSignal<Map<string, string>>(new Map())
const [activeParentSessionId, setActiveParentSessionId] = createSignal<Map<string, string>>(new Map())
@@ -375,6 +381,55 @@ function getSessionFamily(instanceId: string, parentId: string): Session[] {
return [parent, ...children]
}
function getSessionThreads(instanceId: string): SessionThread[] {
const instanceSessions = sessions().get(instanceId)
if (!instanceSessions || instanceSessions.size === 0) return []
const parents: Session[] = []
const childrenByParent = new Map<string, Session[]>()
for (const session of instanceSessions.values()) {
if (session.parentId === null) {
parents.push(session)
continue
}
const parentId = session.parentId
if (!parentId) continue
const children = childrenByParent.get(parentId)
if (children) {
children.push(session)
} else {
childrenByParent.set(parentId, [session])
}
}
const threads: SessionThread[] = []
for (const parent of parents) {
const children = childrenByParent.get(parent.id) ?? []
if (children.length > 1) {
children.sort((a, b) => (b.time.updated ?? 0) - (a.time.updated ?? 0))
}
const parentUpdated = parent.time.updated ?? 0
const latestChild = children[0]?.time.updated ?? 0
const latestUpdated = Math.max(parentUpdated, latestChild)
threads.push({ parent, children, latestUpdated })
}
threads.sort((a, b) => {
if (b.latestUpdated !== a.latestUpdated) return b.latestUpdated - a.latestUpdated
const bParentUpdated = b.parent.time.updated ?? 0
const aParentUpdated = a.parent.time.updated ?? 0
if (bParentUpdated !== aParentUpdated) return bParentUpdated - aParentUpdated
return b.parent.id.localeCompare(a.parent.id)
})
return threads
}
function isSessionBusy(instanceId: string, sessionId: string): boolean {
const instanceSessions = sessions().get(instanceId)
if (!instanceSessions) return false
@@ -530,6 +585,7 @@ export {
getParentSessions,
getChildSessions,
getSessionFamily,
getSessionThreads,
isSessionBusy,
isSessionMessagesLoading,
getSessionInfo,

View File

@@ -16,6 +16,7 @@ import {
getSessionDraftPrompt,
getSessionFamily,
getSessionInfo,
getSessionThreads,
getSessions,
isSessionBusy,
isSessionMessagesLoading,
@@ -100,6 +101,7 @@ export {
getSessionDraftPrompt,
getSessionFamily,
getSessionInfo,
getSessionThreads,
getSessions,
isSessionBusy,
isSessionMessagesLoading,