Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved Chat / Transcript Virtualization #1088

Merged
merged 29 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7f490dc
Improve json parse performance
dragonstyle Jan 6, 2025
9b9b8f3
Properly support hours in time formatting
dragonstyle Jan 6, 2025
bbb8c35
Modernize virtual list
dragonstyle Jan 7, 2025
633d2b8
Add support for dynamic row sizes
dragonstyle Jan 7, 2025
80f7260
Add support for optional parent scroll ref
dragonstyle Jan 7, 2025
617174c
Add virtuoso
dragonstyle Jan 5, 2025
cae966c
Convert transcript to virtual list
dragonstyle Jan 5, 2025
f94ee1b
Convert Chatview to Virtual list
dragonstyle Jan 6, 2025
427d518
Add nested transcript virtualization
dragonstyle Jan 7, 2025
35da2b3
Use viewport sizing
dragonstyle Jan 7, 2025
f1924de
lint
dragonstyle Jan 7, 2025
dce4c01
Improve Chatview scrolling
dragonstyle Jan 7, 2025
f404161
minor tweaks
dragonstyle Jan 7, 2025
6d646f8
Fix issues with scrollRef in Virtual List
dragonstyle Jan 7, 2025
3015e35
Convert chat view to use our own virtual list
dragonstyle Jan 7, 2025
c7a3170
Convert transcript to built in virtual list
dragonstyle Jan 7, 2025
5cd0a54
remove virtuoso
dragonstyle Jan 7, 2025
d65ffab
layout lint
dragonstyle Jan 7, 2025
e456ad7
memoize row rendering
dragonstyle Jan 8, 2025
9ee8177
memoize row positions
dragonstyle Jan 8, 2025
e43920b
batch row measurement updates
dragonstyle Jan 8, 2025
43188ff
handle edge cases in binary search
dragonstyle Jan 8, 2025
ea8a71f
Initialize the total height to the estimated height
dragonstyle Jan 8, 2025
b3e0b3e
Improve API call highlighting
dragonstyle Jan 8, 2025
3f4de83
Scroll to index support
dragonstyle Jan 8, 2025
5c4a850
Remove disused TranscriptState
dragonstyle Jan 9, 2025
5ed4f85
Move transcript event panel state up the stack
dragonstyle Jan 9, 2025
68c9270
Single state update when measuring rows
dragonstyle Jan 9, 2025
2b11c8f
Don’t serialize large samples in vscode
dragonstyle Jan 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,278 changes: 1,534 additions & 744 deletions src/inspect_ai/_view/www/dist/assets/index.js

Large diffs are not rendered by default.

176 changes: 133 additions & 43 deletions src/inspect_ai/_view/www/src/components/ChatView.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,54 @@ import { MessageContent } from "./MessageContent.mjs";
import { ExpandablePanel } from "./ExpandablePanel.mjs";
import { FontSize, TextStyle } from "../appearance/Fonts.mjs";
import { resolveToolInput, ToolCallView } from "./Tools.mjs";
import { VirtualList } from "./VirtualList.mjs";

/**
* Renders the ChatViewVirtualList component.
*
* @param {Object} props - The properties passed to the component.
* @param {string} props.id - The ID for the chat view.
* @param {import("../types/log").Messages} props.messages - The array of chat messages.
* @param {"compact" | "complete"} [props.toolCallStyle] - Whether to show tool calls
* @param {Object} [props.style] - Inline styles for the chat view.
* @param {boolean} props.indented - Whether the chatview has indented messages
* @param {boolean} [props.numbered] - Whether the chatview is numbered
* @param {import("htm/preact").MutableRef<HTMLElement>} props.scrollRef - The scrollable parent element
* @returns {import("preact").JSX.Element} The component.
*/
export const ChatViewVirtualList = ({
id,
messages,
toolCallStyle,
style,
indented,
numbered = true,
scrollRef,
}) => {
const collapsedMessages = resolveMessages(messages);

const renderRow = (item, index) => {
const number =
collapsedMessages.length > 1 && numbered ? index + 1 : undefined;
return html`<${ChatMessageRow}
id=${id}
number=${number}
resolvedMessage=${item}
indented=${indented}
toolCallStyle=${toolCallStyle}
/>`;
};

const result = html`<${VirtualList}
data=${collapsedMessages}
tabIndex="0"
renderRow=${renderRow}
scrollRef=${scrollRef}
style=${{ width: "100%", marginTop: "1em", ...style }}
/>`;

return result;
};

/**
* Renders the ChatView component.
Expand All @@ -28,6 +76,90 @@ export const ChatView = ({
indented,
numbered = true,
}) => {
const collapsedMessages = resolveMessages(messages);
const result = html` <div style=${style}>
${collapsedMessages.map((msg, index) => {
const number =
collapsedMessages.length > 1 && numbered ? index + 1 : undefined;
return html`<${ChatMessageRow}
id=${id}
number=${number}
resolvedMessage=${msg}
indented=${indented}
toolCallStyle=${toolCallStyle}
/>`;
})}
</div>`;
return result;
};

/**
* Renders the ChatMessage component.
*
* @param {Object} props - The properties passed to the component.
* @param {string} props.id - The ID for the chat view.
* @param {number} [props.number] - The message number
* @param {ResolvedMessage} props.resolvedMessage - The array of chat messages.
* @param {"compact" | "complete"} [props.toolCallStyle] - Whether to show tool calls
* @param {boolean} props.indented - Whether the chatview has indented messages
* @returns {import("preact").JSX.Element} The component.
*/
export const ChatMessageRow = ({
id,
number,
resolvedMessage,
toolCallStyle,
indented,
}) => {
if (number) {
return html` <div
style=${{
display: "grid",
gridTemplateColumns: "max-content auto",
columnGap: "0.4em",
}}
>
<div
style=${{
fontSize: FontSize.smaller,
...TextStyle.secondary,
marginTop: "0.1em",
}}
>
${number}
</div>
<${ChatMessage}
id=${`${id}-chat-messages`}
message=${resolvedMessage.message}
toolMessages=${resolvedMessage.toolMessages}
indented=${indented}
toolCallStyle=${toolCallStyle}
/>
</div>`;
} else {
return html`<${ChatMessage}
id=${`${id}-chat-messages`}
message=${resolvedMessage.message}
toolMessages=${resolvedMessage.toolMessages}
indented=${indented}
toolCallStyle=${toolCallStyle}
/>`;
}
};

/**
* @typedef {Object} ResolvedMessage
* @property {import("../types/log").ChatMessageAssistant | import("../types/log").ChatMessageSystem | import("../types/log").ChatMessageUser} message - The main chat message.
* @property {import("../types/log").ChatMessageTool[]} [toolMessages] - Optional array of tool-related messages.
*/

/**
* Renders the ChatView component.
*
* @param {import("../types/log").Messages} messages - The array of chat messages.
* @returns {ResolvedMessage[]} The component.
*/
export const resolveMessages = (messages) => {
// Filter tool messages into a sidelist that the chat stream
// can use to lookup the tool responses

Expand Down Expand Up @@ -88,49 +220,7 @@ export const ChatView = ({
if (systemMessage && systemMessage.content.length > 0) {
collapsedMessages.unshift({ message: systemMessage });
}

const result = html`
<div style=${style}>
${collapsedMessages.map((msg, index) => {
if (collapsedMessages.length > 1 && numbered) {
return html` <div
style=${{
display: "grid",
gridTemplateColumns: "max-content auto",
columnGap: "0.4em",
}}
>
<div
style=${{
fontSize: FontSize.smaller,
...TextStyle.secondary,
marginTop: "0.1em",
}}
>
${index + 1}
</div>
<${ChatMessage}
id=${`${id}-chat-messages`}
message=${msg.message}
toolMessages=${msg.toolMessages}
indented=${indented}
toolCallStyle=${toolCallStyle}
/>
</div>`;
} else {
return html` <${ChatMessage}
id=${`${id}-chat-messages`}
message=${msg.message}
toolMessages=${msg.toolMessages}
indented=${indented}
toolCallStyle=${toolCallStyle}
/>`;
}
})}
</div>
`;

return result;
return collapsedMessages;
};

/**
Expand Down
4 changes: 0 additions & 4 deletions src/inspect_ai/_view/www/src/components/ExpandablePanel.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@ export const ExpandablePanel = ({
contentsStyle.border = "solid var(--bs-light-border-subtle) 1px";
}

if (!showToggle) {
contentsStyle.marginBottom = "1em";
}

return html`<div
class="expandable-panel"
ref=${contentsRef}
Expand Down
39 changes: 19 additions & 20 deletions src/inspect_ai/_view/www/src/components/LargeModal.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,32 @@ import { FontSize } from "../appearance/Fonts.mjs";
import { ProgressBar } from "./ProgressBar.mjs";
import { MessageBand } from "./MessageBand.mjs";

export const LargeModal = (props) => {
const {
id,
title,
detail,
detailTools,
footer,
onkeyup,
visible,
onHide,
showProgress,
children,
initialScrollPositionRef,
setInitialScrollPosition,
warning,
warningHidden,
setWarningHidden,
} = props;

export const LargeModal = ({
id,
title,
detail,
detailTools,
footer,
onkeyup,
visible,
onHide,
showProgress,
children,
initialScrollPositionRef,
setInitialScrollPosition,
warning,
warningHidden,
setWarningHidden,
scrollRef,
}) => {
// The footer
const modalFooter = footer
? html`<div class="modal-footer">${footer}</div>`
: "";

// Support restoring the scroll position
// but only do this for the first time that the children are set
const scrollRef = useRef(/** @type {HTMLElement|null} */ (null));
scrollRef = scrollRef || useRef(/** @type {HTMLElement|null} */ (null));
useEffect(() => {
if (scrollRef.current) {
setTimeout(() => {
Expand Down
4 changes: 3 additions & 1 deletion src/inspect_ai/_view/www/src/components/TabSet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ export const TabPanel = ({
selected,
style,
scrollable,
scrollRef,
classes,
scrollPosition,
setScrollPosition,
children,
}) => {
const tabContentsId = computeTabContentsId(id, index);
const tabContentsRef = useRef(/** @type {HTMLElement|null} */ (null));
const tabContentsRef =
scrollRef || useRef(/** @type {HTMLElement|null} */ (null));
useEffect(() => {
setTimeout(() => {
if (
Expand Down
Loading
Loading