feat(web): session list status indicators (attention + scheduled) (#699)

This commit is contained in:
HeavyGee
2026-05-29 15:43:21 +08:00
committed by GitHub
parent 7457a8fc32
commit ec3722aba9
33 changed files with 910 additions and 37 deletions
+34 -3
View File
@@ -7,11 +7,15 @@ import { useSessionActions } from '@/hooks/mutations/useSessionActions'
import { SessionActionMenu } from '@/components/SessionActionMenu'
import { RenameSessionDialog } from '@/components/RenameSessionDialog'
import { ConfirmDialog } from '@/components/ui/ConfirmDialog'
import { CopyIcon, CheckIcon } from '@/components/icons'
import { CopyIcon, CheckIcon, ScheduleIcon } from '@/components/icons'
import { cn } from '@/lib/utils'
import { useTranslation } from '@/lib/use-translation'
import { DEFAULT_SESSION_PREVIEW_LIMIT, useSessionPreviewLimit } from '@/hooks/useSessionPreviewLimit'
import { AgentFlavorIcon } from '@/components/AgentFlavorIcon'
import { useSessionListStatusMode } from '@/hooks/useSessionListStatusMode'
import { classifySessionAttention } from '@/lib/sessionAttention'
import { getSessionLastSeenAt } from '@/lib/sessionLastSeen'
import { getAttentionLabel, SessionAttentionIndicator } from '@/components/SessionAttentionIndicator'
type SessionGroup = {
key: string
@@ -523,9 +527,10 @@ function SessionItem(props: {
showPath?: boolean
api: ApiClient | null
selected?: boolean
showDetailedStatus?: boolean
}) {
const { t } = useTranslation()
const { session: s, onSelect, showPath = true, api, selected = false } = props
const { session: s, onSelect, showPath = true, api, selected = false, showDetailedStatus = false } = props
const { haptic } = usePlatform()
const [menuOpen, setMenuOpen] = useState(false)
const [menuAnchorPoint, setMenuAnchorPoint] = useState<{ x: number; y: number }>({ x: 0, y: 0 })
@@ -555,6 +560,19 @@ function SessionItem(props: {
const sessionName = getSessionTitle(s)
const todoProgress = getTodoProgress(s)
const attention = useMemo(
() => showDetailedStatus
? classifySessionAttention(s, {
selected,
lastSeenAt: getSessionLastSeenAt(s.id)
})
: null,
[s, selected, showDetailedStatus]
)
const attentionLabel = attention ? getAttentionLabel(attention, t) : null
const scheduledLabel = s.futureScheduledMessageCount > 1
? t('session.item.scheduledMessages', { count: s.futureScheduledMessageCount })
: t('session.item.scheduledMessage')
return (
<>
<button
@@ -572,6 +590,16 @@ function SessionItem(props: {
</div>
{s.active && s.thinking ? (
<LoaderIcon className="h-3.5 w-3.5 shrink-0 text-[var(--app-hint)] animate-spin-slow" />
) : attention ? (
<SessionAttentionIndicator
attention={attention}
label={attentionLabel ?? ''}
/>
) : null}
{showDetailedStatus && s.futureScheduledMessageCount > 0 ? (
<span title={scheduledLabel} aria-label={scheduledLabel} className="inline-flex shrink-0">
<ScheduleIcon className="h-3.5 w-3.5 text-[var(--app-hint)]" />
</span>
) : null}
</div>
<div className="flex items-center gap-2 shrink-0 text-xs">
@@ -581,7 +609,7 @@ function SessionItem(props: {
{todoProgress.completed}/{todoProgress.total}
</span>
) : null}
{s.pendingRequestsCount > 0 ? (
{!attention && s.pendingRequestsCount > 0 ? (
<span className="text-[var(--app-badge-warning-text)]">
{t('session.item.pending')} {s.pendingRequestsCount}
</span>
@@ -659,6 +687,8 @@ export function SessionList(props: {
const { t } = useTranslation()
const { renderHeader = true, api, selectedSessionId, machineLabelsById = {}, onNewSessionInDirectory } = props
const { sessionPreviewLimit } = useSessionPreviewLimit()
const { sessionListStatusMode } = useSessionListStatusMode()
const showDetailedStatus = sessionListStatusMode === 'detailed'
const [searchQuery, setSearchQuery] = useState('')
const normalizedQuery = normalizeSearch(searchQuery)
const isSearching = normalizedQuery.length > 0
@@ -926,6 +956,7 @@ export function SessionList(props: {
showPath={false}
api={api}
selected={s.id === selectedSessionId}
showDetailedStatus={showDetailedStatus}
/>
))}
{!isSearching && group.sessions.length > sessionPreviewLimit && (sessionGroupExpanded || hiddenSessionCount > 0) ? (