Skip to content

Commit

Permalink
message panel buttons
Browse files Browse the repository at this point in the history
update choices showing logic

rename stuff
  • Loading branch information
jwlee64 committed Nov 18, 2024
1 parent 3b826ec commit 7c23b39
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const ChatView = ({chat}: ChatViewProps) => {
messages={chat.request?.messages || []}
scrollLastMessage={scrollLastMessage}
/>
{chatResult && chatResult.choices && (
{chatResult?.choices && chatResult.choices.length > 0 && (
<>
<span className="mt-16 text-sm font-semibold text-moon-800">
Response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const ChoiceView = ({choice, isStructuredOutput}: ChoiceViewProps) => {
index={choice.index}
message={message}
isStructuredOutput={isStructuredOutput}
isChoice
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,43 @@ import classNames from 'classnames';
import _ from 'lodash';
import React, {useEffect, useRef, useState} from 'react';

import {usePlaygroundContext} from '../PlaygroundPage/PlaygroundContext';
import {MessagePanelPart} from './MessagePanelPart';
import {PlaygroundMessagePanelEditor} from './PlaygroundMessagePanelEditor';
import {PlaygroundMessagePanelButtons} from './PlaygroundMessagePanelButtons';
import {ShowMoreButton} from './ShowMoreButton';
import {ToolCalls} from './ToolCalls';
import {Message} from './types';
import {Message, ToolCall} from './types';

type MessagePanelProps = {
index: number;
message: Message;
isStructuredOutput?: boolean;
isChoice?: boolean;
isNested?: boolean;
pendingToolResponseId?: string;
};

export const MessagePanel = ({
index,
message,
isStructuredOutput,
isChoice,
isNested,
// The id of the tool call response that is pending
// If the tool call response is pending, the editor will be shown automatically
// and on save the tool call response will be updated and sent to the LLM
pendingToolResponseId,
}: MessagePanelProps) => {
const [isShowingMore, setIsShowingMore] = useState(false);
const [isOverflowing, setIsOverflowing] = useState(false);
const [isHovering, setIsHovering] = useState(false);
const [editorHeight, setEditorHeight] = useState<number | null>(
pendingToolResponseId ? 100 : null
);
const contentRef = useRef<HTMLDivElement>(null);

const {isPlayground} = usePlaygroundContext();
useEffect(() => {
if (contentRef.current) {
setIsOverflowing(contentRef.current.scrollHeight > 400);
Expand All @@ -37,6 +53,14 @@ export const MessagePanel = ({
message.tool_calls != null && message.tool_calls.length > 0;
const hasContent = message.content != null && message.content.length > 0;

const responseIndexes: number[] | undefined = hasToolCalls
? message
.tool_calls!.map(
(toolCall: ToolCall) => toolCall.response?.original_index
)
.filter((idx): idx is number => idx !== undefined)
: undefined;

return (
<div className={classNames('flex gap-8', {'mt-24': !isTool})}>
{!isNested && !isSystemPrompt && (
Expand All @@ -63,7 +87,9 @@ export const MessagePanel = ({
'ml-auto': isUser,
'mr-auto': !isUser,
'py-8': hasContent,
})}>
})}
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}>
<div>
{isSystemPrompt && (
<div className="flex justify-between px-16">
Expand All @@ -87,39 +113,70 @@ export const MessagePanel = ({
'max-h-[400px]': !isShowingMore,
'max-h-full': isShowingMore,
})}>
{hasContent && (
<div
className={classNames(hasToolCalls ? 'pb-8' : '', ' text-sm', {
'px-16': isSystemPrompt || isUser,
})}>
{_.isString(message.content) ? (
<MessagePanelPart
value={message.content}
isStructuredOutput={isStructuredOutput}
/>
) : (
message.content!.map((p, i) => (
<MessagePanelPart key={i} value={p} />
))
{isPlayground && editorHeight ? (
<PlaygroundMessagePanelEditor
message={message}
index={index}
isChoice={isChoice ?? false}
editorHeight={editorHeight}
isNested={isNested ?? false}
pendingToolResponseId={pendingToolResponseId}
setEditorHeight={setEditorHeight}
/>
) : (
<>
{hasContent && (
<div
className={classNames(
hasToolCalls ? 'pb-8' : '',
' text-sm',
{'px-16': isSystemPrompt || isUser}
)}>
{_.isString(message.content) ? (
<MessagePanelPart
value={message.content}
isStructuredOutput={isStructuredOutput}
/>
) : (
message.content!.map((p, i) => (
<MessagePanelPart key={i} value={p} />
))
)}
</div>
)}
</div>
)}
{hasToolCalls && (
<div
className={classNames({
'border-t border-moon-250 pt-8': hasContent,
})}>
<ToolCalls toolCalls={message.tool_calls!} />
</div>
{hasToolCalls && (
<div
className={classNames({
'border-t border-moon-250 pt-8': hasContent,
})}>
<ToolCalls toolCalls={message.tool_calls!} />
</div>
)}
</>
)}
</div>
{isOverflowing && (

{isOverflowing && !editorHeight && (
<ShowMoreButton
isUser={isUser}
isShowingMore={isShowingMore}
setIsShowingMore={setIsShowingMore}
/>
)}

{/* Playground buttons (retry, edit, delete) */}
{isPlayground && isHovering && !editorHeight && (
<PlaygroundMessagePanelButtons
index={index}
isChoice={isChoice ?? false}
isTool={isTool}
hasContent={hasContent}
isNested={isNested ?? false}
contentRef={contentRef}
setEditorHeight={setEditorHeight}
responseIndexes={responseIndexes}
/>
)}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import {Button} from '@wandb/weave/components/Button';
import classNames from 'classnames';
import React from 'react';

import {usePlaygroundContext} from '../PlaygroundPage/PlaygroundContext';

type PlaygroundMessagePanelButtonsProps = {
index: number;
isChoice: boolean;
isTool: boolean;
hasContent: boolean;
isNested: boolean;
contentRef: React.RefObject<HTMLDivElement>;
setEditorHeight: (height: number | null) => void;
responseIndexes?: number[];
};

export const PlaygroundMessagePanelButtons: React.FC<
PlaygroundMessagePanelButtonsProps
> = ({
index,
isChoice,
isTool,
hasContent,
isNested,
contentRef,
setEditorHeight,
responseIndexes,
}) => {
const {deleteMessage, deleteChoice, retry} = usePlaygroundContext();

return (
<div
className={classNames(
'absolute right-0 flex w-full items-center justify-end pt-20',
isNested ? 'bottom-0' : 'bottom-[-32px]'
)}>
<div className="z-10 flex gap-4 rounded-lg border border-moon-250 bg-white p-4">
<Button
variant="quiet"
size="small"
startIcon="randomize-reset-reload"
onClick={() => retry?.(index, isChoice)}
tooltip={
!hasContent
? 'We currently do not support retrying functions'
: 'Retry'
}
disabled={!hasContent}>
Retry
</Button>
<Button
variant="quiet"
size="small"
startIcon="pencil-edit"
onClick={() => {
setEditorHeight(
contentRef?.current?.clientHeight
? // Accounts for padding and save buttons
contentRef.current.clientHeight - 56
: null
);
}}
tooltip={
!hasContent
? 'We currently do not support editing functions'
: 'Edit'
}
disabled={!hasContent}>
Edit
</Button>
<Button
variant="quiet"
size="small"
startIcon="delete"
onClick={() => {
if (isChoice) {
deleteChoice?.(index);
} else {
deleteMessage?.(index, responseIndexes);
}
}}
tooltip={
isTool ? 'Tool responses cannot be deleted' : 'Delete message'
}
disabled={isTool}>
Delete
</Button>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {Button} from '@wandb/weave/components/Button';
import classNames from 'classnames';
import _ from 'lodash';
import React, {useEffect, useMemo, useState} from 'react';

import {usePlaygroundContext} from '../PlaygroundPage/PlaygroundContext';
import {StyledTextArea} from '../PlaygroundPage/StyledTextarea';
import {Message} from './types';

type PlaygroundMessagePanelEditorProps = {
editorHeight: number;
isNested: boolean;
pendingToolResponseId?: string;
message: Message;
index: number;
isChoice: boolean;
setEditorHeight: (height: number | null) => void;
};

export const PlaygroundMessagePanelEditor: React.FC<
PlaygroundMessagePanelEditorProps
> = ({
index,
isChoice,
setEditorHeight,
editorHeight,
isNested,
pendingToolResponseId,
message,
}) => {
const {sendMessage, editMessage, editChoice} = usePlaygroundContext();

const initialContent = useMemo(
() =>
_.isString(message.content)
? message.content
: message.content?.join('') ?? '',
[message.content]
);

const [editedContent, setEditedContent] = useState(initialContent);

useEffect(() => {
setEditedContent(initialContent);
}, [initialContent]);

const handleSave = () => {
if (isChoice) {
editChoice?.(index, {
content: editedContent,
role: message.role,
});
} else {
editMessage?.(index, {
...message,
content: editedContent,
});
}
setEditorHeight(null);
};

const handleCancel = () => {
setEditedContent(initialContent);
setEditorHeight(null);
};

return (
<div
className={classNames(
'w-full pt-16 text-sm',
isNested ? 'px-2' : 'px-16'
)}>
<StyledTextArea
value={editedContent}
onChange={e => setEditedContent(e.target.value)}
style={{
minHeight: `${editorHeight}px`,
}}
/>
<div className="z-100 mt-8 flex justify-end gap-8">
<Button variant="quiet" size="small" onClick={handleCancel}>
Cancel
</Button>
<Button
variant="primary"
size="small"
onClick={
pendingToolResponseId
? () =>
sendMessage?.(
'tool',
editedContent ?? '',
pendingToolResponseId
)
: handleSave
}>
Save
</Button>
</div>
</div>
);
};
Loading

0 comments on commit 7c23b39

Please sign in to comment.