mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-06 17:38:35 +00:00
fix(webui): align automation metadata with timestamps
This commit is contained in:
parent
f45436b61d
commit
faff0ac2fa
@ -368,6 +368,7 @@ export function MessageBubble({
|
|||||||
assistantTimestampLabel.length > 0
|
assistantTimestampLabel.length > 0
|
||||||
&& (!empty || hasReasoning || media.length > 0);
|
&& (!empty || hasReasoning || media.length > 0);
|
||||||
const assistantTimestampTitle = showAssistantTimestamp ? fmtDateTime(assistantTimestamp) : "";
|
const assistantTimestampTitle = showAssistantTimestamp ? fmtDateTime(assistantTimestamp) : "";
|
||||||
|
const showAutomationTrigger = showAssistantTimestamp && automationSourceLabel.length > 0;
|
||||||
const showAssistantFooterRow = showCopyButton || showForkButton || showAssistantTimestamp;
|
const showAssistantFooterRow = showCopyButton || showForkButton || showAssistantTimestamp;
|
||||||
const showAssistantFooterSlot =
|
const showAssistantFooterSlot =
|
||||||
message.role === "assistant"
|
message.role === "assistant"
|
||||||
@ -385,12 +386,6 @@ export function MessageBubble({
|
|||||||
<ThinkingState />
|
<ThinkingState />
|
||||||
) : empty && message.isStreaming ? null : (
|
) : empty && message.isStreaming ? null : (
|
||||||
<>
|
<>
|
||||||
{automationSourceLabel ? (
|
|
||||||
<AutomationSourceBadge
|
|
||||||
label={automationSourceLabel}
|
|
||||||
triggerLabel={automationTriggeredLabel}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<div data-assistant-selectable={message.isStreaming ? undefined : "true"}>
|
<div data-assistant-selectable={message.isStreaming ? undefined : "true"}>
|
||||||
{/* A mode switch rebuilds Streamdown's subtree and moves the scroll anchor. */}
|
{/* A mode switch rebuilds Streamdown's subtree and moves the scroll anchor. */}
|
||||||
<MarkdownText
|
<MarkdownText
|
||||||
@ -451,6 +446,12 @@ export function MessageBubble({
|
|||||||
{assistantTimestampLabel}
|
{assistantTimestampLabel}
|
||||||
</time>
|
</time>
|
||||||
) : null}
|
) : null}
|
||||||
|
{showAutomationTrigger ? (
|
||||||
|
<AutomationTriggerMeta
|
||||||
|
label={automationTriggeredLabel}
|
||||||
|
sourceLabel={automationSourceLabel}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
) : null}
|
) : null}
|
||||||
@ -476,22 +477,23 @@ function UserQuotedContext({ text, label }: { text: string; label: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AutomationSourceBadge({ label, triggerLabel }: { label: string; triggerLabel: string }) {
|
function AutomationTriggerMeta({ label, sourceLabel }: { label: string; sourceLabel: string }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<Tooltip>
|
||||||
className={cn(
|
<TooltipTrigger asChild>
|
||||||
"mb-2 inline-flex max-w-full items-center gap-1.5 rounded-full px-2 py-1",
|
<span
|
||||||
"border border-sky-500/15 bg-sky-500/[0.06]",
|
data-automation-trigger
|
||||||
"text-[11px] font-medium leading-none text-sky-700",
|
tabIndex={0}
|
||||||
"dark:border-sky-300/15 dark:bg-sky-300/[0.08] dark:text-sky-200/80",
|
className={cn(
|
||||||
)}
|
"shrink-0 cursor-help text-[11px] leading-none text-muted-foreground/70 tabular-nums",
|
||||||
title={triggerLabel}
|
"focus-visible:rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||||
>
|
)}
|
||||||
<Clock3 className="h-3 w-3 shrink-0" aria-hidden />
|
>
|
||||||
<span className="min-w-0 truncate">{label}</span>
|
{label}
|
||||||
<span className="text-current/45" aria-hidden>·</span>
|
</span>
|
||||||
<span className="shrink-0">{triggerLabel}</span>
|
</TooltipTrigger>
|
||||||
</div>
|
<TooltipContent side="top" align="center">{sourceLabel}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -455,20 +455,39 @@ describe("MessageBubble", () => {
|
|||||||
expect(screen.getByText(/not @krita/)).toBeInTheDocument();
|
expect(screen.getByText(/not @krita/)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders a lightweight automation source label for cron replies", () => {
|
it("places automation metadata after the timestamp and reveals its source on hover", async () => {
|
||||||
|
const completedAt = Date.UTC(2026, 6, 25, 12, 34, 56);
|
||||||
const message: UIMessage = {
|
const message: UIMessage = {
|
||||||
id: "a-cron",
|
id: "a-cron",
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
content: "Time to drink water.",
|
content: "Time to drink water.",
|
||||||
source: { kind: "cron", label: "drink water" },
|
source: { kind: "cron", label: "drink water" },
|
||||||
createdAt: Date.now(),
|
completedAt,
|
||||||
|
createdAt: completedAt - 1_000,
|
||||||
};
|
};
|
||||||
|
|
||||||
render(<MessageBubble message={message} />);
|
const { container } = render(<MessageBubble message={message} />);
|
||||||
|
|
||||||
expect(screen.getByText("drink water")).toBeInTheDocument();
|
const footer = container.querySelector("[data-assistant-footer]")!;
|
||||||
expect(screen.getByText("Triggered automatically")).toBeInTheDocument();
|
const timestamp = footer.querySelector("[data-message-timestamp]")!;
|
||||||
|
const trigger = footer.querySelector("[data-automation-trigger]")!;
|
||||||
|
|
||||||
|
expect(timestamp).toHaveTextContent(formatMessageEndTime(completedAt));
|
||||||
|
expect(trigger).toHaveTextContent("Triggered automatically");
|
||||||
|
expect(trigger.previousElementSibling).toBe(timestamp);
|
||||||
|
expect(trigger).toHaveClass(
|
||||||
|
"text-[11px]",
|
||||||
|
"leading-none",
|
||||||
|
"text-muted-foreground/70",
|
||||||
|
"tabular-nums",
|
||||||
|
);
|
||||||
|
expect(trigger.className).not.toMatch(/(?:^|\s)(?:border|bg-)/);
|
||||||
|
expect(trigger.querySelector("svg")).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText("drink water")).not.toBeInTheDocument();
|
||||||
expect(screen.getByText("Time to drink water.")).toBeInTheDocument();
|
expect(screen.getByText("Time to drink water.")).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.pointerMove(trigger);
|
||||||
|
expect(await screen.findByRole("tooltip")).toHaveTextContent("drink water");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders structured CLI app attachments even without the installed catalog", () => {
|
it("renders structured CLI app attachments even without the installed catalog", () => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user