Merge remote-tracking branch 'origin/main' into website-and-inspector-visuals

# Conflicts:
#	frontend/packages/inspector/src/components/chat/ChatMessages.tsx
#	frontend/packages/inspector/src/components/chat/messageUtils.tsx
This commit is contained in:
Nicholas Kissel 2026-02-11 23:05:06 -08:00
commit ed30b5f068
44 changed files with 883 additions and 383 deletions

View file

@ -1,6 +1,5 @@
import { useState } from "react";
import { getAvatarLabel, getMessageClass } from "./messageUtils";
import renderContentPart from "./renderContentPart";
import type { TimelineEntry } from "./types";
import { AlertTriangle, Settings, ChevronRight, ChevronDown } from "lucide-react";
@ -63,81 +62,98 @@ const ChatMessages = ({
);
}
// Other status messages - collapsible
// Other status messages - collapsible only if there's detail
const hasDetail = Boolean(entry.meta?.detail);
if (hasDetail) {
return (
<CollapsibleMessage
key={entry.id}
id={entry.id}
icon={isError ? <AlertTriangle size={14} className="error-icon" /> : <Settings size={14} className="system-icon" />}
label={title}
className={isError ? "error" : "system"}
>
<div className="part-body">{entry.meta?.detail}</div>
</CollapsibleMessage>
);
}
// No detail - simple non-collapsible message
return (
<div key={entry.id} className={`simple-status-message ${isError ? "error" : "system"}`}>
{isError ? <AlertTriangle size={14} className="error-icon" /> : <Settings size={14} className="system-icon" />}
<span>{title}</span>
</div>
);
}
if (entry.kind === "reasoning") {
return (
<CollapsibleMessage
key={entry.id}
id={entry.id}
icon={isError ? <AlertTriangle size={14} className="error-icon" /> : <Settings size={14} className="system-icon" />}
label={title}
className={isError ? "error" : "system"}
icon={<Settings size={14} className="system-icon" />}
label={`Reasoning${entry.reasoning?.visibility ? ` (${entry.reasoning.visibility})` : ""}`}
className="system"
>
{entry.meta?.detail && <div className="part-body">{entry.meta.detail}</div>}
<div className="part-body muted">{entry.reasoning?.text}</div>
</CollapsibleMessage>
);
}
const item = entry.item;
if (!item) return null;
const hasParts = (item.content ?? []).length > 0;
const isInProgress = item.status === "in_progress";
const isFailed = item.status === "failed";
const messageClass = getMessageClass(item);
const statusLabel = item.status !== "completed" ? item.status.replace("_", " ") : "";
const kindLabel = item.kind.replace("_", " ");
const isTool = messageClass === "tool";
if (entry.kind === "tool") {
const isComplete = entry.toolStatus === "completed" || entry.toolStatus === "failed";
const isFailed = entry.toolStatus === "failed";
const statusLabel = entry.toolStatus && entry.toolStatus !== "completed"
? entry.toolStatus.replace("_", " ")
: "";
// Tool results - collapsible
if (isTool) {
return (
<CollapsibleMessage
key={entry.id}
id={entry.id}
icon={<span className="tool-icon">T</span>}
label={`${kindLabel}${statusLabel ? ` (${statusLabel})` : ""}`}
className="tool"
label={`${entry.toolName ?? "tool"}${statusLabel ? ` (${statusLabel})` : ""}`}
className={`tool${isFailed ? " error" : ""}`}
>
{hasParts ? (
(item.content ?? []).map(renderContentPart)
) : entry.deltaText ? (
<span>{entry.deltaText}</span>
) : (
<span className="muted">No content.</span>
)}
</CollapsibleMessage>
);
}
return (
<div key={entry.id} className={`message ${messageClass} ${isFailed ? "error no-avatar" : ""}`}>
{!isFailed && <div className="avatar">{getAvatarLabel(messageClass)}</div>}
<div className="message-content">
{(item.kind !== "message" || item.status !== "completed") && (
<div className="message-meta">
{isFailed && <AlertTriangle size={14} className="error-icon" />}
<span>{kindLabel}</span>
{statusLabel && (
<span className={`pill ${item.status === "failed" ? "danger" : "accent"}`}>
{statusLabel}
</span>
)}
{entry.toolInput && (
<div className="part">
<div className="part-title">input</div>
<pre className="code-block">{entry.toolInput}</pre>
</div>
)}
{hasParts ? (
(item.content ?? []).map(renderContentPart)
) : entry.deltaText ? (
<span>
{entry.deltaText}
{isInProgress && <span className="cursor" />}
</span>
) : isInProgress ? (
{isComplete && entry.toolOutput && (
<div className="part">
<div className="part-title">output</div>
<pre className="code-block">{entry.toolOutput}</pre>
</div>
)}
{!isComplete && !entry.toolInput && (
<span className="thinking-indicator">
<span className="thinking-dot" />
<span className="thinking-dot" />
<span className="thinking-dot" />
</span>
)}
</CollapsibleMessage>
);
}
// Regular message
const messageClass = getMessageClass(entry);
return (
<div key={entry.id} className={`message ${messageClass}`}>
<div className="avatar">{getAvatarLabel(messageClass)}</div>
<div className="message-content">
{entry.text ? (
<div className="part-body">{entry.text}</div>
) : (
<span className="muted">No content yet.</span>
<span className="thinking-indicator">
<span className="thinking-dot" />
<span className="thinking-dot" />
<span className="thinking-dot" />
</span>
)}
</div>
</div>

View file

@ -1,13 +1,12 @@
import type { UniversalItem } from "sandbox-agent";
import type { TimelineEntry } from "./types";
import { Settings, AlertTriangle, User } from "lucide-react";
import type { ReactNode } from "react";
export const getMessageClass = (item: UniversalItem) => {
if (item.kind === "tool_call" || item.kind === "tool_result") return "tool";
if (item.kind === "system" || item.kind === "status") return "system";
if (item.role === "user") return "user";
if (item.role === "tool") return "tool";
if (item.role === "system") return "system";
export const getMessageClass = (entry: TimelineEntry) => {
if (entry.kind === "tool") return "tool";
if (entry.kind === "meta") return entry.meta?.severity === "error" ? "error" : "system";
if (entry.kind === "reasoning") return "assistant";
if (entry.role === "user") return "user";
return "assistant";
};
@ -16,5 +15,5 @@ export const getAvatarLabel = (messageClass: string): ReactNode => {
if (messageClass === "tool") return "T";
if (messageClass === "system") return <Settings size={14} />;
if (messageClass === "error") return <AlertTriangle size={14} />;
return "AI";
return <span className="ai-label">AI</span>;
};