diff --git a/pyproject.toml b/pyproject.toml index cd235b58bde..5d5db9f1bc4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ classifiers = [ requires-python = ">=3.9" dynamic = ["version"] dependencies = [ - "pydantic>=2.0.0", + "pydantic>=2.0.0,<2.10.0", # Pinning to resolve issues caused in 2.10.0 release "wandb>=0.17.1", "packaging>=21.0", # For version parsing in integrations "tenacity>=8.3.0,!=8.4.0", # Excluding 8.4.0 because it had a bug on import of AsyncRetrying diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChatView.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChatView.tsx index 95d36f2a6e2..727731f18c7 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChatView.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChatView.tsx @@ -34,7 +34,7 @@ export const ChatView = ({chat}: ChatViewProps) => { messages={chat.request?.messages || []} scrollLastMessage={scrollLastMessage} /> - {chatResult && chatResult.choices && ( + {chatResult?.choices && chatResult.choices.length > 0 && ( <> Response diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChoiceView.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChoiceView.tsx index d16a12a12ca..c8143f9549e 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChoiceView.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ChoiceView.tsx @@ -15,6 +15,7 @@ export const ChoiceView = ({choice, isStructuredOutput}: ChoiceViewProps) => { index={choice.index} message={message} isStructuredOutput={isStructuredOutput} + isChoice /> ); }; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessageList.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessageList.tsx index 75f189a6a69..300dd854f1e 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessageList.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessageList.tsx @@ -38,17 +38,17 @@ export const MessageList = ({ const processToolCallMessages = (messages: Messages): Messages => { const processedMessages: Message[] = []; for (let i = 0; i < messages.length; i++) { - const message = messages[i]; + const message = { + ...messages[i], + // Store the original index of the message in the message object + // so that we can use it to sort the messages later. + original_index: i, + }; // If there are no tool calls, just add the message to the processed messages // and continue to the next iteration. if (!message.tool_calls) { - processedMessages.push({ - ...message, - // Store the original index of the message in the message object - // so that we can use it to sort the messages later. - original_index: i, - }); + processedMessages.push(message); continue; } @@ -58,7 +58,7 @@ const processToolCallMessages = (messages: Messages): Messages => { while (i + 1 < messages.length && messages[i + 1].role === 'tool') { toolMessages.push({ ...messages[i + 1], - original_index: (messages[i + 1] as any).original_index ?? i + 1, + original_index: i + 1, }); i++; } diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessagePanel.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessagePanel.tsx index b482a1ed89f..3dfec966631 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessagePanel.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/MessagePanel.tsx @@ -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 {PlaygroundMessagePanelButtons} from './PlaygroundMessagePanelButtons'; +import {PlaygroundMessagePanelEditor} from './PlaygroundMessagePanelEditor'; 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( + pendingToolResponseId ? 100 : null + ); const contentRef = useRef(null); + const {isPlayground} = usePlaygroundContext(); useEffect(() => { if (contentRef.current) { setIsOverflowing(contentRef.current.scrollHeight > 400); @@ -37,10 +53,18 @@ 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 (
{!isNested && !isSystemPrompt && ( -
+
{!isUser && !isTool && ( + })} + onMouseEnter={() => setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)}>
{isSystemPrompt && (
@@ -73,7 +100,7 @@ export const MessagePanel = ({ )} {isTool && ( -
+
Response
@@ -86,39 +113,75 @@ export const MessagePanel = ({ 'max-h-[400px]': !isShowingMore, 'max-h-full': isShowingMore, })}> - {hasContent && ( -
- {_.isString(message.content) ? ( - - ) : ( - message.content!.map((p, i) => ( - - )) + {isPlayground && editorHeight ? ( + + ) : ( + <> + {hasContent && ( +
+ {_.isString(message.content) ? ( + + ) : ( + message.content!.map((p, i) => ( + + )) + )} +
)} -
- )} - {hasToolCalls && ( -
- -
+ {hasToolCalls && ( +
+ +
+ )} + )}
- {isOverflowing && ( + + {isOverflowing && !editorHeight && ( )} + + {/* Playground buttons (retry, edit, delete) */} + {isPlayground && isHovering && !editorHeight && ( +
+ +
+ )}
diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/PlaygroundMessagePanelButtons.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/PlaygroundMessagePanelButtons.tsx new file mode 100644 index 00000000000..27709403960 --- /dev/null +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/PlaygroundMessagePanelButtons.tsx @@ -0,0 +1,79 @@ +import {Button} from '@wandb/weave/components/Button'; +import React from 'react'; + +import {usePlaygroundContext} from '../PlaygroundPage/PlaygroundContext'; + +type PlaygroundMessagePanelButtonsProps = { + index: number; + isChoice: boolean; + isTool: boolean; + hasContent: boolean; + contentRef: React.RefObject; + setEditorHeight: (height: number | null) => void; + responseIndexes?: number[]; +}; + +export const PlaygroundMessagePanelButtons: React.FC< + PlaygroundMessagePanelButtonsProps +> = ({ + index, + isChoice, + isTool, + hasContent, + contentRef, + setEditorHeight, + responseIndexes, +}) => { + const {deleteMessage, deleteChoice, retry} = usePlaygroundContext(); + + return ( +
+ + + +
+ ); +}; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/PlaygroundMessagePanelEditor.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/PlaygroundMessagePanelEditor.tsx new file mode 100644 index 00000000000..6d56259125e --- /dev/null +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/PlaygroundMessagePanelEditor.tsx @@ -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 ( +
+ setEditedContent(e.target.value)} + style={{ + minHeight: `${editorHeight}px`, + }} + /> +
+ + +
+
+ ); +}; diff --git a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ToolCalls.tsx b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ToolCalls.tsx index 2a108397657..3c259b1f722 100644 --- a/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ToolCalls.tsx +++ b/weave-js/src/components/PagePanelComponents/Home/Browse3/pages/ChatView/ToolCalls.tsx @@ -3,6 +3,7 @@ import Prism from 'prismjs'; import React, {useEffect, useRef, useState} from 'react'; import {Alert} from '../../../../../Alert'; +import {usePlaygroundContext} from '../PlaygroundPage/PlaygroundContext'; import {MessagePanel} from './MessagePanel'; import {ToolCall} from './types'; @@ -12,6 +13,7 @@ type OneToolCallProps = { const OneToolCall = ({toolCall}: OneToolCallProps) => { const [isCopying, setIsCopying] = useState(false); + const {isPlayground} = usePlaygroundContext(); const handleCopyText = (text: string) => { try { @@ -56,7 +58,7 @@ const OneToolCall = ({toolCall}: OneToolCallProps) => { const copyText = `${name}(${parsedArgs})`; return ( -
+
{/* The tool call header has a copy button */}
@@ -83,10 +85,11 @@ const OneToolCall = ({toolCall}: OneToolCallProps) => {
{/* The tool call response */} - {toolCall.response && ( + {(toolCall.response || isPlayground) && (
{ const [chatText, setChatText] = useState(''); const [isLoading, setIsLoading] = useState(false); - const chatPercentWidth = 100 / playgroundStates.length; const {handleRetry, handleSend} = useChatCompletionFunctions( setPlaygroundStates, @@ -111,13 +110,15 @@ export const PlaygroundChat = ({ height: '100%', display: 'flex', flexDirection: 'column', + position: 'relative', }}> -
+
{state.traceCall && ( { handleMissingLLMApiKey(response, entity); }); } else { - if (responses && responses.api_key && responses.reason) { + if (responses?.api_key && responses.reason) { toast(
{responses.reason}