2025-09-12 18:09:14 +01:00
|
|
|
import {
|
|
|
|
|
DataWithScrollModifier,
|
|
|
|
|
ScrollModifier,
|
2025-09-15 15:17:53 +02:00
|
|
|
VirtuosoMessageList,
|
|
|
|
|
VirtuosoMessageListLicense,
|
|
|
|
|
VirtuosoMessageListMethods,
|
|
|
|
|
VirtuosoMessageListProps,
|
2025-09-12 18:09:14 +01:00
|
|
|
} from '@virtuoso.dev/message-list';
|
|
|
|
|
import { useEffect, useRef, useState } from 'react';
|
|
|
|
|
import DisplayConversationEntry from '../NormalizedConversation/DisplayConversationEntry';
|
2025-09-18 11:22:10 +01:00
|
|
|
import { useEntries } from '@/contexts/EntriesContext';
|
2025-09-12 18:09:14 +01:00
|
|
|
import {
|
|
|
|
|
AddEntryType,
|
2025-09-15 15:17:53 +02:00
|
|
|
PatchTypeWithKey,
|
|
|
|
|
useConversationHistory,
|
2025-09-12 18:09:14 +01:00
|
|
|
} from '@/hooks/useConversationHistory';
|
|
|
|
|
import { TaskAttempt } from 'shared/types';
|
|
|
|
|
import { Loader2 } from 'lucide-react';
|
|
|
|
|
|
|
|
|
|
interface VirtualizedListProps {
|
|
|
|
|
attempt: TaskAttempt;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-17 16:29:01 +01:00
|
|
|
interface MessageListContext {
|
|
|
|
|
attempt: TaskAttempt;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-12 18:09:14 +01:00
|
|
|
type ChannelData = DataWithScrollModifier<PatchTypeWithKey> | null;
|
|
|
|
|
|
|
|
|
|
const InitialDataScrollModifier: ScrollModifier = {
|
|
|
|
|
type: 'item-location',
|
|
|
|
|
location: {
|
|
|
|
|
index: 'LAST',
|
|
|
|
|
align: 'end',
|
|
|
|
|
},
|
|
|
|
|
purgeItemSizes: true,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const AutoScrollToBottom: ScrollModifier = {
|
|
|
|
|
type: 'auto-scroll-to-bottom',
|
|
|
|
|
autoScroll: ({ atBottom, scrollInProgress }) => {
|
|
|
|
|
if (atBottom || scrollInProgress) {
|
|
|
|
|
return 'smooth';
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-17 16:29:01 +01:00
|
|
|
const ItemContent: VirtuosoMessageListProps<
|
|
|
|
|
PatchTypeWithKey,
|
|
|
|
|
MessageListContext
|
|
|
|
|
>['ItemContent'] = ({ data, context }) => {
|
|
|
|
|
if (data.type === 'STDOUT') {
|
|
|
|
|
return <p>{data.content}</p>;
|
|
|
|
|
} else if (data.type === 'STDERR') {
|
|
|
|
|
return <p>{data.content}</p>;
|
|
|
|
|
} else if (data.type === 'NORMALIZED_ENTRY') {
|
|
|
|
|
return (
|
|
|
|
|
<DisplayConversationEntry
|
|
|
|
|
key={data.patchKey}
|
|
|
|
|
expansionKey={data.patchKey}
|
|
|
|
|
entry={data.content}
|
|
|
|
|
executionProcessId={data.executionProcessId}
|
|
|
|
|
taskAttempt={context.attempt}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-12 18:09:14 +01:00
|
|
|
const VirtualizedList = ({ attempt }: VirtualizedListProps) => {
|
|
|
|
|
const [channelData, setChannelData] = useState<ChannelData>(null);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
2025-09-18 11:22:10 +01:00
|
|
|
const { setEntries, reset } = useEntries();
|
2025-09-12 18:09:14 +01:00
|
|
|
|
2025-09-18 11:22:10 +01:00
|
|
|
// When attempt changes, set loading and reset entries
|
2025-09-12 18:09:14 +01:00
|
|
|
useEffect(() => {
|
|
|
|
|
setLoading(true);
|
2025-09-18 11:22:10 +01:00
|
|
|
reset();
|
|
|
|
|
}, [attempt.id, reset]);
|
2025-09-12 18:09:14 +01:00
|
|
|
|
|
|
|
|
const onEntriesUpdated = (
|
|
|
|
|
newEntries: PatchTypeWithKey[],
|
|
|
|
|
addType: AddEntryType,
|
|
|
|
|
newLoading: boolean
|
|
|
|
|
) => {
|
|
|
|
|
// initial defaults to scrolling to the latest
|
|
|
|
|
let scrollModifier: ScrollModifier = InitialDataScrollModifier;
|
|
|
|
|
|
|
|
|
|
if (addType === 'running' && !loading) {
|
|
|
|
|
scrollModifier = AutoScrollToBottom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setChannelData({ data: newEntries, scrollModifier });
|
2025-09-18 11:22:10 +01:00
|
|
|
setEntries(newEntries); // Update shared context
|
2025-09-12 18:09:14 +01:00
|
|
|
if (loading) {
|
|
|
|
|
setLoading(newLoading);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
useConversationHistory({ attempt, onEntriesUpdated });
|
|
|
|
|
|
|
|
|
|
const messageListRef = useRef<VirtuosoMessageListMethods | null>(null);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<VirtuosoMessageListLicense
|
2025-09-15 15:17:53 +02:00
|
|
|
licenseKey={import.meta.env.VITE_PUBLIC_REACT_VIRTUOSO_LICENSE_KEY}
|
2025-09-12 18:09:14 +01:00
|
|
|
>
|
2025-09-17 16:29:01 +01:00
|
|
|
<VirtuosoMessageList<PatchTypeWithKey, MessageListContext>
|
2025-09-12 18:09:14 +01:00
|
|
|
ref={messageListRef}
|
|
|
|
|
className="flex-1"
|
|
|
|
|
data={channelData}
|
2025-09-17 16:29:01 +01:00
|
|
|
context={{ attempt }}
|
|
|
|
|
itemIdentity={(item) => item.patchKey}
|
|
|
|
|
computeItemKey={({ data }) => data.patchKey}
|
2025-09-12 18:09:14 +01:00
|
|
|
ItemContent={ItemContent}
|
|
|
|
|
Header={() => <div className="h-2"></div>} // Padding
|
|
|
|
|
Footer={() => <div className="h-2"></div>} // Padding
|
|
|
|
|
/>
|
|
|
|
|
</VirtuosoMessageListLicense>
|
|
|
|
|
{loading && (
|
|
|
|
|
<div className="float-left top-0 left-0 w-full h-full bg-primary flex flex-col gap-2 justify-center items-center">
|
|
|
|
|
<Loader2 className="h-8 w-8 animate-spin" />
|
|
|
|
|
<p>Loading History</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default VirtualizedList;
|