| 'use client' |
|
|
| import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react' |
| import { useQueryState } from 'nuqs' |
|
|
| import { useStore } from '@/store' |
| import useSessionLoader from '@/hooks/useSessionLoader' |
|
|
| import SessionItem from './SessionItem' |
| import SessionBlankState from './SessionBlankState' |
| import { Skeleton } from '@/components/ui/skeleton' |
| import { cn } from '@/lib/utils' |
|
|
| interface SkeletonListProps { |
| skeletonCount: number |
| } |
| const SkeletonList: FC<SkeletonListProps> = ({ skeletonCount }) => { |
| const list = useMemo( |
| () => Array.from({ length: skeletonCount }, (_, i) => i), |
| [skeletonCount] |
| ) |
|
|
| return list.map((k, idx) => ( |
| <Skeleton |
| key={k} |
| className={cn( |
| 'mb-1 h-11 rounded-lg px-3 py-2', |
| idx > 0 && 'bg-background-secondary' |
| )} |
| /> |
| )) |
| } |
|
|
| const Sessions = () => { |
| const [agentId] = useQueryState('agent', { |
| parse: (v) => v || undefined, |
| history: 'push' |
| }) |
| const [teamId] = useQueryState('team') |
| const [sessionId] = useQueryState('session') |
| const [dbId] = useQueryState('db_id') |
|
|
| const { |
| selectedEndpoint, |
| mode, |
| isEndpointActive, |
| isEndpointLoading, |
| hydrated, |
| sessionsData, |
| setSessionsData, |
| isSessionsLoading |
| } = useStore() |
|
|
| console.log({ sessionsData }) |
|
|
| const [isScrolling, setIsScrolling] = useState(false) |
| const [selectedSessionId, setSelectedSessionId] = useState<string | null>( |
| null |
| ) |
|
|
| const { getSessions, getSession } = useSessionLoader() |
| const scrollTimeoutRef = useRef<ReturnType<typeof setTimeout>>(null) |
|
|
| const handleScroll = () => { |
| setIsScrolling(true) |
|
|
| if (scrollTimeoutRef.current) { |
| clearTimeout(scrollTimeoutRef.current) |
| } |
|
|
| scrollTimeoutRef.current = setTimeout(() => { |
| setIsScrolling(false) |
| }, 1500) |
| } |
|
|
| |
| useEffect(() => { |
| return () => { |
| if (scrollTimeoutRef.current) { |
| clearTimeout(scrollTimeoutRef.current) |
| } |
| } |
| }, []) |
|
|
| useEffect(() => { |
| if (hydrated && sessionId && selectedEndpoint && (agentId || teamId)) { |
| const entityType = agentId ? 'agent' : 'team' |
| getSession({ entityType, agentId, teamId, dbId }, sessionId) |
| } |
| |
| }, [hydrated, sessionId, selectedEndpoint, agentId, teamId, dbId]) |
|
|
| useEffect(() => { |
| if (!selectedEndpoint || isEndpointLoading) return |
| if (!(agentId || teamId || dbId)) { |
| setSessionsData([]) |
| return |
| } |
| setSessionsData([]) |
| getSessions({ |
| entityType: mode, |
| agentId, |
| teamId, |
| dbId |
| }) |
| |
| }, [ |
| selectedEndpoint, |
| agentId, |
| teamId, |
| mode, |
| isEndpointLoading, |
| getSessions, |
| dbId |
| ]) |
|
|
| useEffect(() => { |
| if (sessionId) setSelectedSessionId(sessionId) |
| }, [sessionId]) |
|
|
| const handleSessionClick = useCallback( |
| (id: string) => () => setSelectedSessionId(id), |
| [] |
| ) |
|
|
| if (isSessionsLoading || isEndpointLoading) { |
| return ( |
| <div className="w-full"> |
| <div className="mb-2 text-xs font-medium uppercase">Sessions</div> |
| <div className="mt-4 h-[calc(100vh-325px)] w-full overflow-y-auto"> |
| <SkeletonList skeletonCount={5} /> |
| </div> |
| </div> |
| ) |
| } |
|
|
| return ( |
| <div className="w-full"> |
| <div className="mb-2 w-full text-xs font-medium uppercase">Sessions</div> |
| <div |
| className={`h-[calc(100vh-345px)] overflow-y-auto font-geist transition-all duration-300 [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar]:transition-opacity [&::-webkit-scrollbar]:duration-300 ${ |
| isScrolling |
| ? '[&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-background [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar]:opacity-0' |
| : '[&::-webkit-scrollbar]:opacity-100' |
| }`} |
| onScroll={handleScroll} |
| onMouseOver={() => setIsScrolling(true)} |
| onMouseLeave={handleScroll} |
| > |
| {!isEndpointActive || |
| (!isSessionsLoading && |
| (!sessionsData || sessionsData?.length === 0)) ? ( |
| <SessionBlankState /> |
| ) : ( |
| <div className="flex flex-col gap-y-1 pr-1"> |
| {sessionsData?.map((entry, idx) => ( |
| <SessionItem |
| key={`${entry?.session_id}-${idx}`} |
| currentSessionId={selectedSessionId} |
| isSelected={selectedSessionId === entry?.session_id} |
| onSessionClick={handleSessionClick(entry?.session_id)} |
| session_name={entry?.session_name ?? '-'} |
| session_id={entry?.session_id} |
| created_at={entry?.created_at} |
| /> |
| ))} |
| </div> |
| )} |
| </div> |
| </div> |
| ) |
| } |
|
|
| export default Sessions |
|
|