mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 00:05:49 +02:00
fix(ui): preserve Pi notice severity and compact summaries
Label error severity consistently and keep one strongest notice category in the collapsed activity summary while preserving the individual expanded messages. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
203343c9ea
commit
b8a59b259c
5 files changed
+47
-4
No files matched your search
@@ -235,6 +235,7 @@ describe("TaskChatProtocolActivityRow", () => {
|
||||
["info", "informational", "Provider update", "lucide-info"],
|
||||
["warning", "informational", "Warning", "lucide-triangle-alert"],
|
||||
["error", "failed", "Error", "lucide-triangle-alert"],
|
||||
["error", "informational", "Error", "lucide-triangle-alert"],
|
||||
["info", "failed", "Error", "lucide-triangle-alert"],
|
||||
] as const)("renders %s/%s notice severity without hiding its summary", (severity, status, label, icon) => {
|
||||
const summary = "Pi estimates this turn at $0.000617. Billing cost is unverified.";
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
protocolActivityIsRunning,
|
||||
protocolActivityLabel,
|
||||
protocolActivityPresentation,
|
||||
providerNoticeSeverity,
|
||||
} from "./task-chat-activity-presentation";
|
||||
|
||||
const COMPACT_RESEARCH_RESULT_LIMIT = 5;
|
||||
@@ -281,6 +282,7 @@ export function TaskChatProtocolActivityRow({ item }: { item: TaskChatProtocolIt
|
||||
if (!presentation) return null;
|
||||
if (item.surface === "provider_activity" && item.family === "provider_notice") {
|
||||
const NoticeIcon = presentation.icon;
|
||||
const severity = providerNoticeSeverity(item);
|
||||
const summary = item.summary
|
||||
?? item.details.find((entry) => entry.label === "Summary")?.value
|
||||
?? "The provider reported a notice without a message.";
|
||||
@@ -288,7 +290,7 @@ export function TaskChatProtocolActivityRow({ item }: { item: TaskChatProtocolIt
|
||||
<div className="flex min-w-0 flex-col gap-1.5 py-1 text-xs" data-testid="task-chat-protocol-activity-row" data-activity-family="provider_notice">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<NoticeIcon className="h-3.5 w-3.5 shrink-0" aria-hidden data-testid="task-chat-protocol-activity-icon" />
|
||||
<span className="font-medium">{item.status === "failed" ? "Error" : item.details.find(detail => detail.label === "Severity")?.value === "info" ? "Provider update" : "Warning"}</span>
|
||||
<span className="font-medium">{severity === "error" ? "Error" : severity === "info" ? "Provider update" : "Warning"}</span>
|
||||
</div>
|
||||
<p className="min-w-0 whitespace-pre-wrap break-words text-foreground">{summary}</p>
|
||||
{item.details.some(detail => detail.label !== "Summary") ? (
|
||||
|
||||
@@ -173,4 +173,17 @@ describe("completedActivitySummary", () => {
|
||||
}]).icon).toBe(AlertTriangle);
|
||||
});
|
||||
|
||||
it("groups distinct notices and keeps a later error visible beside actual work", () => {
|
||||
const notices = ["info", "info", "warning", "error"].map((severity, index) => ({
|
||||
...provider("provider_notice", "informational"),
|
||||
id: `notice-${index}`,
|
||||
summary: `Distinct provider message ${index}`,
|
||||
details: [{ label: "Severity", value: severity }],
|
||||
}));
|
||||
const result = completedActivitySummary([tool("bash"), ...notices]);
|
||||
expect(result.label).toBe("Provider error reported, ran commands");
|
||||
expect(result.fullLabel).toBe(result.label);
|
||||
expect(result.label).not.toContain("Distinct provider message");
|
||||
});
|
||||
|
||||
});
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
type ToolFamily,
|
||||
type ToolIcon,
|
||||
} from "./tool-taxonomy";
|
||||
import { protocolActivityPresentation } from "./task-chat-activity-presentation";
|
||||
import { protocolActivityPresentation, providerNoticeSeverity } from "./task-chat-activity-presentation";
|
||||
|
||||
type Activity = TaskChatActivityPhaseItem["items"][number];
|
||||
const labels: Record<ToolFamily, string> = {
|
||||
@@ -42,6 +42,12 @@ export function completedActivitySummary(items: Activity[]) {
|
||||
completed: boolean;
|
||||
order: number;
|
||||
}> = [];
|
||||
const notices: Array<{
|
||||
summary: string | undefined;
|
||||
severity: "info" | "warning" | "error";
|
||||
icon: ToolIcon;
|
||||
order: number;
|
||||
}> = [];
|
||||
for (const [order, item] of items.entries()) {
|
||||
if (item.kind === "tool") {
|
||||
const p = toolActivityPresentation({
|
||||
@@ -71,7 +77,7 @@ export function completedActivitySummary(items: Activity[]) {
|
||||
// Reuse only the normalized notice text already available in the row.
|
||||
const summary = item.summary?.trim()
|
||||
|| item.details.find((entry) => entry.label === "Summary")?.value.trim();
|
||||
add(summary || "Received a provider update", p.icon, order);
|
||||
notices.push({ summary, severity: providerNoticeSeverity(item), icon: p.icon, order });
|
||||
continue;
|
||||
}
|
||||
const family =
|
||||
@@ -100,6 +106,21 @@ export function completedActivitySummary(items: Activity[]) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (notices.length) {
|
||||
const strongest = notices.find((notice) => notice.severity === "error")
|
||||
?? notices.find((notice) => notice.severity === "warning")
|
||||
?? notices[0]!;
|
||||
const label = notices.length === 1
|
||||
? strongest.summary || "Received a provider update"
|
||||
: strongest.severity === "error"
|
||||
? "Provider error reported"
|
||||
: strongest.severity === "warning"
|
||||
? "Provider warning reported"
|
||||
: "Received provider updates";
|
||||
// One notice category leaves room for actual work. Keep warnings and errors
|
||||
// visible when the compact label truncates other activity categories.
|
||||
add(label, strongest.icon, strongest.severity === "info" ? notices[0]!.order : -1);
|
||||
}
|
||||
const completedFamilies = new Set(
|
||||
tools.filter((tool) => tool.completed).map((tool) => tool.family),
|
||||
);
|
||||
|
||||
@@ -41,6 +41,12 @@ export interface TaskChatActivityPresentation {
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
export function providerNoticeSeverity(item: TaskChatProviderActivityItem): "info" | "warning" | "error" {
|
||||
const severity = item.details.find((entry) => entry.label === "Severity")?.value;
|
||||
if (item.status === "failed" || severity === "error") return "error";
|
||||
return severity === "info" ? "info" : "warning";
|
||||
}
|
||||
|
||||
function providerDetail(item: TaskChatProviderActivityItem, ...labels: string[]): string | undefined {
|
||||
return item.details.find((entry) => labels.includes(entry.label))?.value;
|
||||
}
|
||||
@@ -136,7 +142,7 @@ export function providerActivityPresentation(item: TaskChatProviderActivityItem)
|
||||
case "wait":
|
||||
return { icon: Clock3, runningLabel: "Waiting", completedLabel: "Finished waiting", failedLabel: "Wait failed", interruptedLabel: "Wait stopped", detail };
|
||||
case "provider_notice":
|
||||
return { icon: item.status !== "failed" && providerDetail(item, "Severity") === "info" ? Info : AlertTriangle, runningLabel: "Provider notice", completedLabel: "Provider notice", failedLabel: "Provider error", interruptedLabel: "Provider notice", detail };
|
||||
return { icon: providerNoticeSeverity(item) === "info" ? Info : AlertTriangle, runningLabel: "Provider notice", completedLabel: "Provider notice", failedLabel: "Provider error", interruptedLabel: "Provider notice", detail };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user