diff --git a/web/components/templates/requests/chatComponent/chat.tsx b/web/components/templates/requests/chatComponent/chat.tsx index a2dd657a19..9e66dc9ee6 100644 --- a/web/components/templates/requests/chatComponent/chat.tsx +++ b/web/components/templates/requests/chatComponent/chat.tsx @@ -13,11 +13,13 @@ import { ChatTopBar, PROMPT_MODES } from "./chatTopBar"; import { JsonView } from "./jsonView"; import { LlmSchema } from "../../../../lib/api/models/requestResponseModel"; import { useLocalStorage } from "../../../../services/hooks/localStorage"; +import { HeliconeRequest } from "@/lib/api/request/request"; interface ChatProps { llmSchema?: LlmSchema; requestBody: any; responseBody: any; + request: HeliconeRequest; requestId: string; status: number; model: string; @@ -31,6 +33,7 @@ interface ChatProps { } export const Chat: React.FC = ({ + request, requestBody, responseBody, requestId, @@ -42,7 +45,7 @@ export const Chat: React.FC = ({ autoInputs, hideTopBar, messageSlice, - className = "bg-gray-50", + className = "bg-slate-50", status, }) => { const [open, setOpen] = useState(false); @@ -97,7 +100,7 @@ export const Chat: React.FC = ({ className )} > -
+
{!hideTopBar && } {mode === "JSON" ? ( @@ -122,7 +125,7 @@ export const Chat: React.FC = ({
-
+
{mode === "JSON" ? ( diff --git a/web/components/templates/requests/chatComponent/chatTopBar.tsx b/web/components/templates/requests/chatComponent/chatTopBar.tsx index b0d1fd2cf7..cfbe9d213f 100644 --- a/web/components/templates/requests/chatComponent/chatTopBar.tsx +++ b/web/components/templates/requests/chatComponent/chatTopBar.tsx @@ -44,11 +44,11 @@ export const ChatTopBar: React.FC = ({ const router = useRouter(); return ( -
+
@@ -68,10 +68,10 @@ export const Completion = (props: CompletionProps) => { onClick={() => { setMode(mode === "pretty" ? "json" : "pretty"); }} - className="flex flex-row space-x-1 items-center hover:bg-gray-200 dark:hover:bg-gray-800 py-1 px-2 rounded-lg" + className="flex flex-row space-x-1 items-center hover:bg-slate-200 dark:hover:bg-slate-800 py-1 px-2 rounded-lg" > -

+

{mode === "pretty" ? "JSON" : "Pretty"}

@@ -79,39 +79,39 @@ export const Completion = (props: CompletionProps) => {
{mode === "json" ? ( -
+
-

+

Request

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

+

{response.title}

-
+              
                 {JSON.stringify(rawResponse, null, 2)}
               
) : ( -
+
-

+

Request

-

+

{removeLeadingWhitespace(request)}

-

+

{response.title}

-

+

{response && removeLeadingWhitespace(response.text)} {renderImageRow()}

diff --git a/web/components/templates/requestsV2/builder/chatBuilder.tsx b/web/components/templates/requestsV2/builder/chatBuilder.tsx index 7a28a93123..36cae9d3ae 100644 --- a/web/components/templates/requestsV2/builder/chatBuilder.tsx +++ b/web/components/templates/requestsV2/builder/chatBuilder.tsx @@ -76,6 +76,7 @@ class ChatBuilder extends AbstractRequestBuilder { const renderChat = () => { return ( Pending...

) : this.response.response_status === 200 ? ( {this.response.request_body.messages && ( { @@ -9,27 +12,47 @@ const StatusBadge = (props: StatusBadgeProps) => { switch (statusType) { case "cached": return ( - + Cached ); case "success": case "COMPLETED": return ( - + Success ); case "pending": case "PENDING": return ( - + Pending ); case "RUNNING": return ( - + Running ); @@ -37,38 +60,68 @@ const StatusBadge = (props: StatusBadgeProps) => { case "error": if (errorCode === -2) { return ( - + {`Pending`} ); } else if (errorCode === -3) { return ( - + {`Cancelled`} ); } else if (errorCode === -1) { return ( - + {`Timeout`} ); } else if (errorCode === -4) { return ( - + {`Threat`} ); } else { return ( - + {`${errorCode} Error`} ); } default: return ( - + Unknown ); diff --git a/web/components/templates/sessions/sessionId/SessionContent.tsx b/web/components/templates/sessions/sessionId/SessionContent.tsx index c2e6893cc8..f21f491c65 100644 --- a/web/components/templates/sessions/sessionId/SessionContent.tsx +++ b/web/components/templates/sessions/sessionId/SessionContent.tsx @@ -134,6 +134,7 @@ const SessionContent: React.FC = ({ selectedRequestId={selectedRequestId} setSelectedRequestId={handleRequestIdChange} requests={requests} + showSpan={false} /> )} diff --git a/web/components/templates/sessions/sessionId/SessionContentV2.tsx b/web/components/templates/sessions/sessionId/SessionContentV2.tsx new file mode 100644 index 0000000000..5d949952b3 --- /dev/null +++ b/web/components/templates/sessions/sessionId/SessionContentV2.tsx @@ -0,0 +1,186 @@ +import { useRouter } from "next/router"; +import { useMemo, useState } from "react"; +import { Session } from "../../../../lib/sessions/sessionTypes"; +import { useLocalStorage } from "../../../../services/hooks/localStorage"; +import { useGetRequests } from "../../../../services/hooks/requests"; +import { Col } from "../../../layout/common/col"; +import getNormalizedRequest from "../../requestsV2/builder/requestBuilder"; +import RequestDrawerV2 from "../../requestsV2/requestDrawerV2"; +import { BreadCrumbV2 } from "./breadCrumbV2"; +import ChatSession from "./Chat/ChatSession"; +import TreeView from "./Tree/TreeView"; +import { Row } from "@/components/layout/common"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Button } from "@/components/ui/button"; +import { EyeIcon, EyeOffIcon } from "lucide-react"; + +interface SessionContentV2Props { + session: Session; + session_id: string; + requests: ReturnType; +} + +export const TABS = [ + { + id: "tree", + label: "Tree", + }, + { + id: "chat", + label: "Chat", + }, +] as const; + +const SessionContentV2: React.FC = ({ + session, + session_id, + requests, +}) => { + const router = useRouter(); + const { view, requestId } = router.query; + + const [selectedRequestId, setSelectedRequestId] = useState( + (requestId as string) || "" + ); + + const handleRequestIdChange = (newRequestId: string) => { + setSelectedRequestId(newRequestId); + router.push( + { + pathname: router.pathname, + query: { ...router.query, requestId: newRequestId }, + }, + undefined, + { shallow: true } + ); + }; + + const [openDrawer, setOpenDrawer] = useState(false); + + const [currentTopView, setCurrentTopView] = useLocalStorage( + "currentTopView", + (view as (typeof TABS)[number]["id"]) ?? "tree" + ); + + const startTime = useMemo(() => { + const dates = + requests.requests.requests?.map((r) => new Date(r.request_created_at)) ?? + []; + + return dates.sort((a, b) => a.getTime() - b.getTime())?.[0] ?? undefined; + }, [requests.requests.requests]); + + const endTime = useMemo(() => { + const dates = + requests.requests.requests?.map((r) => new Date(r.request_created_at)) ?? + []; + + return dates.sort((a, b) => b.getTime() - a.getTime())?.[0] ?? undefined; + }, [requests.requests.requests]); + + const [showSpan, setShowSpan] = useLocalStorage("showSpan-TreeView", true); + + return ( + + + setCurrentTopView(tab as (typeof TABS)[number]["id"]) + } + > + + trace.request.user) + .filter((user) => user !== "" && user != null)} + models={session.traces.map((trace) => trace.request.model ?? "")} + promptTokens={session.traces.reduce( + (acc, trace) => + acc + (parseInt(`${trace?.request?.promptTokens}`) || 0), + 0 + )} + completionTokens={session.traces.reduce( + (acc, trace) => + acc + (parseInt(`${trace?.request?.completionTokens}`) || 0), + 0 + )} + sessionId={session_id as string} + numTraces={session.traces.length} + sessionCost={session.session_cost_usd} + startTime={startTime} + endTime={endTime} + /> + + + + {TABS.map((tab) => ( + + {tab.label} + + ))} + + {currentTopView === "tree" && + (showSpan ? ( + + ) : ( + + ))} + + + + + + + + + + + r.request_id === selectedRequestId + ) && + getNormalizedRequest( + requests.requests.requests?.find( + (r) => r.request_id === selectedRequestId + )! + ) + } + open={selectedRequestId !== "" && openDrawer} + setOpen={(open) => handleRequestIdChange("")} + properties={[]} + /> + + + ); +}; + +export default SessionContentV2; diff --git a/web/components/templates/sessions/sessionId/Span.tsx b/web/components/templates/sessions/sessionId/Span.tsx index 317d18965d..e81e0930d1 100644 --- a/web/components/templates/sessions/sessionId/Span.tsx +++ b/web/components/templates/sessions/sessionId/Span.tsx @@ -12,6 +12,8 @@ import { import { Session, Trace } from "../../../../lib/sessions/sessionTypes"; import { Col } from "../../../layout/common/col"; import { clsx } from "../../../shared/clsx"; +import { Row } from "@/components/layout/common"; +import { Clock4Icon } from "lucide-react"; interface BarChartTrace { name: string; @@ -40,6 +42,7 @@ export const TraceSpan = ({ (trace.end_unix_timestamp_ms - trace.start_unix_timestamp_ms) / 1000, trace: trace, })); + console.log(spanData, "spanData"); const roundedRadius = 5; const domain = [ @@ -51,7 +54,7 @@ export const TraceSpan = ({ const barSize = 35; // Increased from 30 to 50 return ( -
+
{" "} {/* Increased from 350px to 500px */} @@ -65,7 +68,7 @@ export const TraceSpan = ({ { const { payload } = props; + const trace: BarChartTrace = payload?.[0]?.payload; return ( - -
{trace?.name}
- -
Duration: {trace?.duration}s
-
- Start:{" "} - {new Date( - trace?.trace.request.createdAt ?? 0 - ).toISOString()} -
- + + + +

+ {trace?.name} +

+ {/*
*/} +
+ + +

+ {trace?.duration}s +

+
+
+

+ Start:{" "} + {new Date( + trace?.trace.request.createdAt ?? 0 + ).toLocaleString()} +

); }} @@ -154,7 +177,7 @@ export const TraceSpan = ({ ? y + height / 2 : y } - fill={isSelected ? "#FFFFFF" : "#000000"} + fill={isSelected ? "#0369A1" : "#334155"} opacity={isSelected ? 1 : 0.7} textAnchor="start" dominantBaseline="central" @@ -178,9 +201,11 @@ export const TraceSpan = ({ key={`colored-cell-${index}`} className={clsx( entry.trace.request_id === selectedRequestId - ? "fill-[#1E40AF] hover:fill-[#1E3A8A]" - : "fill-[#BFDBFE] hover:fill-[#93C5FD]" + ? "fill-sky-200 hover:fill-sky-200/50 hover:cursor-pointer" + : "fill-sky-100 hover:fill-sky-50 hover:cursor-pointer" )} + strokeWidth={1} + stroke="#bae6fd" onClick={() => setSelectedRequestId(entry.trace.request_id)} /> ))} @@ -225,22 +250,42 @@ export const TraceSpan = ({ /> { const { payload } = props; + const trace: BarChartTrace = payload?.[0]?.payload; return ( - -
{trace?.name}
- -
Duration: {trace?.duration}s
-
- Start:{" "} - {new Date( - trace?.trace.request.createdAt ?? 0 - ).toISOString()} -
- + + + +

+ {trace?.name} +

+ {/*
*/} +
+ + +

+ {trace?.duration}s +

+
+
+

+ Start:{" "} + {new Date( + trace?.trace.request.createdAt ?? 0 + ).toLocaleString()} +

); }} diff --git a/web/components/templates/sessions/sessionId/Tree/PathNode.tsx b/web/components/templates/sessions/sessionId/Tree/PathNode.tsx index b14b101806..602259ffd1 100644 --- a/web/components/templates/sessions/sessionId/Tree/PathNode.tsx +++ b/web/components/templates/sessions/sessionId/Tree/PathNode.tsx @@ -1,67 +1,40 @@ +import { MinusSquareIcon, PlusSquareIcon } from "lucide-react"; import { TreeNodeData } from "../../../../../lib/sessions/sessionTypes"; -import { clsx } from "../../../../shared/clsx"; - -function MinusSign() { - return ( - - - - - ); -} - -function PlusSign() { - return ( - - - - ); -} export function PathNode(props: { node: TreeNodeData; setCloseChildren: React.Dispatch>; closeChildren: boolean; setSelectedRequestId: (x: string) => void; + level: number; }) { - const { node, setCloseChildren, closeChildren, setSelectedRequestId } = props; + const { node, setCloseChildren, closeChildren, setSelectedRequestId, level } = + props; return (
node.children ? setCloseChildren(!closeChildren) : setSelectedRequestId(node.trace?.request_id ?? "") } > -
- {!closeChildren ? : } + {!closeChildren ? ( + + ) : ( + + )} -
{node.name}
-
{node.duration}
+
+
{node.name}
+
{node.duration}
+
); diff --git a/web/components/templates/sessions/sessionId/Tree/RequestNode.tsx b/web/components/templates/sessions/sessionId/Tree/RequestNode.tsx index 7cde0f246f..cdd3e30cfb 100644 --- a/web/components/templates/sessions/sessionId/Tree/RequestNode.tsx +++ b/web/components/templates/sessions/sessionId/Tree/RequestNode.tsx @@ -1,9 +1,8 @@ import { TreeNodeData } from "../../../../../lib/sessions/sessionTypes"; -import { Col } from "../../../../layout/common/col"; import { Row } from "../../../../layout/common/row"; import StatusBadge from "../../../requestsV2/statusBadge"; import { clsx } from "../../../../shared/clsx"; -import { HorizontalLine } from "./Tree"; +import { Clock4Icon } from "lucide-react"; export function RequestNode(props: { selectedRequestId: string; @@ -11,22 +10,27 @@ export function RequestNode(props: { setCloseChildren: React.Dispatch>; closeChildren: boolean; setSelectedRequestId: (x: string) => void; + level: number; + setShowDrawer: (x: boolean) => void; }) { + console.log(props.selectedRequestId, props.node.trace?.request_id); const { selectedRequestId, node, setCloseChildren, closeChildren, setSelectedRequestId, + level, + setShowDrawer, } = props; return (
node.children @@ -34,69 +38,30 @@ export function RequestNode(props: { : setSelectedRequestId(node.trace?.request_id ?? "") } > - - - + +
+ {node.trace?.request.model === "vector_db" || + node.trace?.request.model.startsWith("tool") + ? node.trace?.request.model.split(":")[0] + : node.name} +
+
+ {node.trace?.request.model} +
+
+ + {node.trace?.request.status && ( + )} - > - {node.name} -
- - - - {node.trace?.request.model} - - - - - - - - {node.duration} - - - - - {node.trace?.properties && ( -
- {Object.entries(node.trace?.properties).map( - ([key, value], index) => ( -
- {key}: {value} -
- ) - )} -
- )} -
- {node.trace?.request.status && ( - - )} + + + {node.duration} - +
); diff --git a/web/components/templates/sessions/sessionId/Tree/Tree.tsx b/web/components/templates/sessions/sessionId/Tree/Tree.tsx index 415043f254..365b4451b2 100644 --- a/web/components/templates/sessions/sessionId/Tree/Tree.tsx +++ b/web/components/templates/sessions/sessionId/Tree/Tree.tsx @@ -1,81 +1,158 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { TreeNodeData } from "../../../../../lib/sessions/sessionTypes"; import { clsx } from "../../../../shared/clsx"; import { PathNode } from "./PathNode"; import { RequestNode } from "./RequestNode"; -interface VerticalLineProps { - isLastChild: boolean; -} - -const VerticalLine: React.FC = ({ isLastChild }) => { - return ( -
- ); -}; - -export const HorizontalLine: React.FC = () => { - return ( -
- ); -}; +import { Col, Row } from "@/components/layout/common"; +import { SidebarCloseIcon } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; export interface TreeNodeProps { node: TreeNodeData; selectedRequestIdDispatch: [string, (x: string) => void]; isLastChild: boolean; + level: number; + collapseAll?: boolean; + setShowDrawer: (x: boolean) => void; } const TreeNode: React.FC = ({ node, selectedRequestIdDispatch, isLastChild, + level, + collapseAll, + setShowDrawer, }) => { - const [closeChildren, setCloseChildren] = useState(false); + const [closeChildren, setCloseChildren] = useState(collapseAll ?? false); const [selectedRequestId, setSelectedRequestId] = selectedRequestIdDispatch; + useEffect(() => { + setCloseChildren(collapseAll ?? false); + }, [collapseAll]); + return (
- {node.children ? ( - + + + {new Array(level).fill(null).map((_, index) => ( +
+ node.children + ? setCloseChildren(!closeChildren) + : setSelectedRequestId(node.trace?.request_id ?? "") + } + > +
+
+ ))} + + + {!closeChildren && + node.children.map((child, index) => ( + + ))} + ) : ( - - )} - {!closeChildren ? ( - node.children && - node.children.map((child, index) => ( - +
+ node.children + ? setCloseChildren(!closeChildren) + : setSelectedRequestId(node.trace?.request_id ?? "") } + > +
+ + + + + + +

View request

+
+
+
+
+ + {new Array(level - 1).fill(null).map((_, index) => ( +
+ node.children + ? setCloseChildren(!closeChildren) + : setSelectedRequestId(node.trace?.request_id ?? "") + } + > +
+
+ ))} + - )) - ) : ( - <> + )}
); @@ -84,16 +161,20 @@ interface TreeProps { data: TreeNodeData; className?: string; selectedRequestIdDispatch: [string, (x: string) => void]; + collapseAll?: boolean; + setShowDrawer: (x: boolean) => void; } export const Tree: React.FC = ({ data, className, selectedRequestIdDispatch, + collapseAll, + setShowDrawer, }) => (
@@ -104,6 +185,9 @@ export const Tree: React.FC = ({ node={child} selectedRequestIdDispatch={selectedRequestIdDispatch} isLastChild={!!data.children && index === data.children.length - 1} + level={0} + collapseAll={collapseAll} + setShowDrawer={setShowDrawer} /> ))}
diff --git a/web/components/templates/sessions/sessionId/Tree/TreeView.tsx b/web/components/templates/sessions/sessionId/Tree/TreeView.tsx index e0f609eb0a..ecfeb8e433 100644 --- a/web/components/templates/sessions/sessionId/Tree/TreeView.tsx +++ b/web/components/templates/sessions/sessionId/Tree/TreeView.tsx @@ -5,17 +5,29 @@ import { Row } from "../../../../layout/common/row"; import getNormalizedRequest from "../../../requestsV2/builder/requestBuilder"; import { TraceSpan } from "../Span"; import { Tree } from "./Tree"; -import RequestRow from "../../../requestsV2/requestRow"; -import { ChevronDownIcon } from "@heroicons/react/20/solid"; import { useGetRequests } from "../../../../../services/hooks/requests"; import { Col } from "../../../../layout/common"; -import { useLocalStorage } from "../../../../../services/hooks/localStorage"; +import { Button } from "@/components/ui/button"; +import { + ChevronsDownUpIcon, + ChevronsUpDownIcon, + ExpandIcon, + ShrinkIcon, +} from "lucide-react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import RequestDrawerV2 from "@/components/templates/requestsV2/requestDrawerV2"; interface TreeViewProps { session: Session; selectedRequestId: string; setSelectedRequestId: (id: string) => void; requests: ReturnType; + showSpan: boolean; } const TreeView: React.FC = ({ @@ -23,95 +35,81 @@ const TreeView: React.FC = ({ selectedRequestId, setSelectedRequestId, requests, + showSpan, }) => { const [expandReq, setExpandReq] = useState(false); const requestIdToShow = selectedRequestId ?? session.traces?.[0]?.request_id ?? null; - - const [showSpan, setShowSpan] = useLocalStorage("showSpan-TreeView", true); - + const [expandSpan, setExpandSpan] = useState(false); + const [collapseAll, setCollapseAll] = useState(false); + const [showDrawer, setShowDrawer] = useState(false); return ( {showSpan && ( -
+
+
)} - - - -
- {requestIdToShow && ( -
-
- - {session.traces.filter( - (trace) => trace.request_id == selectedRequestId - )?.[0]?.request && ( - trace.request_id == selectedRequestId - )?.[0]?.request - } - properties={[]} - open={true} - /> - )} -
- -
- -
-
- )} - + + +
+ + + + + + Collapse All + + +
+ + +
{requestIdToShow && requests.requests.requests?.find( @@ -125,6 +123,18 @@ const TreeView: React.FC = ({
+ {showDrawer && requestIdToShow && ( + r.request_id === requestIdToShow + )! + )} + properties={[]} + /> + )} ); }; diff --git a/web/components/templates/sessions/sessionId/breadCrumbV2.tsx b/web/components/templates/sessions/sessionId/breadCrumbV2.tsx new file mode 100644 index 0000000000..0b266ec151 --- /dev/null +++ b/web/components/templates/sessions/sessionId/breadCrumbV2.tsx @@ -0,0 +1,195 @@ +import { useState } from "react"; +import { getTimeAgo } from "../../../../lib/sql/timeHelpers"; +import HcBreadcrumb from "../../../ui/hcBreadcrumb"; +import { formatLargeNumber } from "../../../shared/utils/numberFormat"; +import { CopyIcon, InfoIcon } from "lucide-react"; +import { + HoverCard, + HoverCardContent, + HoverCardTrigger, +} from "@/components/ui/hoverCard"; +import { Button } from "@/components/ui/button"; +import useNotification from "@/components/shared/notification/useNotification"; +import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; +import { Badge } from "@/components/ui/badge"; + +function timeDiff(startTime: Date, endTime: Date): string { + const diff = endTime.getTime() - startTime.getTime(); + const seconds = Math.floor(diff / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const milliseconds = diff % 1000; + + if (hours > 0) { + return `${hours}h ${minutes}m ${seconds}s`; + } else if (hours == 0 && minutes > 0) { + return `${minutes}m ${seconds}s`; + } else if (hours == 0 && minutes == 0 && seconds == 0) { + return `${milliseconds}ms`; + } else { + return `${seconds}.${milliseconds}s`; + } +} + +export const BreadCrumbV2 = ({ + sessionId, + startTime, + endTime, + numTraces, + sessionCost, + promptTokens, + completionTokens, + models, + users, +}: { + sessionId: string; + startTime?: Date; + endTime?: Date; + numTraces: number; + sessionCost: number; + models: string[]; + promptTokens: number; + completionTokens: number; + users: string[]; +}) => { + const [expanded, setExpanded] = useState(false); + const { setNotification } = useNotification(); + + return ( +
+ + + + + + + +
+
+

+ Session ID +

+
+

{sessionId}

+ +
+
+
+ +
+
+

Traces

+

{numTraces}

+
+
+

+ Total cost +

+

+ ${formatLargeNumber(sessionCost)} +

+
+
+

+ Total latency +

+

+ {startTime && endTime ? timeDiff(startTime, endTime) : ""} +

+
+
+

+ Models used +

+ +
+ {Array.from(new Set(models)).map((model, idx) => ( + + {model} + + ))} +
+ +
+
+
+ +
+
+

+ Total tokens +

+

+ {promptTokens + completionTokens} +

+
+
+

+ Prompt tokens +

+

{promptTokens}

+
+
+

+ Completion tokens +

+

+ {completionTokens} +

+
+
+
+
+

+ Last used +

+

+ {getTimeAgo(endTime)} +

+
+
+

+ Created on +

+

+ {startTime ? startTime.toLocaleDateString() : ""} +

+
+
+
+
+
+ ); +}; diff --git a/web/components/ui/hoverCard.tsx b/web/components/ui/hoverCard.tsx new file mode 100644 index 0000000000..d130a8bfb3 --- /dev/null +++ b/web/components/ui/hoverCard.tsx @@ -0,0 +1,29 @@ +"use client"; + +import * as React from "react"; +import * as HoverCardPrimitive from "@radix-ui/react-hover-card"; + +import { cn } from "@/lib/utils"; + +const HoverCard = HoverCardPrimitive.Root; + +const HoverCardTrigger = HoverCardPrimitive.Trigger; + +const HoverCardContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + +)); +HoverCardContent.displayName = HoverCardPrimitive.Content.displayName; + +export { HoverCard, HoverCardTrigger, HoverCardContent }; diff --git a/web/components/ui/tabs.tsx b/web/components/ui/tabs.tsx index 10e0c7e1c5..bd40119890 100644 --- a/web/components/ui/tabs.tsx +++ b/web/components/ui/tabs.tsx @@ -1,35 +1,72 @@ import * as React from "react"; import * as TabsPrimitive from "@radix-ui/react-tabs"; +import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const Tabs = TabsPrimitive.Root; +const tabsListVariants = cva( + "inline-flex h-10 items-center justify-center rounded-md p-1", + { + variants: { + variant: { + default: + "bg-slate-100 text-slate-500 dark:bg-slate-800 dark:text-slate-400", + secondary: + "bg-white text-slate-500 dark:bg-slate-950 dark:text-slate-400", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +interface TabsListProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + const TabsList = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( + TabsListProps +>(({ className, variant, ...props }, ref) => ( )); TabsList.displayName = TabsPrimitive.List.displayName; +const tabsTriggerVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-white transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300", + { + variants: { + variant: { + default: + "data-[state=active]:bg-white data-[state=active]:text-slate-950 data-[state=active]:shadow-sm", + secondary: + "bg-white hover:bg-slate-100 data-[state=active]:bg-slate-100 data-[state=active]:text-slate-950", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +interface TabsTriggerProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + const TabsTrigger = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( + TabsTriggerProps +>(({ className, variant, ...props }, ref) => ( )); diff --git a/web/package.json b/web/package.json index f5b0dc94f6..2baa666f7f 100644 --- a/web/package.json +++ b/web/package.json @@ -41,6 +41,7 @@ "@radix-ui/react-context-menu": "^2.2.1", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", + "@radix-ui/react-hover-card": "^1.1.1", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-menubar": "^1.1.1", "@radix-ui/react-progress": "^1.1.0", @@ -126,25 +127,24 @@ "use-deep-compare-effect": "^1.8.1" }, "devDependencies": { - "@types/node": "18.11.9", - "@types/react": "^18.2.72", - "@types/react-dom": "^18.2.22", - "@types/uuid": "^9.0.8", - "typescript": "5.1.6", "@graphql-eslint/eslint-plugin": "^3.17.0", "@parcel/watcher": "^2.3.0", "@tailwindcss/typography": "^0.5.12", "@types/dagre": "^0.7.49", "@types/dateformat": "^5.0.0", "@types/js-cookie": "^3.0.3", + "@types/node": "18.11.9", "@types/papaparse": "^5.3.7", "@types/pg": "^8.6.6", "@types/prismjs": "^1.26.4", + "@types/react": "^18.2.72", "@types/react-beautiful-dnd": "^13", + "@types/react-dom": "^18.2.22", "@types/react-grid-layout": "^1.3.3", "@types/react-lottie": "^1", "@types/react-resizable": "^3.0.8", "@types/react-syntax-highlighter": "^15.5.6", + "@types/uuid": "^9.0.8", "autoprefixer": "^10.4.16", "commandbar": "^1.7.3", "eslint-config-prettier": "^8.5.0", @@ -154,6 +154,7 @@ "postcss": "^8.4.31", "prettier": "^2.8.0", "shadcn-ui": "^0.9.0", - "tailwindcss": "^3.3.5" + "tailwindcss": "^3.3.5", + "typescript": "5.1.6" } } diff --git a/web/pages/sessions/[session_id].tsx b/web/pages/sessions/[session_id].tsx index f23caebd78..2a239bf319 100644 --- a/web/pages/sessions/[session_id].tsx +++ b/web/pages/sessions/[session_id].tsx @@ -4,8 +4,8 @@ import { withAuthSSR } from "../../lib/api/handlerWrappers"; import { useGetRequests } from "../../services/hooks/requests"; -import SessionContent from "../../components/templates/sessions/sessionId/SessionContent"; import { sessionFromHeliconeRequests } from "../../lib/sessions/sessionsFromHeliconeTequests"; +import SessionContentV2 from "../../components/templates/sessions/sessionId/SessionContentV2"; const SessionDetail = ({ session_id }: { session_id: string }) => { const requests = useGetRequests( @@ -30,7 +30,7 @@ const SessionDetail = ({ session_id }: { session_id: string }) => { const session = sessionFromHeliconeRequests(requests.requests.requests ?? []); return ( -