diff --git a/README.md b/README.md index c165a6b0..62fd657d 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # `ell` [WIP, unreleased, experimental] -[![](https://dcbadge.limes.pink/api/server/Yqyga6Qr?style=flat)](https://discord.gg/Yqyga6Qr) -[![](https://dcbadge.limes.pink/api/server/Yqyga6Qr?style=flat)](https://discord.gg/Yqyga6Qr) -[![](https://dcbadge.limes.pink/api/server/Yqyga6Qr?style=flat)](https://discord.gg/Yqyga6Qr) -[![](https://dcbadge.limes.pink/api/server/Yqyga6Qr?style=flat)](https://discord.gg/Yqyga6Qr) +[![](https://dcbadge.limes.pink/api/server/vWntgU52Xb?style=flat)](https://discord.gg/vWntgU52Xb) +[![](https://dcbadge.limes.pink/api/server/vWntgU52Xb?style=flat)](https://discord.gg/vWntgU52Xb) +[![](https://dcbadge.limes.pink/api/server/vWntgU52Xb?style=flat)](https://discord.gg/vWntgU52Xb) +[![](https://dcbadge.limes.pink/api/server/vWntgU52Xb?style=flat)](https://discord.gg/vWntgU52Xb) > **IMPORTANT**: This repository is currently pre-v1.0, highly experimental, and not yet packaged for general use. It contains numerous bugs, and the schemas are subject to frequent changes. While we welcome contributions, please be aware that submitting pull requests at this stage is at your own discretion, as the codebase is rapidly evolving. -> **[JOIN THE DISCORD](https://discord.gg/Yqyga6Qr)** We are developing this in public and I want all hands on deck regarding design decisions! Join us at [https://discord.gg/Yqyga6Qr](https://discord.gg/Yqyga6Qr) +> **[JOIN THE DISCORD](https://discord.gg/vWntgU52Xb)** We are developing this in public and I want all hands on deck regarding design decisions! Join us at [https://discord.gg/vWntgU52Xb](https://discord.gg/vWntgU52Xb) # What is `ell`? diff --git a/ell-studio/src/App.css b/ell-studio/src/App.css deleted file mode 100644 index 74b5e053..00000000 --- a/ell-studio/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/ell-studio/src/components/HierarchicalTable.js b/ell-studio/src/components/HierarchicalTable.js index cb0b2902..48fee663 100644 --- a/ell-studio/src/components/HierarchicalTable.js +++ b/ell-studio/src/components/HierarchicalTable.js @@ -2,6 +2,8 @@ import React, { useMemo, useRef, useEffect, useState, useCallback } from 'react' import { FiChevronDown, FiArrowUp, FiArrowDown, FiChevronLeft, FiChevronRight, FiChevronsLeft, FiChevronsRight } from 'react-icons/fi'; import { HierarchicalTableProvider, useHierarchicalTable } from './HierarchicalTableContext'; import { Checkbox } from "components/common/Checkbox" +import { debounce } from 'lodash'; + // Update the SmoothLine component const SmoothLine = ({ index, startX, startY, endX: endXPreprocess, special, endY, color, animated, opacity, offset }) => { const endX = endXPreprocess; @@ -83,34 +85,44 @@ const TableRow = ({ item, schema, level = 0, onRowClick, columnWidths, updateWid }, [isNew]); const primaryColumnRef = useRef(null); - + useEffect(() => { if (!primaryColumnRef.current) return; + const table = primaryColumnRef.current.closest('table'); + let tableRect = table.getBoundingClientRect(); + const updatePosition = () => { - const tableRect = primaryColumnRef.current.closest('table').getBoundingClientRect(); - const rowRect = primaryColumnRef.current.getBoundingClientRect(); - const relativeX = rowRect.left - tableRect.left; - const relativeY = rowRect.top - tableRect.top + rowRect.height / 2; - setRowRef(item.id, { id: item.id, x: relativeX, y: relativeY, visible: true }); + requestAnimationFrame(() => { + if (!primaryColumnRef.current) return; + const rowRect = primaryColumnRef.current.getBoundingClientRect(); + const relativeX = rowRect.left - tableRect.left; + const relativeY = rowRect.top - tableRect.top + rowRect.height / 2; + setRowRef(item.id, { id: item.id, x: relativeX, y: relativeY, visible: true }); + }); }; - // Initial position update - updatePosition(); + const debouncedUpdatePosition = debounce(updatePosition, 100); + + // Shared ResizeObserver + const resizeObserver = new ResizeObserver(() => { + tableRect = table.getBoundingClientRect(); // Update tableRect + debouncedUpdatePosition(); + }); - // Create a ResizeObserver - const resizeObserver = new ResizeObserver(updatePosition); + // Observe only the table + resizeObserver.observe(table); - // Observe both the row and the table - resizeObserver.observe(primaryColumnRef.current); - resizeObserver.observe(primaryColumnRef.current.closest('table')); + // Initial position update + updatePosition(); // Clean up return () => { setRowRef(item.id, {visible: false}); resizeObserver.disconnect(); + debouncedUpdatePosition.cancel(); }; - }, [item.id, setRowRef, sortedData.length, expandedRows, linkColumn]); + }, [item.id, setRowRef]); return ( diff --git a/ell-studio/src/components/LMPDetailsSidePanel.js b/ell-studio/src/components/LMPDetailsSidePanel.js index 9c4f2660..474e6454 100644 --- a/ell-studio/src/components/LMPDetailsSidePanel.js +++ b/ell-studio/src/components/LMPDetailsSidePanel.js @@ -1,30 +1,21 @@ -import React, { useState, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { Link } from 'react-router-dom'; -import { FiClock, FiTag, FiGitCommit, FiZap, FiHash, FiCalendar } from 'react-icons/fi'; +import { FiClock, FiTag, FiZap, FiHash, FiChevronRight, FiCode } from 'react-icons/fi'; import { getTimeAgo } from '../utils/lmpUtils'; import VersionBadge from './VersionBadge'; -import MetricChart from './MetricChart'; import { useInvocationsFromLMP } from '../hooks/useBackend'; import { LMPCardTitle } from './depgraph/LMPCardTitle'; -import { subDays, format, addDays, endOfMonth } from 'date-fns'; -import { ScrollArea } from './common/ScrollArea'; - -function StatItem({ icon: Icon, label, value }) { - return ( -
- {label} - {value} -
- ); -} +import { format } from 'date-fns'; +import SidePanel from './common/SidePanel'; +import MetricChart from './MetricChart'; +import { motion } from 'framer-motion'; +import {Card} from './common/Card'; function LMPDetailsSidePanel({ lmp, uses, versionHistory }) { - // TODO: update this for all versions aswell.. const { data: invocations } = useInvocationsFromLMP(lmp.name, lmp.lmp_id, 0, 100); const chartData = useMemo(() => { if (!invocations || invocations.length === 0) return []; - return invocations .map(inv => ({ date: new Date(inv.created_at), @@ -42,101 +33,128 @@ function LMPDetailsSidePanel({ lmp, uses, versionHistory }) { }, [invocations]); return ( - // -
-

Details

-
-
-
-

Version

- -
- - - - + + +
+
+

Version Info

+
- - {lmp.lm_kwargs && ( -
-

LM Keywords

-
-                {JSON.stringify(lmp.lm_kwargs, null, 2)}
-              
+
+
+ + Created:
- )} +
{getTimeAgo(new Date(lmp.created_at))}
+
+ + Is LMP: +
+
{lmp.is_lm ? 'Yes' : 'No'}
+
+ + Invocations: +
+
{totalInvocations}
+
+ + Avg. Latency: +
+
{avgLatency.toFixed(2)}ms
+
+
-
-

Uses

- {uses && uses.length > 0 ? ( -
    - {uses.filter(use => !!use).map((use) => ( -
  • - - - -
  • - ))} -
- ) : ( -

No dependencies

- )} + {lmp.lm_kwargs && ( +
+

+ LM Keywords +

+
+              {JSON.stringify(lmp.lm_kwargs, null, 2)}
+            
+ )} - +
+

Uses

+ {uses && uses.length > 0 ? ( +
    + {uses.filter(use => !!use).map((use) => ( + + + + + + + + ))} +
+ ) : ( +

No dependencies

+ )} +
- + -
-

Version History

-
- {versionHistory.map((version, index) => ( - -
- - v{versionHistory.length - index} - - - - {format(new Date(version.created_at), 'MMM d, yyyy')} - + +{/* +
+

Version History

+
+ {versionHistory.map((version, index) => ( + + +
+
+ v{versionHistory.length - index} + + {format(new Date(version.created_at), 'MMM d, yyyy HH:mm')} + +
+
{version.commit_message && ( -

- +

{version.commit_message}

)} - ))} -
+ + ))}
-
-
- // +
*/} + + ); } diff --git a/ell-studio/src/components/MetricChart.js b/ell-studio/src/components/MetricChart.js index 977baeb2..3d735b75 100644 --- a/ell-studio/src/components/MetricChart.js +++ b/ell-studio/src/components/MetricChart.js @@ -8,22 +8,17 @@ import { ResponsiveContainer, CartesianGrid, Brush, - Legend, } from "recharts"; import { format, differenceInDays, differenceInHours, - differenceInMinutes, startOfMinute, - endOfMinute, eachMinuteOfInterval, eachHourOfInterval, eachDayOfInterval, startOfHour, - endOfHour, startOfDay, - endOfDay, addMinutes, getMinutes, subMonths, @@ -31,7 +26,7 @@ import { subHours, } from "date-fns"; -function MetricChart({ rawData, dataKey, color, title, yAxisLabel, aggregation="sum" }) { +function MetricChart({ rawData, dataKey, color, yAxisLabel, aggregation="sum", title }) { const [dateRange, setDateRange] = useState(null); const [selectedTimeRange, setSelectedTimeRange] = useState("all"); @@ -43,23 +38,16 @@ function MetricChart({ rawData, dataKey, color, title, yAxisLabel, aggregation=" { value: "12h", label: "Last 12 Hours" }, { value: "1h", label: "Last Hour" }, ].filter((range) => { - if(!rawData) - return true; - else { - // if the range extends beyond the available data, don't show it - const earliestDate = new Date(rawData[0]?.date); - if(range.value === "all") - return true; - else { - const start = range.value === "1m" ? subMonths(new Date(), 1) : - range.value === "7d" ? subDays(new Date(), 7) : - range.value === "24h" ? subHours(new Date(), 24) : - range.value === "12h" ? subHours(new Date(), 12) : - range.value === "1h" ? subHours(new Date(), 1) : - new Date(); // Default case, shouldn't occur - return start >= earliestDate; - } - } + if(!rawData) return true; + const earliestDate = new Date(rawData[0]?.date); + if(range.value === "all") return true; + const start = range.value === "1m" ? subMonths(new Date(), 1) : + range.value === "7d" ? subDays(new Date(), 7) : + range.value === "24h" ? subHours(new Date(), 24) : + range.value === "12h" ? subHours(new Date(), 12) : + range.value === "1h" ? subHours(new Date(), 1) : + new Date(); + return start >= earliestDate; }), [rawData]); useEffect(() => { @@ -68,24 +56,12 @@ function MetricChart({ rawData, dataKey, color, title, yAxisLabel, aggregation=" let start = rawData[0]?.date; switch (selectedTimeRange) { - case "1m": - start = subMonths(latestNow, 1); - break; - case "7d": - start = subDays(latestNow, 7); - break; - case "24h": - start = subHours(latestNow, 24); - break; - case "12h": - start = subHours(latestNow, 12); - break; - case "1h": - start = subHours(latestNow, 1); - break; - default: - // "all" - use the earliest date in rawData - break; + case "1m": start = subMonths(latestNow, 1); break; + case "7d": start = subDays(latestNow, 7); break; + case "24h": start = subHours(latestNow, 24); break; + case "12h": start = subHours(latestNow, 12); break; + case "1h": start = subHours(latestNow, 1); break; + default: break; } setDateRange({ @@ -95,19 +71,16 @@ function MetricChart({ rawData, dataKey, color, title, yAxisLabel, aggregation=" } }, [rawData, selectedTimeRange]); - // Determine aggregation based on zoom const aggregatedData = useMemo(() => { if (!rawData?.length || !dateRange) return []; - const zoomStart = dateRange.start; + const zoomStart = dateRange.start; const zoomEnd = dateRange.end; const daysDiff = differenceInDays(zoomEnd, zoomStart); const hoursDiff = differenceInHours(zoomEnd, zoomStart); - const minutesDiff = differenceInMinutes(zoomEnd, zoomStart); - let aggregationInterval; - let aggregationFunction; + let aggregationInterval, aggregationFunction; if (daysDiff > 30) { aggregationInterval = eachDayOfInterval; @@ -157,7 +130,6 @@ function MetricChart({ rawData, dataKey, color, title, yAxisLabel, aggregation=" ); }, [rawData, dateRange, dataKey, aggregation]); - // Memoize formatting functions const formatXAxis = useCallback( (tickItem) => { const date = new Date(tickItem); @@ -170,21 +142,18 @@ function MetricChart({ rawData, dataKey, color, title, yAxisLabel, aggregation=" ); const formatTooltip = useCallback( - (value, name, props) => { - return [`${value} ${yAxisLabel || title.toLowerCase()}`, title]; + (value) => { + return [`${value} ${yAxisLabel || ''}`, '']; }, - [yAxisLabel, title] + [yAxisLabel] ); - const handleZoom = (domain) => { - }; - return ( -
-
-

{title}

+
+
+

{title}

-
+
- + - + format(new Date(label), "PPpp")} formatter={formatTooltip} /> - diff --git a/ell-studio/src/components/Sidebar.js b/ell-studio/src/components/Sidebar.js index d3e1578f..80ad504d 100644 --- a/ell-studio/src/components/Sidebar.js +++ b/ell-studio/src/components/Sidebar.js @@ -12,7 +12,7 @@ const Sidebar = () => { to={to} className={({ isActive }) => ` group flex items-center py-3 px-4 rounded-lg transition-all duration-200 - ${isActive ? 'bg-blue-500/10 text-blue-500' : 'text-gray-400 hover:text-white'} + ${isActive ? 'bg-accent text-accent-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-accent/50'} `} > @@ -20,7 +20,7 @@ const Sidebar = () => { {label} )} {!isExpanded && ( -
+
{label}
)} @@ -31,7 +31,7 @@ const Sidebar = () => {
ell-studio Logo @@ -50,9 +50,9 @@ const Sidebar = () => { diff --git a/ell-studio/src/components/common/MetricCard.js b/ell-studio/src/components/common/MetricCard.js new file mode 100644 index 00000000..10d29b92 --- /dev/null +++ b/ell-studio/src/components/common/MetricCard.js @@ -0,0 +1,17 @@ +import React from 'react'; +import MetricChart from '../MetricChart'; + +const MetricCard = ({ title, rawData, dataKey, color, yAxisLabel, aggregation }) => ( +
+ +
+); + +export default MetricCard; \ No newline at end of file diff --git a/ell-studio/src/components/common/ScrollArea.js b/ell-studio/src/components/common/ScrollArea.js index a9fa2ec8..7127df96 100644 --- a/ell-studio/src/components/common/ScrollArea.js +++ b/ell-studio/src/components/common/ScrollArea.js @@ -4,17 +4,9 @@ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" import { cn } from "library/utils" const ScrollArea = React.forwardRef(({ className, children, ...props }, ref) => ( - - - {children} - - - - +
+ {children} +
)) ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName @@ -37,4 +29,4 @@ const ScrollBar = React.forwardRef(({ className, orientation = "vertical", ...pr )) ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName -export { ScrollArea, ScrollBar } +export { ScrollArea, ScrollBar } \ No newline at end of file diff --git a/ell-studio/src/components/common/SidePanel.js b/ell-studio/src/components/common/SidePanel.js new file mode 100644 index 00000000..c9264c4b --- /dev/null +++ b/ell-studio/src/components/common/SidePanel.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { ScrollArea } from './ScrollArea'; + +const SidePanel = ({ title, children }) => ( + +

{title}

+
+ {children} +
+
+); + +export default SidePanel; \ No newline at end of file diff --git a/ell-studio/src/components/common/StatItem.js b/ell-studio/src/components/common/StatItem.js new file mode 100644 index 00000000..829df0a2 --- /dev/null +++ b/ell-studio/src/components/common/StatItem.js @@ -0,0 +1,16 @@ +import React from 'react'; + +const StatItem = ({ icon: Icon, label, value }) => ( +
+ + + {label} + + {value} +
+); + +export default StatItem; \ No newline at end of file diff --git a/ell-studio/src/components/invocations/InvocationsAnalyticsSidePanel.js b/ell-studio/src/components/invocations/InvocationsAnalyticsSidePanel.js new file mode 100644 index 00000000..8a98b8e7 --- /dev/null +++ b/ell-studio/src/components/invocations/InvocationsAnalyticsSidePanel.js @@ -0,0 +1,119 @@ +import React from 'react'; +import { FiZap, FiClock, FiHash, FiUsers, FiPercent, FiBox } from 'react-icons/fi'; +import { Link } from 'react-router-dom'; +import SidePanel from '../common/SidePanel'; +import MetricChart from '../MetricChart'; +import { motion } from 'framer-motion'; +import { LMPCardTitle } from '../depgraph/LMPCardTitle'; +import { Card } from '../common/Card'; + +const InvocationsAnalyticsSidePanel = ({ aggregateData, sidebarMetrics }) => { + if (!aggregateData || !sidebarMetrics) return null; + + return ( + + +
+

Overview

+
+
+ + Total Invocations: +
+
{sidebarMetrics.totalInvocations}
+
+ + Avg. Latency: +
+
{sidebarMetrics.avgLatency?.toFixed(2)}ms
+
+ + Total Tokens: +
+
{sidebarMetrics.totalTokens}
+
+ + Unique LMPs: +
+
{sidebarMetrics.uniqueLMPs}
+
+ + Success Rate: +
+
{sidebarMetrics.successRate.toFixed(2)}%
+
+ + Avg Tokens/Invocation: +
+
{(sidebarMetrics.totalTokens / sidebarMetrics.totalInvocations)?.toFixed(2)}
+
+
+ + + + + + + + {/*
+

Top 5 LMPs

+
    + {sidebarMetrics.topLMPs.map(([lmpName, count], index) => ( + + + + +
    + {count} invocations +
    +
    +
    +
    +
    + +
    + ))} +
+
*/} +
+
+ ); +}; + +export default InvocationsAnalyticsSidePanel; \ No newline at end of file diff --git a/ell-studio/src/components/invocations/InvocationsLayout.js b/ell-studio/src/components/invocations/InvocationsLayout.js index c17d782f..7fad392c 100644 --- a/ell-studio/src/components/invocations/InvocationsLayout.js +++ b/ell-studio/src/components/invocations/InvocationsLayout.js @@ -1,5 +1,5 @@ import React, { useState, useMemo, useEffect, useCallback } from 'react'; -import InvocationDetailsSidevar from './InvocationDetailsSidebar'; +import InvocationDetailsSidevar from './details/InvocationDetailsPopover'; import { useNavigate, useLocation } from 'react-router-dom'; const InvocationsLayout = ({ children, selectedTrace, setSelectedTrace, showSidebar = false, containerClass = '' }) => { diff --git a/ell-studio/src/components/invocations/InvocationDataPane.js b/ell-studio/src/components/invocations/details/InvocationDataPane.js similarity index 94% rename from ell-studio/src/components/invocations/InvocationDataPane.js rename to ell-studio/src/components/invocations/details/InvocationDataPane.js index 7ac65dc8..52701932 100644 --- a/ell-studio/src/components/invocations/InvocationDataPane.js +++ b/ell-studio/src/components/invocations/details/InvocationDataPane.js @@ -1,6 +1,6 @@ import React, { useState, useEffect, useMemo } from "react"; -import { lstrCleanStringify } from '../../utils/lstrCleanStringify'; -import { CodeSection } from '../source/CodeSection'; +import { lstrCleanStringify } from '../../../utils/lstrCleanStringify'; +import { CodeSection } from '../../source/CodeSection'; const InvocationDataPane = ({ invocation }) => { const [inputExpanded, setInputExpanded] = useState(true); diff --git a/ell-studio/src/components/invocations/InvocationDetailsSidebar.js b/ell-studio/src/components/invocations/details/InvocationDetailsPopover.js similarity index 94% rename from ell-studio/src/components/invocations/InvocationDetailsSidebar.js rename to ell-studio/src/components/invocations/details/InvocationDetailsPopover.js index a20ed4b1..c2e4a891 100644 --- a/ell-studio/src/components/invocations/InvocationDetailsSidebar.js +++ b/ell-studio/src/components/invocations/details/InvocationDetailsPopover.js @@ -1,10 +1,10 @@ import React, { useState, useRef, useEffect, useMemo } from "react"; import { FiLink, FiCopy, FiChevronDown, FiClock, FiTag } from "react-icons/fi"; -import { lstrCleanStringify } from '../../utils/lstrCleanStringify'; -import { CodeSection } from '../source/CodeSection'; -import { TraceGraph } from './TraceGraph'; -import ResizableSidebar from '../ResizableSidebar'; -import { InvocationInfoPane } from './InvocationInfoPane'; +import { lstrCleanStringify } from '../../../utils/lstrCleanStringify'; +import { CodeSection } from '../../source/CodeSection'; +import { TraceGraph } from '../TraceGraph'; +import ResizableSidebar from '../../ResizableSidebar'; +import { InvocationInfoPane } from '../InvocationInfoPane'; import InvocationDataPane from './InvocationDataPane'; const InvocationDetailsSidebar = ({ invocation, onClose, onResize }) => { diff --git a/ell-studio/src/components/invocations/TraceDetailsPane.js b/ell-studio/src/components/invocations/details/TraceDetailsPane.js similarity index 100% rename from ell-studio/src/components/invocations/TraceDetailsPane.js rename to ell-studio/src/components/invocations/details/TraceDetailsPane.js diff --git a/ell-studio/src/components/layouts/GenericPageLayout.js b/ell-studio/src/components/layouts/GenericPageLayout.js new file mode 100644 index 00000000..06a4aec2 --- /dev/null +++ b/ell-studio/src/components/layouts/GenericPageLayout.js @@ -0,0 +1,43 @@ +import React, { useState, useEffect } from 'react'; +import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "../common/Resizable"; +import { ScrollArea } from '../common/ScrollArea'; +import InvocationsLayout from '../invocations/InvocationsLayout'; + +const GenericPageLayout = ({ + children, + selectedTrace, + setSelectedTrace, + sidebarContent, + showSidebar = true, +}) => { + const [sidebarVisible, setSidebarVisible] = useState(!selectedTrace && showSidebar); + + useEffect(() => { + setSidebarVisible(!selectedTrace && showSidebar); + }, [selectedTrace, showSidebar]); + + return ( + + + +
+ {children} +
+
+
+ + + + {sidebarContent} + + +
+ ); +}; + +export default GenericPageLayout; \ No newline at end of file diff --git a/ell-studio/src/contexts/ThemeContext.js b/ell-studio/src/contexts/ThemeContext.js index 847202f6..a2b2a58b 100644 --- a/ell-studio/src/contexts/ThemeContext.js +++ b/ell-studio/src/contexts/ThemeContext.js @@ -1,4 +1,4 @@ -import React, { createContext, useState, useContext } from 'react'; +import React, { createContext, useState, useContext, useEffect } from 'react'; const ThemeContext = createContext(); @@ -7,6 +7,14 @@ export const useTheme = () => useContext(ThemeContext); export const ThemeProvider = ({ children }) => { const [darkMode, setDarkMode] = useState(true); + useEffect(() => { + if (darkMode) { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } + }, [darkMode]); + const toggleDarkMode = () => { setDarkMode(!darkMode); }; diff --git a/ell-studio/src/pages/Home.js b/ell-studio/src/pages/Home.js index e6a3485f..6436530f 100644 --- a/ell-studio/src/pages/Home.js +++ b/ell-studio/src/pages/Home.js @@ -17,7 +17,6 @@ function Home() { const { darkMode } = useTheme(); const { data: lmps, isLoading: isLoadingLMPs } = useLatestLMPs(); const { data: traces, isLoading: isLoadingTraces } = useTraces(lmps); - const toggleExpand = (lmpName, event) => { if (event.target.tagName.toLowerCase() !== 'a') { @@ -45,58 +44,58 @@ function Home() { const memoizedLMPs = useMemo(() => firstLMPs, [firstLMPs]); if (!memoizedLMPs || !memoizedTraces) { - return
-

Loading...

+ return
+

Loading...

; } return ( -
+
-
- +
+ Language Model Programs
- + Total LMPs: {lmps.length} - + Last Updated: {lmps[0] && lmps[0].versions && lmps[0].versions[0] ? getTimeAgo(lmps[0].versions[0].created_at) : 'N/A'}
- +
-
+
{lmps.map((lmp) => ( toggleExpand(lmp.name, e)} >
e.stopPropagation()} > - {lmp.name} + {lmp.name}
- + ID: {truncateId(lmp.lmp_id)} - + Latest @@ -104,9 +103,9 @@ function Home() {
-
- - {lmp.source.length > 100 ? `${lmp.source.substring(0, 100)}...` : lmp.source} +
+ + {lmp.source.length > 100 ? `${lmp.source.substring(0, 100)}...` : lmp.source}
diff --git a/ell-studio/src/pages/Invocations.js b/ell-studio/src/pages/Invocations.js index 0937cc41..073d3b81 100644 --- a/ell-studio/src/pages/Invocations.js +++ b/ell-studio/src/pages/Invocations.js @@ -1,14 +1,12 @@ import React, { useState, useEffect, useMemo } from 'react'; import { FiZap, FiEdit2, FiFilter, FiClock, FiColumns, FiPause, FiPlay, FiSearch } from 'react-icons/fi'; import InvocationsTable from '../components/invocations/InvocationsTable'; -import InvocationsLayout from '../components/invocations/InvocationsLayout'; -import MetricChart from '../components/MetricChart'; import { useNavigate, useLocation } from 'react-router-dom'; import { useInvocationsFromLMP, useInvocationsAggregate } from '../hooks/useBackend'; -import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "../components/common/Resizable"; -import { ScrollArea } from '../components/common/ScrollArea'; +import GenericPageLayout from '../components/layouts/GenericPageLayout'; +import InvocationsAnalyticsSidePanel from '../components/invocations/InvocationsAnalyticsSidePanel'; -const Traces = () => { +const Invocations = () => { const [selectedTrace, setSelectedTrace] = useState(null); const [isPolling, setIsPolling] = useState(true); const navigate = useNavigate(); @@ -130,229 +128,133 @@ const Traces = () => { }; }, [filteredInvocations, totalInvocations, avgLatency]); + const sidebarContent = ( + + ); + if (isLoading || isAggregateLoading) { return
Loading...
; } return ( - - - -
-

- {/* */} - Invocations -

+ +
+
+

Invocations

+
+
+
- {/* Search bar, advanced filters, and controls */} -
-
-
- setSearchTerm(e.target.value)} - /> - -
- - -
- - {advancedFilters.isOpen && ( -
- setAdvancedFilters(prev => ({ ...prev, lmpName: e.target.value }))} - /> - setAdvancedFilters(prev => ({ ...prev, inputContains: e.target.value }))} - /> - setAdvancedFilters(prev => ({ ...prev, outputContains: e.target.value }))} - /> -
- setAdvancedFilters(prev => ({ ...prev, latencyMin: e.target.value }))} - /> - setAdvancedFilters(prev => ({ ...prev, latencyMax: e.target.value }))} - /> -
-
- setAdvancedFilters(prev => ({ ...prev, tokensMin: e.target.value }))} - /> - setAdvancedFilters(prev => ({ ...prev, tokensMax: e.target.value }))} - /> -
-
- )} - +
+
+ setSearchTerm(e.target.value)} + /> +
- -
- + +
+ + {advancedFilters.isOpen && ( +
+ setAdvancedFilters(prev => ({ ...prev, lmpName: e.target.value }))} + /> + setAdvancedFilters(prev => ({ ...prev, inputContains: e.target.value }))} + /> + setAdvancedFilters(prev => ({ ...prev, outputContains: e.target.value }))} + /> +
+ setAdvancedFilters(prev => ({ ...prev, latencyMin: e.target.value }))} + /> + setAdvancedFilters(prev => ({ ...prev, latencyMax: e.target.value }))} + /> +
+
+ setAdvancedFilters(prev => ({ ...prev, tokensMin: e.target.value }))} + /> + setAdvancedFilters(prev => ({ ...prev, tokensMax: e.target.value }))} + /> +
- - - - {!selectedTrace && ( - <> - - - - {!isAggregateLoading && aggregateData && ( - <> -
-
-

Total Invocations

-

{aggregateData.total_invocations}

-
-
-

Avg Latency

-

{aggregateData.avg_latency.toFixed(2)}ms

-
-
-

Total Tokens

-

{aggregateData.total_tokens}

-
-
-

Unique LMPs

-

{aggregateData.unique_lmps}

-
-
- -
-
-

Invocations Over Time

- -
- -
-

Latency Over Time

- -
- -
-

Tokens Over Time

- -
- - -
-

Top 5 LMPs

-
    - {sidebarMetrics.topLMPs.map(([lmp, count], index) => ( -
  • - {index + 1}. {lmp} - {count} invocations -
  • - ))} -
-
- -
-

Additional Metrics

-
-
- Success Rate: - {aggregateData.success_rate?.toFixed(2)}% -
-
- Avg Tokens per Invocation: - {(aggregateData.total_tokens / aggregateData.total_invocations).toFixed(2)} -
-
-
-
- - )} -
-
- - )} - + )} + +
+ +
+ +
+ ); }; -export default Traces; \ No newline at end of file +export default Invocations; \ No newline at end of file diff --git a/ell-studio/src/pages/LMP.js b/ell-studio/src/pages/LMP.js index f16227dc..9cc2f66a 100644 --- a/ell-studio/src/pages/LMP.js +++ b/ell-studio/src/pages/LMP.js @@ -21,13 +21,9 @@ import { LMPCardTitle } from "../components/depgraph/LMPCardTitle"; import InvocationsLayout from "../components/invocations/InvocationsLayout"; import ToggleSwitch from "../components/common/ToggleSwitch"; import LMPDetailsSidePanel from "../components/LMPDetailsSidePanel"; -import { - ResizablePanelGroup, - ResizablePanel, - ResizableHandle, -} from "../components/common/Resizable"; -import { ScrollArea } from "../components/common/ScrollArea"; +import { Card } from "../components/common/Card"; +import GenericPageLayout from "../components/layouts/GenericPageLayout"; function LMP() { const { name, id } = useParams(); let [searchParams, setSearchParams] = useSearchParams(); @@ -56,7 +52,6 @@ function LMP() { ); const uses = lmp?.uses; - const [activeTab, setActiveTab] = useState("runs"); const [selectedTrace, setSelectedTrace] = useState(null); const navigate = useNavigate(); @@ -105,13 +100,23 @@ function LMP() { }); }; + const sidebar = useMemo( + () => ( + + ), + [lmp, uses, versionHistory] + ); const handleViewModeToggle = () => { setViewMode((prevMode) => (prevMode === "Source" ? "Diff" : "Source")); }; if (isLoadingLMP) return ( -
+
Loading...
); @@ -119,182 +124,149 @@ function LMP() { const omitColumns = ["name"]; return ( - - - {/* */} - -
-

- - - -

-
- - -
-
+
+
+

+ + + + + +

+
-
-
-
-
-

- Language Model Program -

- {(id || requestedInvocationId) && ( - <> - +
+
+
+
+

Language Model Program

+ {(id || requestedInvocationId) && ( + <> + + + + )} +
+
+ {previousVersion && ( + <> + {viewMode === "Diff" && ( +
+ + Comparing to - - )} -
-
- {previousVersion && ( - <> - {viewMode === "Diff" && ( -
- - Comparing to - -
- )} - - - )} - -
-
-
- -
-
- -
-
- {["Runs", "Version History"].map( - (tab) => ( - - ) - )} -
- -
- {activeTab === "runs" && ( - <> -
-
- - - - - -
-
- { - setSelectedTrace(trace); - setSearchParams({ i: trace.id }); - }} - currentlySelectedTrace={selectedTrace} - omitColumns={omitColumns} - /> - - )} - {activeTab === "version_history" && ( - - )} - -
+ )} + + + )} +
-
- - {/* */} - - {!selectedTrace && ( - <> - - -
- +
+
- - - )} - +
+ +
+
+ {["Runs", "Version History"].map((tab) => ( + + ))} +
+ +
+ {activeTab === "runs" && ( + <> +
+
+ + + + + +
+ +
+ { + setSelectedTrace(trace); + setSearchParams({ i: trace.id }); + }} + currentlySelectedTrace={selectedTrace} + omitColumns={omitColumns} + /> + + )} + {activeTab === "version_history" && ( + + )} +
+
+
+
+ ); } -export default LMP; +export default LMP; \ No newline at end of file diff --git a/ell-studio/src/styles/globals.css b/ell-studio/src/styles/globals.css index 4c6132f7..3a4fdaca 100644 --- a/ell-studio/src/styles/globals.css +++ b/ell-studio/src/styles/globals.css @@ -4,32 +4,49 @@ @layer base { :root { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - --primary: 217.2 91.2% 59.8%; - --primary-foreground: 222.2 47.4% 11.2%; - --secondary: 217.2 32.6% 17.5%; + --background: 224 71% 4%; + --foreground: 213 31% 91%; + --card: 224 71% 4%; + --card-foreground: 213 31% 91%; + --popover: 224 71% 4%; + --popover-foreground: 213 31% 91%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 1.2%; + --secondary: 222.2 47.4% 11.2%; --secondary-foreground: 210 40% 98%; - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - --accent: 217.2 32.6% 17.5%; + --muted: 223 47% 11%; + --muted-foreground: 215.4 16.3% 56.9%; + --accent: 216 34% 17%; --accent-foreground: 210 40% 98%; - --destructive: 0 62.8% 30.6%; + --destructive: 0 63% 31%; --destructive-foreground: 210 40% 98%; - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 224.3 76.3% 48%; - --chart-1: 220 70% 50%; - --chart-2: 160 60% 45%; - --chart-3: 30 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; + --border: 216 34% 17%; + --input: 216 34% 17%; + --ring: 216 34% 17%; --radius: 0.5rem; } + + .dark { + --background: 224 71% 4%; + --foreground: 213 31% 91%; + --card: 224 71% 4%; + --card-foreground: 213 31% 91%; + --popover: 224 71% 4%; + --popover-foreground: 213 31% 91%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 1.2%; + --secondary: 222.2 47.4% 11.2%; + --secondary-foreground: 210 40% 98%; + --muted: 223 47% 11%; + --muted-foreground: 215.4 16.3% 56.9%; + --accent: 216 34% 17%; + --accent-foreground: 210 40% 98%; + --destructive: 0 63% 31%; + --destructive-foreground: 210 40% 98%; + --border: 216 34% 17%; + --input: 216 34% 17%; + --ring: 216 34% 17%; + } } @layer base { @@ -88,3 +105,4 @@ stroke-dashoffset: -10; } } + diff --git a/tailwind.config.js b/tailwind.config.js index d3ec7ae0..dc8495df 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,11 +1,43 @@ module.exports = { - // ... other config + darkMode: ["class"], + content: [ + "./src/**/*.{js,jsx,ts,tsx}", + ], theme: { extend: { colors: { - gray: { - 750: '#2d333b', - 850: '#22272e', + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", }, }, keyframes: { @@ -29,5 +61,4 @@ module.exports = { addUtilities(newUtilities, ['responsive', 'hover']); }, ], - // ... other config -}; \ No newline at end of file +} \ No newline at end of file