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-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/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=="],
|
"@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
|
api: ApiClient | null
|
||||||
selected?: boolean
|
selected?: boolean
|
||||||
showDetailedStatus?: boolean
|
showDetailedStatus?: boolean
|
||||||
|
inRunningSection?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
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 { haptic } = usePlatform()
|
||||||
const [menuOpen, setMenuOpen] = useState(false)
|
const [menuOpen, setMenuOpen] = useState(false)
|
||||||
const [menuAnchorPoint, setMenuAnchorPoint] = useState<{ x: number; y: number }>({ x: 0, y: 0 })
|
const [menuAnchorPoint, setMenuAnchorPoint] = useState<{ x: number; y: number }>({ x: 0, y: 0 })
|
||||||
@@ -859,6 +860,7 @@ function SessionItem(props: {
|
|||||||
nestedTooltips
|
nestedTooltips
|
||||||
attentionTooltipId={attentionId}
|
attentionTooltipId={attentionId}
|
||||||
scheduleTooltipId={scheduleId}
|
scheduleTooltipId={scheduleId}
|
||||||
|
inRunningSection={inRunningSection}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -1066,8 +1068,14 @@ export function SessionList(props: {
|
|||||||
: visibleSessions.filter(session => (session.metadata?.machineId ?? UNKNOWN_MACHINE_ID) === activeMachineFilter),
|
: visibleSessions.filter(session => (session.metadata?.machineId ?? UNKNOWN_MACHINE_ID) === activeMachineFilter),
|
||||||
[visibleSessions, activeMachineFilter]
|
[visibleSessions, activeMachineFilter]
|
||||||
)
|
)
|
||||||
|
const runningSessions = useMemo(
|
||||||
|
() => machineFilteredSessions
|
||||||
|
.filter((session) => session.active)
|
||||||
|
.sort((a, b) => b.updatedAt - a.updatedAt),
|
||||||
|
[machineFilteredSessions]
|
||||||
|
)
|
||||||
const groups = useMemo(
|
const groups = useMemo(
|
||||||
() => groupSessionsByDirectory(machineFilteredSessions),
|
() => groupSessionsByDirectory(machineFilteredSessions.filter((session) => !session.active)),
|
||||||
[machineFilteredSessions]
|
[machineFilteredSessions]
|
||||||
)
|
)
|
||||||
const [collapseOverrides, setCollapseOverrides] = useState<Map<string, boolean>>(
|
const [collapseOverrides, setCollapseOverrides] = useState<Map<string, boolean>>(
|
||||||
@@ -1393,12 +1401,41 @@ export function SessionList(props: {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : 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)]">
|
<div className="px-4 py-8 text-center text-sm text-[var(--app-hint)]">
|
||||||
{t('sessions.search.noResults')}
|
{t('sessions.search.noResults')}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : 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) => {
|
{groups.map((group) => {
|
||||||
const isCollapsed = isGroupCollapsed(group)
|
const isCollapsed = isGroupCollapsed(group)
|
||||||
const visibleGroupSessions = getVisibleGroupSessions(group)
|
const visibleGroupSessions = getVisibleGroupSessions(group)
|
||||||
|
|||||||
@@ -111,6 +111,8 @@ export function SessionRowSummary(props: {
|
|||||||
attentionTooltipId?: string
|
attentionTooltipId?: string
|
||||||
scheduleTooltipId?: string
|
scheduleTooltipId?: string
|
||||||
className?: string
|
className?: string
|
||||||
|
/** Rows inside the pinned "in progress" section skip the text label (dot only). */
|
||||||
|
inRunningSection?: boolean
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
session: s,
|
session: s,
|
||||||
@@ -121,6 +123,7 @@ export function SessionRowSummary(props: {
|
|||||||
attentionTooltipId: attentionTooltipIdProp,
|
attentionTooltipId: attentionTooltipIdProp,
|
||||||
scheduleTooltipId: scheduleTooltipIdProp,
|
scheduleTooltipId: scheduleTooltipIdProp,
|
||||||
className,
|
className,
|
||||||
|
inRunningSection = false,
|
||||||
} = props
|
} = props
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const sessionName = getSessionTitle(s)
|
const sessionName = getSessionTitle(s)
|
||||||
@@ -136,6 +139,8 @@ export function SessionRowSummary(props: {
|
|||||||
[s, selected, showDetailedStatus]
|
[s, selected, showDetailedStatus]
|
||||||
)
|
)
|
||||||
const attentionLabel = attention ? getAttentionLabel(attention, t) : null
|
const attentionLabel = attention ? getAttentionLabel(attention, t) : null
|
||||||
|
const urgentAttention = attention !== null
|
||||||
|
&& (attention.kind === 'permission' || attention.kind === 'input')
|
||||||
const scheduledLabel = s.futureScheduledMessageCount > 1
|
const scheduledLabel = s.futureScheduledMessageCount > 1
|
||||||
? t('session.item.scheduledMessages', { count: s.futureScheduledMessageCount })
|
? t('session.item.scheduledMessages', { count: s.futureScheduledMessageCount })
|
||||||
: t('session.item.scheduledMessage')
|
: t('session.item.scheduledMessage')
|
||||||
@@ -160,7 +165,30 @@ export function SessionRowSummary(props: {
|
|||||||
{sessionName}
|
{sessionName}
|
||||||
</div>
|
</div>
|
||||||
{s.active && s.thinking ? (
|
{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 ? (
|
) : attention && nestedTooltips && attentionId ? (
|
||||||
<SessionAttentionIndicator
|
<SessionAttentionIndicator
|
||||||
attention={attention}
|
attention={attention}
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export default {
|
|||||||
'sessions.timeFilter.dayWithActivity': '{date}, has session activity',
|
'sessions.timeFilter.dayWithActivity': '{date}, has session activity',
|
||||||
'sessions.group.expand': 'Expand {n}',
|
'sessions.group.expand': 'Expand {n}',
|
||||||
'sessions.group.collapse': 'Collapse {n}',
|
'sessions.group.collapse': 'Collapse {n}',
|
||||||
|
'sessions.runningSection': 'In progress',
|
||||||
'sessions.group.new': 'New session in this directory',
|
'sessions.group.new': 'New session in this directory',
|
||||||
'sessions.machineFilter.label': 'Filter sessions by machine',
|
'sessions.machineFilter.label': 'Filter sessions by machine',
|
||||||
'sessions.machineFilter.all': 'All',
|
'sessions.machineFilter.all': 'All',
|
||||||
@@ -133,6 +134,7 @@ export default {
|
|||||||
'session.item.worktree': 'worktree',
|
'session.item.worktree': 'worktree',
|
||||||
'session.item.pending': 'pending',
|
'session.item.pending': 'pending',
|
||||||
'session.item.thinking': 'thinking',
|
'session.item.thinking': 'thinking',
|
||||||
|
'session.item.running': 'Running',
|
||||||
'session.item.permission': 'Permission required',
|
'session.item.permission': 'Permission required',
|
||||||
'session.item.needsInput': 'Needs input',
|
'session.item.needsInput': 'Needs input',
|
||||||
'session.item.background': 'Background tasks running',
|
'session.item.background': 'Background tasks running',
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export default {
|
|||||||
'sessions.timeFilter.dayWithActivity': '{date},有会话活动',
|
'sessions.timeFilter.dayWithActivity': '{date},有会话活动',
|
||||||
'sessions.group.expand': '展开 {n} 个',
|
'sessions.group.expand': '展开 {n} 个',
|
||||||
'sessions.group.collapse': '收起 {n} 个',
|
'sessions.group.collapse': '收起 {n} 个',
|
||||||
|
'sessions.runningSection': '进行中',
|
||||||
'sessions.group.new': '在此目录新建会话',
|
'sessions.group.new': '在此目录新建会话',
|
||||||
'sessions.machineFilter.label': '按机器筛选会话',
|
'sessions.machineFilter.label': '按机器筛选会话',
|
||||||
'sessions.machineFilter.all': '全部',
|
'sessions.machineFilter.all': '全部',
|
||||||
@@ -133,6 +134,7 @@ export default {
|
|||||||
'session.item.worktree': '工作树',
|
'session.item.worktree': '工作树',
|
||||||
'session.item.pending': '待处理',
|
'session.item.pending': '待处理',
|
||||||
'session.item.thinking': '思考中',
|
'session.item.thinking': '思考中',
|
||||||
|
'session.item.running': '运行中',
|
||||||
'session.item.permission': '需要权限',
|
'session.item.permission': '需要权限',
|
||||||
'session.item.needsInput': '需要输入',
|
'session.item.needsInput': '需要输入',
|
||||||
'session.item.background': '后台任务运行中',
|
'session.item.background': '后台任务运行中',
|
||||||
|
|||||||
Reference in New Issue
Block a user