mirror of
https://fastgit.cc/github.com/openclaw/openclaw
synced 2026-05-01 06:36:23 +08:00
dreaming: add an advanced review tab
This commit is contained in:
@@ -277,6 +277,7 @@ export const en: TranslationMap = {
|
||||
tabs: {
|
||||
scene: "Scene",
|
||||
diary: "Diary",
|
||||
advanced: "Advanced",
|
||||
},
|
||||
header: {
|
||||
refresh: "Refresh",
|
||||
@@ -302,6 +303,21 @@ export const en: TranslationMap = {
|
||||
rem: "Rem",
|
||||
off: "off",
|
||||
},
|
||||
advanced: {
|
||||
eyebrow: "Operator Review",
|
||||
title: "Grounded Replay + Promotion",
|
||||
description:
|
||||
"Inspect grounded replay output and use maintenance actions without cluttering the main Dreaming scene.",
|
||||
stagedTitle: "Grounded Replay",
|
||||
shortTermTitle: "Short-term Queue",
|
||||
signalsTitle: "Signal Hotspots",
|
||||
promotedTitle: "Recent Promotions",
|
||||
emptyGrounded: "No staged grounded replay entries right now.",
|
||||
emptyShortTerm: "No short-term entries to inspect.",
|
||||
emptySignals: "No signal-rich entries to inspect.",
|
||||
emptyPromoted: "No recent promotions to inspect.",
|
||||
updatedPrefix: "updated",
|
||||
},
|
||||
stats: {
|
||||
shortTerm: "Short-term",
|
||||
grounded: "Grounded",
|
||||
|
||||
@@ -415,13 +415,6 @@
|
||||
color: var(--ok-muted);
|
||||
}
|
||||
|
||||
.dreams__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.dreams__status-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
@@ -457,6 +450,181 @@
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
Dreaming Advanced – Operator Review
|
||||
=========================================== */
|
||||
|
||||
.dreams-advanced {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
padding: 24px 32px 40px;
|
||||
flex: 1;
|
||||
min-height: 320px;
|
||||
min-width: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.dreams-advanced__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dreams-advanced__intro {
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.dreams-advanced__eyebrow {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--accent-muted);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dreams-advanced__title {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
line-height: 1.1;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dreams-advanced__description {
|
||||
margin: 10px 0 0;
|
||||
max-width: 60ch;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.dreams-advanced__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dreams-advanced__summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.dreams-advanced__summary-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid color-mix(in oklab, var(--border) 70%, transparent);
|
||||
background: color-mix(in oklab, var(--panel) 88%, transparent);
|
||||
}
|
||||
|
||||
.dreams-advanced__summary-label {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.dreams-advanced__summary-value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dreams-advanced__sections {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.dreams-advanced__section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
border-radius: 16px;
|
||||
border: 1px solid color-mix(in oklab, var(--border) 70%, transparent);
|
||||
background: color-mix(in oklab, var(--panel) 86%, transparent);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dreams-advanced__section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid color-mix(in oklab, var(--border) 62%, transparent);
|
||||
}
|
||||
|
||||
.dreams-advanced__section-title {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.dreams-advanced__section-count {
|
||||
min-width: 26px;
|
||||
height: 26px;
|
||||
padding: 0 8px;
|
||||
border-radius: 999px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: color-mix(in oklab, var(--accent-subtle) 85%, transparent);
|
||||
color: var(--accent);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.dreams-advanced__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dreams-advanced__item {
|
||||
padding: 14px 16px;
|
||||
border-top: 1px solid color-mix(in oklab, var(--border) 50%, transparent);
|
||||
}
|
||||
|
||||
.dreams-advanced__item:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.dreams-advanced__snippet {
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.dreams-advanced__source,
|
||||
.dreams-advanced__meta,
|
||||
.dreams-advanced__empty {
|
||||
font-size: 11px;
|
||||
line-height: 1.45;
|
||||
color: var(--muted);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.dreams-advanced__source {
|
||||
font-family: var(--mono);
|
||||
}
|
||||
|
||||
.dreams-advanced__empty {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* ===========================================
|
||||
Dream Diary – Scroll section below hero
|
||||
=========================================== */
|
||||
@@ -667,6 +835,15 @@
|
||||
min-height: calc(100vh - 96px);
|
||||
}
|
||||
|
||||
.dreams-advanced {
|
||||
padding: 20px 16px 32px;
|
||||
}
|
||||
|
||||
.dreams-advanced__summary,
|
||||
.dreams-advanced__sections {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.dreams__phases {
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -69,6 +69,7 @@ import {
|
||||
backfillDreamDiary,
|
||||
loadDreamDiary,
|
||||
loadDreamingStatus,
|
||||
resetGroundedShortTerm,
|
||||
resetDreamDiary,
|
||||
resolveConfiguredDreaming,
|
||||
updateDreamingEnabled,
|
||||
@@ -1930,8 +1931,14 @@ export function renderApp(state: AppViewState) {
|
||||
${state.tab === "dreams"
|
||||
? renderDreaming({
|
||||
active: dreamingOn,
|
||||
shortTermCount: state.dreamingStatus?.shortTermCount ?? 0,
|
||||
groundedSignalCount: state.dreamingStatus?.groundedSignalCount ?? 0,
|
||||
totalSignalCount: state.dreamingStatus?.totalSignalCount ?? 0,
|
||||
promotedCount: state.dreamingStatus?.promotedToday ?? 0,
|
||||
phases: state.dreamingStatus?.phases ?? undefined,
|
||||
shortTermEntries: state.dreamingStatus?.shortTermEntries ?? [],
|
||||
signalEntries: state.dreamingStatus?.signalEntries ?? [],
|
||||
promotedEntries: state.dreamingStatus?.promotedEntries ?? [],
|
||||
dreamingOf: null,
|
||||
nextCycle: dreamingNextCycle,
|
||||
timezone: state.dreamingStatus?.timezone ?? null,
|
||||
@@ -1947,6 +1954,7 @@ export function renderApp(state: AppViewState) {
|
||||
onRefreshDiary: () => loadDreamDiary(state),
|
||||
onBackfillDiary: () => backfillDreamDiary(state),
|
||||
onResetDiary: () => resetDreamDiary(state),
|
||||
onResetGroundedShortTerm: () => resetGroundedShortTerm(state),
|
||||
onRequestUpdate: requestHostUpdate,
|
||||
})
|
||||
: nothing}
|
||||
|
||||
@@ -7,12 +7,64 @@ import { renderDreaming, setDreamSubTab, type DreamingProps } from "./dreaming.t
|
||||
function buildProps(overrides?: Partial<DreamingProps>): DreamingProps {
|
||||
return {
|
||||
active: true,
|
||||
shortTermCount: 47,
|
||||
groundedSignalCount: 9,
|
||||
totalSignalCount: 182,
|
||||
promotedCount: 12,
|
||||
phases: {
|
||||
light: { enabled: true, cron: "0 * * * *", nextRunAtMs: Date.parse("2026-04-05T11:30:00Z") },
|
||||
deep: { enabled: true, cron: "30 * * * *", nextRunAtMs: Date.parse("2026-04-05T12:00:00Z") },
|
||||
rem: { enabled: false, cron: "0 4 * * *" },
|
||||
},
|
||||
shortTermEntries: [
|
||||
{
|
||||
key: "memory:memory/2026-04-05.md:1:2",
|
||||
path: "memory/2026-04-05.md",
|
||||
startLine: 1,
|
||||
endLine: 2,
|
||||
snippet: "Emma prefers shorter, lower-pressure check-ins.",
|
||||
recallCount: 2,
|
||||
dailyCount: 1,
|
||||
groundedCount: 1,
|
||||
totalSignalCount: 3,
|
||||
lightHits: 1,
|
||||
remHits: 1,
|
||||
phaseHitCount: 2,
|
||||
},
|
||||
],
|
||||
signalEntries: [
|
||||
{
|
||||
key: "memory:memory/2026-04-05.md:1:2",
|
||||
path: "memory/2026-04-05.md",
|
||||
startLine: 1,
|
||||
endLine: 2,
|
||||
snippet: "Emma prefers shorter, lower-pressure check-ins.",
|
||||
recallCount: 2,
|
||||
dailyCount: 1,
|
||||
groundedCount: 1,
|
||||
totalSignalCount: 3,
|
||||
lightHits: 1,
|
||||
remHits: 1,
|
||||
phaseHitCount: 2,
|
||||
},
|
||||
],
|
||||
promotedEntries: [
|
||||
{
|
||||
key: "memory:memory/2026-04-04.md:4:5",
|
||||
path: "memory/2026-04-04.md",
|
||||
startLine: 4,
|
||||
endLine: 5,
|
||||
snippet: "Use the Happy Together calendar for flights.",
|
||||
recallCount: 3,
|
||||
dailyCount: 2,
|
||||
groundedCount: 4,
|
||||
totalSignalCount: 9,
|
||||
lightHits: 0,
|
||||
remHits: 0,
|
||||
phaseHitCount: 0,
|
||||
promotedAt: "2026-04-05T04:00:00.000Z",
|
||||
},
|
||||
],
|
||||
dreamingOf: null,
|
||||
nextCycle: "4:00 AM",
|
||||
timezone: "America/Los_Angeles",
|
||||
@@ -29,6 +81,7 @@ function buildProps(overrides?: Partial<DreamingProps>): DreamingProps {
|
||||
onRefreshDiary: () => {},
|
||||
onBackfillDiary: () => {},
|
||||
onResetDiary: () => {},
|
||||
onResetGroundedShortTerm: () => {},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -73,13 +126,14 @@ describe("dreaming view", () => {
|
||||
expect(container.querySelector(".dreams__phase--off")?.textContent).toContain("off");
|
||||
});
|
||||
|
||||
it("renders scene backfill and reset controls", () => {
|
||||
it("keeps maintenance controls out of the scene tab", () => {
|
||||
const container = renderInto(buildProps());
|
||||
const buttons = [...container.querySelectorAll("button")].map((node) =>
|
||||
node.textContent?.trim(),
|
||||
);
|
||||
expect(buttons).toContain("Backfill");
|
||||
expect(buttons).toContain("Reset");
|
||||
expect(buttons).not.toContain("Backfill");
|
||||
expect(buttons).not.toContain("Reset");
|
||||
expect(buttons).not.toContain("Clear Grounded");
|
||||
});
|
||||
|
||||
it("shows dream bubble when active", () => {
|
||||
@@ -131,9 +185,10 @@ describe("dreaming view", () => {
|
||||
it("renders sub-tab navigation", () => {
|
||||
const container = renderInto(buildProps());
|
||||
const tabs = container.querySelectorAll(".dreams__tab");
|
||||
expect(tabs.length).toBe(2);
|
||||
expect(tabs.length).toBe(3);
|
||||
expect(tabs[0]?.textContent).toContain("Scene");
|
||||
expect(tabs[1]?.textContent).toContain("Diary");
|
||||
expect(tabs[2]?.textContent).toContain("Advanced");
|
||||
});
|
||||
|
||||
it("renders dream diary with parsed entry on diary tab", () => {
|
||||
@@ -258,5 +313,24 @@ describe("dreaming view", () => {
|
||||
setDreamSubTab("scene");
|
||||
});
|
||||
|
||||
it("renders operator actions and evidence lists on the advanced tab", () => {
|
||||
setDreamSubTab("advanced");
|
||||
const container = renderInto(buildProps());
|
||||
expect(container.querySelector(".dreams-advanced__title")?.textContent).toContain(
|
||||
"Grounded Replay",
|
||||
);
|
||||
const buttons = [...container.querySelectorAll("button")].map((node) =>
|
||||
node.textContent?.trim(),
|
||||
);
|
||||
expect(buttons).toContain("Backfill");
|
||||
expect(buttons).toContain("Reset");
|
||||
expect(buttons).toContain("Clear Grounded");
|
||||
expect(container.querySelector(".dreams-advanced__summary-value")?.textContent).toBe("47");
|
||||
expect(container.querySelector(".dreams-advanced__item")?.textContent).toContain(
|
||||
"Emma prefers shorter",
|
||||
);
|
||||
setDreamSubTab("scene");
|
||||
});
|
||||
|
||||
// Toggle lives in the page header (app-render.ts), not inside the dreaming view.
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { html, nothing } from "lit";
|
||||
import { t } from "../../i18n/index.ts";
|
||||
import type { DreamingEntry } from "../controllers/dreaming.ts";
|
||||
|
||||
// ── Diary entry parser ─────────────────────────────────────────────────
|
||||
|
||||
@@ -89,12 +90,18 @@ type DreamingPhaseInfo = {
|
||||
|
||||
export type DreamingProps = {
|
||||
active: boolean;
|
||||
shortTermCount: number;
|
||||
groundedSignalCount: number;
|
||||
totalSignalCount: number;
|
||||
promotedCount: number;
|
||||
phases?: {
|
||||
light: DreamingPhaseInfo;
|
||||
deep: DreamingPhaseInfo;
|
||||
rem: DreamingPhaseInfo;
|
||||
};
|
||||
shortTermEntries: DreamingEntry[];
|
||||
signalEntries: DreamingEntry[];
|
||||
promotedEntries: DreamingEntry[];
|
||||
dreamingOf: string | null;
|
||||
nextCycle: string | null;
|
||||
timezone: string | null;
|
||||
@@ -110,6 +117,7 @@ export type DreamingProps = {
|
||||
onRefreshDiary: () => void;
|
||||
onBackfillDiary: () => void;
|
||||
onResetDiary: () => void;
|
||||
onResetGroundedShortTerm: () => void;
|
||||
onRequestUpdate?: () => void;
|
||||
};
|
||||
|
||||
@@ -145,7 +153,7 @@ const DREAM_SWAP_MS = 6_000;
|
||||
|
||||
// ── Sub-tab state ─────────────────────────────────────────────────────
|
||||
|
||||
type DreamSubTab = "scene" | "diary";
|
||||
type DreamSubTab = "scene" | "diary" | "advanced";
|
||||
let _subTab: DreamSubTab = "scene";
|
||||
|
||||
export function setDreamSubTab(tab: DreamSubTab): void {
|
||||
@@ -254,9 +262,22 @@ export function renderDreaming(props: DreamingProps) {
|
||||
>
|
||||
${t("dreaming.tabs.diary")}
|
||||
</button>
|
||||
<button
|
||||
class="dreams__tab ${_subTab === "advanced" ? "dreams__tab--active" : ""}"
|
||||
@click=${() => {
|
||||
_subTab = "advanced";
|
||||
props.onRequestUpdate?.();
|
||||
}}
|
||||
>
|
||||
${t("dreaming.tabs.advanced")}
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
${_subTab === "scene" ? renderScene(props, idle, dreamText) : renderDiarySection(props)}
|
||||
${_subTab === "scene"
|
||||
? renderScene(props, idle, dreamText)
|
||||
: _subTab === "diary"
|
||||
? renderDiarySection(props)
|
||||
: renderAdvancedSection(props)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -380,24 +401,175 @@ function renderScene(props: DreamingProps, idle: boolean, dreamText: string) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="dreams__actions">
|
||||
<button
|
||||
class="btn btn--subtle btn--sm"
|
||||
?disabled=${props.modeSaving || props.dreamDiaryActionLoading}
|
||||
@click=${() => props.onBackfillDiary()}
|
||||
>
|
||||
${props.dreamDiaryActionLoading
|
||||
? t("dreaming.scene.working")
|
||||
: t("dreaming.scene.backfill")}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn--subtle btn--sm"
|
||||
?disabled=${props.modeSaving || props.dreamDiaryActionLoading}
|
||||
@click=${() => props.onResetDiary()}
|
||||
>
|
||||
${t("dreaming.scene.reset")}
|
||||
</button>
|
||||
${props.statusError
|
||||
? html`<div class="dreams__controls-error">${props.statusError}</div>`
|
||||
: nothing}
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
function formatRange(path: string, startLine: number, endLine: number): string {
|
||||
return startLine === endLine ? `${path}:${startLine}` : `${path}:${startLine}-${endLine}`;
|
||||
}
|
||||
|
||||
function formatCompactDateTime(value: string): string {
|
||||
const parsed = Date.parse(value);
|
||||
if (!Number.isFinite(parsed)) {
|
||||
return value;
|
||||
}
|
||||
return new Date(parsed).toLocaleString([], {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
|
||||
function renderAdvancedEntryList(
|
||||
titleKey: string,
|
||||
emptyKey: string,
|
||||
entries: DreamingEntry[],
|
||||
meta: (entry: DreamingEntry) => string[],
|
||||
) {
|
||||
return html`
|
||||
<section class="dreams-advanced__section">
|
||||
<div class="dreams-advanced__section-header">
|
||||
<span class="dreams-advanced__section-title">${t(titleKey)}</span>
|
||||
<span class="dreams-advanced__section-count">${entries.length}</span>
|
||||
</div>
|
||||
${entries.length === 0
|
||||
? html`<div class="dreams-advanced__empty">${t(emptyKey)}</div>`
|
||||
: html`
|
||||
<div class="dreams-advanced__list">
|
||||
${entries.map(
|
||||
(entry) => html`
|
||||
<article class="dreams-advanced__item" data-entry-key=${entry.key}>
|
||||
<div class="dreams-advanced__snippet">${entry.snippet}</div>
|
||||
<div class="dreams-advanced__source">
|
||||
${formatRange(entry.path, entry.startLine, entry.endLine)}
|
||||
</div>
|
||||
<div class="dreams-advanced__meta">
|
||||
${meta(entry)
|
||||
.filter((part) => part.length > 0)
|
||||
.join(" · ")}
|
||||
</div>
|
||||
</article>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
`}
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderAdvancedSection(props: DreamingProps) {
|
||||
const groundedEntries = props.shortTermEntries.filter((entry) => entry.groundedCount > 0);
|
||||
|
||||
return html`
|
||||
<section class="dreams-advanced">
|
||||
<div class="dreams-advanced__header">
|
||||
<div class="dreams-advanced__intro">
|
||||
<span class="dreams-advanced__eyebrow">${t("dreaming.advanced.eyebrow")}</span>
|
||||
<h2 class="dreams-advanced__title">${t("dreaming.advanced.title")}</h2>
|
||||
<p class="dreams-advanced__description">${t("dreaming.advanced.description")}</p>
|
||||
</div>
|
||||
<div class="dreams-advanced__actions">
|
||||
<button
|
||||
class="btn btn--subtle btn--sm"
|
||||
?disabled=${props.modeSaving || props.dreamDiaryActionLoading}
|
||||
@click=${() => props.onBackfillDiary()}
|
||||
>
|
||||
${props.dreamDiaryActionLoading
|
||||
? t("dreaming.scene.working")
|
||||
: t("dreaming.scene.backfill")}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn--subtle btn--sm"
|
||||
?disabled=${props.modeSaving || props.dreamDiaryActionLoading}
|
||||
@click=${() => props.onResetDiary()}
|
||||
>
|
||||
${t("dreaming.scene.reset")}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn--subtle btn--sm"
|
||||
?disabled=${props.modeSaving || props.dreamDiaryActionLoading}
|
||||
@click=${() => props.onResetGroundedShortTerm()}
|
||||
>
|
||||
${t("dreaming.scene.clearGrounded")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dreams-advanced__summary">
|
||||
<div class="dreams-advanced__summary-card">
|
||||
<span class="dreams-advanced__summary-label">${t("dreaming.stats.shortTerm")}</span>
|
||||
<span class="dreams-advanced__summary-value">${props.shortTermCount}</span>
|
||||
</div>
|
||||
<div class="dreams-advanced__summary-card">
|
||||
<span class="dreams-advanced__summary-label">${t("dreaming.stats.grounded")}</span>
|
||||
<span class="dreams-advanced__summary-value">${props.groundedSignalCount}</span>
|
||||
</div>
|
||||
<div class="dreams-advanced__summary-card">
|
||||
<span class="dreams-advanced__summary-label">${t("dreaming.stats.signals")}</span>
|
||||
<span class="dreams-advanced__summary-value">${props.totalSignalCount}</span>
|
||||
</div>
|
||||
<div class="dreams-advanced__summary-card">
|
||||
<span class="dreams-advanced__summary-label">${t("dreaming.stats.promoted")}</span>
|
||||
<span class="dreams-advanced__summary-value">${props.promotedCount}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dreams-advanced__sections">
|
||||
${renderAdvancedEntryList(
|
||||
"dreaming.advanced.stagedTitle",
|
||||
"dreaming.advanced.emptyGrounded",
|
||||
groundedEntries,
|
||||
(entry) => [
|
||||
entry.groundedCount > 0
|
||||
? `${entry.groundedCount} ${t("dreaming.stats.grounded").toLowerCase()}`
|
||||
: "",
|
||||
entry.recallCount > 0 ? `${entry.recallCount} recall` : "",
|
||||
entry.dailyCount > 0 ? `${entry.dailyCount} daily` : "",
|
||||
],
|
||||
)}
|
||||
${renderAdvancedEntryList(
|
||||
"dreaming.advanced.shortTermTitle",
|
||||
"dreaming.advanced.emptyShortTerm",
|
||||
props.shortTermEntries,
|
||||
(entry) => [
|
||||
entry.recallCount > 0 ? `${entry.recallCount} recall` : "",
|
||||
entry.dailyCount > 0 ? `${entry.dailyCount} daily` : "",
|
||||
entry.groundedCount > 0
|
||||
? `${entry.groundedCount} ${t("dreaming.stats.grounded").toLowerCase()}`
|
||||
: "",
|
||||
entry.phaseHitCount > 0 ? `${entry.phaseHitCount} phase hit` : "",
|
||||
],
|
||||
)}
|
||||
${renderAdvancedEntryList(
|
||||
"dreaming.advanced.signalsTitle",
|
||||
"dreaming.advanced.emptySignals",
|
||||
props.signalEntries,
|
||||
(entry) => [
|
||||
`${entry.totalSignalCount} ${t("dreaming.stats.signals").toLowerCase()}`,
|
||||
entry.phaseHitCount > 0 ? `${entry.phaseHitCount} phase hit` : "",
|
||||
],
|
||||
)}
|
||||
${renderAdvancedEntryList(
|
||||
"dreaming.advanced.promotedTitle",
|
||||
"dreaming.advanced.emptyPromoted",
|
||||
props.promotedEntries,
|
||||
(entry) => [
|
||||
entry.promotedAt
|
||||
? `${t("dreaming.advanced.updatedPrefix")} ${formatCompactDateTime(entry.promotedAt)}`
|
||||
: "",
|
||||
entry.groundedCount > 0
|
||||
? `${entry.groundedCount} ${t("dreaming.stats.grounded").toLowerCase()}`
|
||||
: "",
|
||||
entry.totalSignalCount > 0
|
||||
? `${entry.totalSignalCount} ${t("dreaming.stats.signals").toLowerCase()}`
|
||||
: "",
|
||||
],
|
||||
)}
|
||||
</div>
|
||||
|
||||
${props.statusError
|
||||
|
||||
Reference in New Issue
Block a user