+
+
+
+
+
+
+
+ setVisibleRoles(checked ? new Set(allRoles) : new Set())
+ }
+ >
+ Show all messages
+
+
+ {(
+ [
+ ["system", "System"],
+ ["user", "User"],
+ ["assistant", "Assistant"],
+ ["tool", "Tool"],
+ ["reasoning", "Reasoning"],
+ ] as [MessageRole, string][]
+ ).map(([role, label]) => (
+
+ setVisibleRoles((prev) => {
+ const next = new Set(prev);
+ checked ? next.add(role) : next.delete(role);
+ return next;
+ })
+ }
+ >
+
+ {label}
+
+ ))}
+
+ setVisibleRoles(new Set())}
+ className="text-muted-foreground justify-center text-[12px]"
+ >
+ Clear all
+
+
+
+
{(log.ocr_input || log.ocr_output) && (
)}
@@ -1540,7 +1662,12 @@ export function LogDetailView({
{!isPassthrough && ((log.input_history && log.input_history.length > 0) ||
(log.output_message && !log.error_details?.error.message)) && (
- {log.input_history?.map((message, index) => {
+ {(visibleRoles.size < allRoles.length
+ ? log.input_history?.filter((m) =>
+ visibleRoles.has(((m.role as string) || "user") as MessageRole)
+ )
+ : log.input_history
+ )?.map((message, index) => {
const role = ((message.role as string) ||
"user") as MessageRole;
const text = extractMessageText(message);
@@ -1611,6 +1738,7 @@ export function LogDetailView({
})}
{log.output_message &&
!log.error_details?.error.message &&
+ visibleRoles.has("assistant") &&
(() => {
const text = extractMessageText(log.output_message);
const lineCount = text ? text.split("\n").length : 0;
@@ -1639,11 +1767,17 @@ export function LogDetailView({
)}
{(() => {
- const inputMsgs = log.responses_input_history ?? [];
- const outputMsgs =
+ const rawInput = log.responses_input_history ?? [];
+ const inputMsgs = visibleRoles.size < allRoles.length
+ ? rawInput.filter((m) => visibleRoles.has(getResponsesRole(m)))
+ : rawInput;
+ const rawOutput =
log.status !== "processing" && !log.error_details?.error.message
? (log.responses_output ?? [])
: [];
+ const outputMsgs = visibleRoles.size < allRoles.length
+ ? rawOutput.filter((m) => visibleRoles.has(getResponsesRole(m)))
+ : rawOutput;
const all: ResponsesMessage[] = [...inputMsgs, ...outputMsgs];
if (all.length === 0) return null;
return (
@@ -1953,44 +2087,7 @@ export function LogDetailView({
)}
{log.routing_engine_logs && (
-
log.routing_engine_logs || ""}
- >
-
- {log.routing_engine_logs
- .split("\n")
- .filter((l) => l.trim())
- .map((line, i) => {
- const m = line.match(
- /^\[(\d+)\]\s+\[([^\]]+)\]\s+-\s+(.*)$/,
- );
- const ts = m ? Number(m[1]) : null;
- const scope = m ? m[2] : null;
- const message = m ? m[3] : line;
- return (
-
- {ts != null ? (
-
- {format(new Date(ts), "HH:mm:ss.SSS")}
-
- ) : null}
- {scope ? (
-
- {scope}
-
- ) : null}
-
- {message}
-
-
- );
- })}
-
-
+
)}
{!log.attempt_trail?.length && !log.routing_engine_logs && (
diff --git a/ui/components/sidebar.tsx b/ui/components/sidebar.tsx
index 469834a065..62b361b03f 100644
--- a/ui/components/sidebar.tsx
+++ b/ui/components/sidebar.tsx
@@ -167,6 +167,8 @@ const getSidebarItemHref = (item: Pick
) => {
return item.queryParam ? `${item.url}?tab=${item.queryParam}` : item.url;
};
+const slug = (s: string) => s.toLowerCase().replace(/\s+/g, "-");
+
const TIME_FILTER_PAGES = new Set(["/workspace/dashboard", "/workspace/logs", "/workspace/mcp-logs"]);
const SidebarItemView = ({
@@ -194,6 +196,24 @@ const SidebarItemView = ({
expandSidebar: () => void;
highlightedUrl?: string;
}) => {
+ const [flyoutOpen, setFlyoutOpen] = useState(false);
+ const flyoutCloseTimer = useRef | null>(null);
+ const openFlyout = () => {
+ if (flyoutCloseTimer.current) clearTimeout(flyoutCloseTimer.current);
+ setFlyoutOpen(true);
+ };
+ const closeFlyout = () => {
+ if (flyoutCloseTimer.current) clearTimeout(flyoutCloseTimer.current);
+ flyoutCloseTimer.current = setTimeout(() => {
+ setFlyoutOpen(false);
+ flyoutCloseTimer.current = null;
+ }, 80);
+ };
+ useEffect(() => {
+ return () => {
+ if (flyoutCloseTimer.current) clearTimeout(flyoutCloseTimer.current);
+ };
+ }, []);
const hasSubItems = "subItems" in item && item.subItems && item.subItems.length > 0;
const isRouteMatch = (url: string) => {
if (url === "/workspace/custom-pricing") return pathname === url;
@@ -266,7 +286,7 @@ const SidebarItemView = ({
let menuButton: React.ReactNode;
if (hasSubItems) {
menuButton = (
-
+
{innerContent}
);
@@ -296,7 +316,72 @@ const SidebarItemView = ({
return (
- {menuButton}
+ {isSidebarCollapsed && hasSubItems ? (
+
+
+ {menuButton}
+
+
+ {item.title}
+ {item.subItems?.map((subItem) => {
+ const href = getSidebarItemHref(subItem);
+ const isSubItemActive = subItem.queryParam ? pathname === subItem.url : pathname.startsWith(subItem.url);
+ const SubItemIcon = subItem.icon;
+ const subSlug = slug(subItem.title);
+ const inner = (
+
+ {SubItemIcon && (
+
+ )}
+
+ {subItem.title}
+
+ {subItem.tag && (
+
+ {subItem.tag}
+
+ )}
+
+ );
+ return (
+ setFlyoutOpen(false)}
+ >
+ {subItem.hasAccess === false ? (
+
+ {inner}
+
+ ) : (
+
+ {inner}
+
+ )}
+
+ );
+ })}
+
+
+ ) : (
+ menuButton
+ )}
{hasSubItems && isExpanded && (
{item.subItems?.map((subItem: SidebarItem) => {