Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 80 additions & 21 deletions packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,88 @@ export function DialogSessionList() {

const sessions = createMemo(() => searchResults() ?? sync.data.session)

function parseSessionTitle(title: string): { group?: string; displayTitle: string } {
const pipeIndex = title.indexOf("|")
if (pipeIndex === -1) {
return { displayTitle: title }
}

const group = title.slice(0, pipeIndex).trim()
const displayTitle = title.slice(pipeIndex + 1).trim()

if (!group) {
return { displayTitle }
}

return { group, displayTitle }
}

const options = createMemo(() => {
const today = new Date().toDateString()
return sessions()
.filter((x) => x.parentID === undefined)
.toSorted((a, b) => b.time.updated - a.time.updated)
.map((x) => {
const date = new Date(x.time.updated)
let category = date.toDateString()
if (category === today) {
category = "Today"
}
const isDeleting = toDelete() === x.id
const status = sync.data.session_status?.[x.id]
const isWorking = status?.type === "busy"
return {
title: isDeleting ? `Press ${keybind.print("session_delete")} again to confirm` : x.title,
bg: isDeleting ? theme.error : undefined,
value: x.id,
category,
footer: Locale.time(x.time.updated),
gutter: isWorking ? <Spinner /> : undefined,
}
})
const allSessions = sessions().filter((x) => x.parentID === undefined)

// Separate into grouped and ungrouped
const grouped: typeof allSessions = []
const ungrouped: typeof allSessions = []

for (const session of allSessions) {
const parsed = parseSessionTitle(session.title)
if (parsed.group) {
grouped.push(session)
} else {
ungrouped.push(session)
}
}

// Sort grouped by group name ASC, then updated DESC
grouped.sort((a, b) => {
const aParsed = parseSessionTitle(a.title)
const bParsed = parseSessionTitle(b.title)
const groupCompare = (aParsed.group ?? "").localeCompare(bParsed.group ?? "")
if (groupCompare !== 0) return groupCompare
return b.time.updated - a.time.updated
})

// Sort ungrouped by updated DESC
ungrouped.sort((a, b) => b.time.updated - a.time.updated)

// Map grouped sessions
const groupedOptions = grouped.map((session) => {
const parsed = parseSessionTitle(session.title)
const isDeleting = toDelete() === session.id
const status = sync.data.session_status?.[session.id]
const isWorking = status?.type === "busy"
return {
title: isDeleting ? `Press ${keybind.print("session_delete")} again to confirm` : parsed.displayTitle,
bg: isDeleting ? theme.error : undefined,
value: session.id,
category: parsed.group,
footer: Locale.shortDateTime(session.time.updated),
gutter: isWorking ? <Spinner /> : undefined,
}
})

// Map ungrouped sessions
const ungroupedOptions = ungrouped.map((session) => {
const date = new Date(session.time.updated)
let category = date.toDateString()
if (category === today) {
category = "Today"
}
const isDeleting = toDelete() === session.id
const status = sync.data.session_status?.[session.id]
const isWorking = status?.type === "busy"
return {
title: isDeleting ? `Press ${keybind.print("session_delete")} again to confirm` : session.title,
bg: isDeleting ? theme.error : undefined,
value: session.id,
category,
footer: Locale.time(session.time.updated),
gutter: isWorking ? <Spinner /> : undefined,
}
})

return [...groupedOptions, ...ungroupedOptions]
})

onMount(() => {
Expand Down
21 changes: 21 additions & 0 deletions packages/opencode/src/util/locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,27 @@ export namespace Locale {
}
}

export function shortDateTime(input: number): string {
const date = new Date(input)
const now = new Date()
const isToday =
date.getFullYear() === now.getFullYear() &&
date.getMonth() === now.getMonth() &&
date.getDate() === now.getDate()

const timeStr = time(input)

if (isToday) {
return timeStr
} else {
const dateStr = date.toLocaleDateString(undefined, {
month: "short",
day: "numeric",
})
return `${dateStr} · ${timeStr}`
}
}

export function number(num: number): string {
if (num >= 1000000) {
return (num / 1000000).toFixed(1) + "M"
Expand Down