Files
openclaw/ui/src/components/session-attention-presentation.ts
Peter Steinberger b8ff4fe3d7 fix: show sessions waiting for concurrency slots (#125654)
* fix: show sessions waiting for concurrency slots

* test: align queued session integration fixtures

* test: distinguish queued and reactivated followups

* fix: preserve queued state in workboard and android

* fix: project queued status through chat history

* test(ui): keep queued sidebar case under line cap
2026-08-18 02:31:39 -07:00

104 lines
3.3 KiB
TypeScript

import { html, nothing } from "lit";
import { t } from "../i18n/index.ts";
import type { SidebarRecentSession, SidebarSessionAttention } from "./app-sidebar-session-types.ts";
import { icons } from "./icons.ts";
import { resolveSessionAttentionIcon } from "./session-attention-icon-registry.ts";
export function renderSessionAttentionIcon(attention: SidebarSessionAttention) {
if (attention.kind === "none") {
return nothing;
}
const icon =
attention.kind === "question"
? icons.hand
: attention.kind === "approval"
? icons.shieldQuestion
: attention.kind === "agent"
? resolveSessionAttentionIcon(attention.icon)
: icons.alertTriangle;
return html`<span
class="sidebar-session-attention__icon sidebar-session-attention__icon--${attention.kind}"
data-session-attention=${attention.kind}
aria-hidden="true"
>${icon}</span
>`;
}
export function sessionAttentionSubtitle(attention: SidebarSessionAttention): string | undefined {
switch (attention.kind) {
case "question":
return t("sessionsView.waitingForAnswer");
case "approval":
return t("sessionsView.waitingForApproval");
case "error":
return t("sessionsView.runFailedReason", { reason: attention.reason });
case "agent":
return attention.note;
case "none":
return undefined;
default:
return attention satisfies never;
}
}
export function renderSessionUnreadState(session: SidebarRecentSession) {
return !session.isChild && session.unread
? html`<span
class="session-unread-dot sidebar-recent-session__unread"
role="img"
aria-label=${t("sessionsView.unread")}
></span>`
: nothing;
}
export function renderSessionRunSpinner(showTitle = true) {
return html`<span
class="session-run-spinner sidebar-recent-session__state"
role="img"
aria-label=${t("sessionsView.activeRun")}
title=${showTitle ? t("sessionsView.activeRun") : nothing}
></span>`;
}
export function renderSessionState(session: SidebarRecentSession, showTitle = true) {
if (session.hasActiveRun) {
if (session.status === "queued") {
const label = t("sessionsView.statusQueued");
return html`<span
class="sidebar-child-session__status sidebar-child-session__status--queued"
role="img"
aria-label=${label}
title=${showTitle ? label : nothing}
>${icons.hourglass}</span
>`;
}
return renderSessionRunSpinner(showTitle);
}
if (!session.isChild) {
return renderSessionUnreadState(session);
}
const status = session.status;
if (!status) {
return nothing;
}
const statusBadge =
status === "done"
? { icon: icons.check, label: t("sessionsView.statusDone") }
: status === "killed"
? { icon: icons.stop, label: t("sessionsView.statusKilled") }
: status === "timeout"
? { icon: icons.alertTriangle, label: t("sessionsView.statusTimeout") }
: status === "failed"
? { icon: icons.alertTriangle, label: t("sessionsView.statusFailed") }
: null;
return statusBadge
? html`<span
class="sidebar-child-session__status sidebar-child-session__status--${status}"
role="img"
aria-label=${statusBadge.label}
title=${statusBadge.label}
>${statusBadge.icon}</span
>`
: nothing;
}