From 2ce87a3d623e0cf072308554dc522ffe2440a997 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Fri, 30 Jan 2026 18:17:45 -0800 Subject: [PATCH] new utils --- .../LogDetailsDrawer/clipboardUtils.ts | 43 +++++++++ .../view_logs/LogDetailsDrawer/constants.ts | 44 +++++++++ .../view_logs/LogDetailsDrawer/utils.ts | 93 +++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/clipboardUtils.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/constants.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/utils.ts diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/clipboardUtils.ts b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/clipboardUtils.ts new file mode 100644 index 0000000000..6aae95bb72 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/clipboardUtils.ts @@ -0,0 +1,43 @@ +import { message } from "antd"; +import { MESSAGE_COPY_SUCCESS } from "./constants"; + +/** + * Copies text to clipboard with fallback for non-secure contexts. + * Shows success/error message to user. + * + * @param text - Text to copy to clipboard + * @param label - Label for the copied content (e.g., "Request", "Metadata") + * @returns Promise - true if copy succeeded, false otherwise + */ +export async function copyToClipboard(text: string, label: string): Promise { + try { + // Try modern clipboard API first + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(text); + message.success(`${label} ${MESSAGE_COPY_SUCCESS}`); + return true; + } else { + // Fallback for non-secure contexts (like 0.0.0.0) + const textArea = document.createElement("textarea"); + textArea.value = text; + textArea.style.position = "fixed"; + textArea.style.opacity = "0"; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + const successful = document.execCommand("copy"); + document.body.removeChild(textArea); + + if (!successful) { + throw new Error("execCommand failed"); + } + message.success(`${label} ${MESSAGE_COPY_SUCCESS}`); + return true; + } + } catch (error) { + console.error("Copy failed:", error); + message.error(`Failed to copy ${label}`); + return false; + } +} diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/constants.ts b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/constants.ts new file mode 100644 index 0000000000..91f5ff8f11 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/constants.ts @@ -0,0 +1,44 @@ +// Drawer configuration constants +export const DRAWER_WIDTH = "60%"; +export const DRAWER_HEADER_PADDING = "16px 24px"; +export const DRAWER_CONTENT_PADDING = "24px"; + +// Truncation and display limits +export const DEFAULT_MAX_WIDTH = 180; +export const API_BASE_MAX_WIDTH = 200; +export const JSON_MAX_HEIGHT = 400; +export const METADATA_MAX_HEIGHT = 300; + +// Tab keys (kept for backwards compatibility if needed) +export const TAB_REQUEST = "request" as const; +export const TAB_RESPONSE = "response" as const; + +// Keyboard shortcuts +export const KEY_ESCAPE = "Escape"; +export const KEY_J_LOWER = "j"; +export const KEY_J_UPPER = "J"; +export const KEY_K_LOWER = "k"; +export const KEY_K_UPPER = "K"; + +// Typography +export const FONT_FAMILY_MONO = "monospace"; +export const FONT_SIZE_SMALL = 12; +export const FONT_SIZE_MEDIUM = 13; +export const FONT_SIZE_HEADER = 16; + +// Colors +export const COLOR_BORDER = "#f0f0f0"; +export const COLOR_BACKGROUND = "#fff"; +export const COLOR_SECONDARY = "#8c8c8c"; +export const COLOR_BG_LIGHT = "#fafafa"; + +// Spacing +export const SPACING_SMALL = 4; +export const SPACING_MEDIUM = 8; +export const SPACING_LARGE = 12; +export const SPACING_XLARGE = 16; +export const SPACING_XXLARGE = 24; + +// Messages +export const MESSAGE_COPY_SUCCESS = "copied to clipboard"; +export const MESSAGE_REQUEST_ID_COPIED = "Request ID copied to clipboard"; diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/utils.ts b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/utils.ts new file mode 100644 index 0000000000..61301cf5b5 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/utils.ts @@ -0,0 +1,93 @@ +/** + * Utility functions for LogDetailsDrawer component. + * These functions handle data formatting, validation, and guardrail calculations. + */ + +/** + * Formats data for display. If input is a string, attempts to parse as JSON. + * @param input - Data to format (string or object) + * @returns Parsed JSON object or original input + */ +export function formatData(input: any) { + if (typeof input === "string") { + try { + return JSON.parse(input); + } catch { + return input; + } + } + return input; +} + +/** + * Checks if messages array/object contains data. + * @param messages - Messages to check + * @returns True if messages exist and have content + */ +export function checkHasMessages(messages: any): boolean { + if (!messages) return false; + if (Array.isArray(messages)) return messages.length > 0; + if (typeof messages === "object") return Object.keys(messages).length > 0; + return false; +} + +/** + * Checks if response object contains data. + * @param response - Response to check + * @returns True if response exists and has content + */ +export function checkHasResponse(response: any): boolean { + if (!response) return false; + return Object.keys(formatData(response)).length > 0; +} + +/** + * Normalizes guardrail information into an array. + * @param guardrailInfo - Guardrail data (may be array, object, or null) + * @returns Array of guardrail entries + */ +export function normalizeGuardrailEntries(guardrailInfo: any): any[] { + if (Array.isArray(guardrailInfo)) return guardrailInfo; + if (guardrailInfo) return [guardrailInfo]; + return []; +} + +/** + * Calculates total number of masked entities across all guardrail entries. + * @param entries - Array of guardrail entries + * @returns Total count of masked entities + */ +export function calculateTotalMaskedEntities(entries: any[]): number { + return entries.reduce((sum, entry) => { + const maskedCounts = entry?.masked_entity_count; + if (!maskedCounts) return sum; + return ( + sum + + Object.values(maskedCounts).reduce((acc, count) => (typeof count === "number" ? acc + count : acc), 0) + ); + }, 0); +} + +/** + * Gets a display label for guardrail(s). + * @param entries - Array of guardrail entries + * @returns Display string for guardrail label + */ +export function getGuardrailLabel(entries: any[]): string { + if (entries.length === 0) return "-"; + if (entries.length === 1) return entries[0]?.guardrail_name ?? "-"; + return `${entries.length} guardrails`; +} + +/** + * Checks if vector store data exists in metadata. + * @param metadata - Metadata object to check + * @returns True if vector store data exists and is non-empty + */ +export function checkHasVectorStoreData(metadata: Record): boolean { + return ( + metadata.vector_store_request_metadata && + Array.isArray(metadata.vector_store_request_metadata) && + metadata.vector_store_request_metadata.length > 0 + ); +}