mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat(web): pin running sessions in an 'in progress' section with a live badge
This commit is contained in:
@@ -1096,6 +1096,8 @@
|
||||
|
||||
"@twsxtd/hapi-linux-x64": ["@twsxtd/hapi-linux-x64@0.25.3", "", { "os": "linux", "cpu": "x64", "bin": { "hapi": "bin/hapi" } }, "sha512-mLe7mJKctdEIeMmf19u0wWcpf4Lj7sA/SJT65cd+4G5wWRy632SAPcJ7pl+KSdsbEu9TMTyrXm1L4usB0xc4Gg=="],
|
||||
|
||||
"@twsxtd/hapi-win32-x64": ["@twsxtd/hapi-win32-x64@0.25.3", "", { "os": "win32", "cpu": "x64", "bin": { "hapi": "bin/hapi.exe" } }, "sha512-K1zhYTj8eAPt5W7q92U4ywdUs0122faulU2kywHOuzmwO95nI4zQ0CXlCNkJlGpTv6AYj7GiQdU21sR1NJSYFg=="],
|
||||
|
||||
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
|
||||
|
||||
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
||||
|
||||
@@ -764,9 +764,10 @@ function SessionItem(props: {
|
||||
api: ApiClient | null
|
||||
selected?: boolean
|
||||
showDetailedStatus?: boolean
|
||||
inRunningSection?: boolean
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { session: s, onSelect, showPath = true, api, selected = false, showDetailedStatus = false } = props
|
||||
const { session: s, onSelect, showPath = true, api, selected = false, showDetailedStatus = false, inRunningSection = false } = props
|
||||
const { haptic } = usePlatform()
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
const [menuAnchorPoint, setMenuAnchorPoint] = useState<{ x: number; y: number }>({ x: 0, y: 0 })
|
||||
@@ -859,6 +860,7 @@ function SessionItem(props: {
|
||||
nestedTooltips
|
||||
attentionTooltipId={attentionId}
|
||||
scheduleTooltipId={scheduleId}
|
||||
inRunningSection={inRunningSection}
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -1066,8 +1068,14 @@ export function SessionList(props: {
|
||||
: visibleSessions.filter(session => (session.metadata?.machineId ?? UNKNOWN_MACHINE_ID) === activeMachineFilter),
|
||||
[visibleSessions, activeMachineFilter]
|
||||
)
|
||||
const runningSessions = useMemo(
|
||||
() => machineFilteredSessions
|
||||
.filter((session) => session.active)
|
||||
.sort((a, b) => b.updatedAt - a.updatedAt),
|
||||
[machineFilteredSessions]
|
||||
)
|
||||
const groups = useMemo(
|
||||
() => groupSessionsByDirectory(machineFilteredSessions),
|
||||
() => groupSessionsByDirectory(machineFilteredSessions.filter((session) => !session.active)),
|
||||
[machineFilteredSessions]
|
||||
)
|
||||
const [collapseOverrides, setCollapseOverrides] = useState<Map<string, boolean>>(
|
||||
@@ -1393,12 +1401,41 @@ export function SessionList(props: {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{props.sessions.length > 0 && (isFiltering || activeMachineFilter !== null) && groups.length === 0 ? (
|
||||
{props.sessions.length > 0 && (isFiltering || activeMachineFilter !== null) && groups.length === 0 && runningSessions.length === 0 ? (
|
||||
<div className="px-4 py-8 text-center text-sm text-[var(--app-hint)]">
|
||||
{t('sessions.search.noResults')}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{runningSessions.length > 0 ? (
|
||||
<div key="running-section">
|
||||
<div className="group/running flex min-w-0 w-full select-none items-center gap-2 rounded-lg py-1.5 pl-2 pr-2">
|
||||
<span className="inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center" aria-hidden="true">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-[var(--app-badge-success-text)] animate-pulse" />
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate text-sm font-medium">
|
||||
{t('sessions.runningSection')}
|
||||
</span>
|
||||
<span className="shrink-0 text-[11px] tabular-nums text-[var(--app-hint)]">
|
||||
({runningSessions.length})
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-0.5 ml-3 pl-1 py-1">
|
||||
{runningSessions.map((s) => (
|
||||
<SessionItem
|
||||
key={s.id}
|
||||
session={s}
|
||||
onSelect={props.onSelect}
|
||||
showPath={false}
|
||||
api={api}
|
||||
selected={s.id === selectedSessionId}
|
||||
showDetailedStatus={showDetailedStatus}
|
||||
inRunningSection
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{groups.map((group) => {
|
||||
const isCollapsed = isGroupCollapsed(group)
|
||||
const visibleGroupSessions = getVisibleGroupSessions(group)
|
||||
|
||||
@@ -111,6 +111,8 @@ export function SessionRowSummary(props: {
|
||||
attentionTooltipId?: string
|
||||
scheduleTooltipId?: string
|
||||
className?: string
|
||||
/** Rows inside the pinned "in progress" section skip the text label (dot only). */
|
||||
inRunningSection?: boolean
|
||||
}) {
|
||||
const {
|
||||
session: s,
|
||||
@@ -121,6 +123,7 @@ export function SessionRowSummary(props: {
|
||||
attentionTooltipId: attentionTooltipIdProp,
|
||||
scheduleTooltipId: scheduleTooltipIdProp,
|
||||
className,
|
||||
inRunningSection = false,
|
||||
} = props
|
||||
const { t } = useTranslation()
|
||||
const sessionName = getSessionTitle(s)
|
||||
@@ -136,6 +139,8 @@ export function SessionRowSummary(props: {
|
||||
[s, selected, showDetailedStatus]
|
||||
)
|
||||
const attentionLabel = attention ? getAttentionLabel(attention, t) : null
|
||||
const urgentAttention = attention !== null
|
||||
&& (attention.kind === 'permission' || attention.kind === 'input')
|
||||
const scheduledLabel = s.futureScheduledMessageCount > 1
|
||||
? t('session.item.scheduledMessages', { count: s.futureScheduledMessageCount })
|
||||
: t('session.item.scheduledMessage')
|
||||
@@ -160,7 +165,30 @@ export function SessionRowSummary(props: {
|
||||
{sessionName}
|
||||
</div>
|
||||
{s.active && s.thinking ? (
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin-slow text-[var(--app-hint)]" />
|
||||
<LoaderIcon className="h-3.5 w-3.5 shrink-0 animate-spin-slow text-[var(--app-badge-success-text)]" />
|
||||
) : urgentAttention && nestedTooltips && attentionId ? (
|
||||
<SessionAttentionIndicator
|
||||
attention={attention}
|
||||
summary={s}
|
||||
label={attentionLabel ?? ''}
|
||||
tooltipId={attentionId}
|
||||
/>
|
||||
) : urgentAttention ? (
|
||||
<span
|
||||
className={`inline-flex h-2 w-2 shrink-0 rounded-full ${ATTENTION_DOT_CLASS[attention.kind]}`}
|
||||
title={attentionLabel ?? undefined}
|
||||
aria-label={attentionLabel ?? undefined}
|
||||
/>
|
||||
) : s.active ? (
|
||||
<span
|
||||
className="inline-flex shrink-0 items-center gap-1 text-[var(--app-badge-success-text)]"
|
||||
title={t('session.item.running')}
|
||||
>
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-current animate-pulse" aria-hidden="true" />
|
||||
{!inRunningSection ? (
|
||||
<span className="text-[11px] font-medium leading-none">{t('session.item.running')}</span>
|
||||
) : null}
|
||||
</span>
|
||||
) : attention && nestedTooltips && attentionId ? (
|
||||
<SessionAttentionIndicator
|
||||
attention={attention}
|
||||
|
||||
@@ -74,6 +74,7 @@ export default {
|
||||
'sessions.timeFilter.dayWithActivity': '{date}, has session activity',
|
||||
'sessions.group.expand': 'Expand {n}',
|
||||
'sessions.group.collapse': 'Collapse {n}',
|
||||
'sessions.runningSection': 'In progress',
|
||||
'sessions.group.new': 'New session in this directory',
|
||||
'sessions.machineFilter.label': 'Filter sessions by machine',
|
||||
'sessions.machineFilter.all': 'All',
|
||||
@@ -133,6 +134,7 @@ export default {
|
||||
'session.item.worktree': 'worktree',
|
||||
'session.item.pending': 'pending',
|
||||
'session.item.thinking': 'thinking',
|
||||
'session.item.running': 'Running',
|
||||
'session.item.permission': 'Permission required',
|
||||
'session.item.needsInput': 'Needs input',
|
||||
'session.item.background': 'Background tasks running',
|
||||
|
||||
@@ -74,6 +74,7 @@ export default {
|
||||
'sessions.timeFilter.dayWithActivity': '{date},有会话活动',
|
||||
'sessions.group.expand': '展开 {n} 个',
|
||||
'sessions.group.collapse': '收起 {n} 个',
|
||||
'sessions.runningSection': '进行中',
|
||||
'sessions.group.new': '在此目录新建会话',
|
||||
'sessions.machineFilter.label': '按机器筛选会话',
|
||||
'sessions.machineFilter.all': '全部',
|
||||
@@ -133,6 +134,7 @@ export default {
|
||||
'session.item.worktree': '工作树',
|
||||
'session.item.pending': '待处理',
|
||||
'session.item.thinking': '思考中',
|
||||
'session.item.running': '运行中',
|
||||
'session.item.permission': '需要权限',
|
||||
'session.item.needsInput': '需要输入',
|
||||
'session.item.background': '后台任务运行中',
|
||||
|
||||
Reference in New Issue
Block a user