mirror of
https://github.com/aljazceru/goose.git
synced 2025-12-20 15:44:25 +01:00
fix: continue to use resumed session after confirmation is cancelled (#1548)
This commit is contained in:
@@ -29,6 +29,9 @@ import {
|
|||||||
export interface ChatType {
|
export interface ChatType {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
|
// messages up to this index are presumed to be "history" from a resumed session, this is used to track older tool confirmation requests
|
||||||
|
// anything before this index should not render any buttons, but anything after should
|
||||||
|
messageHistoryIndex: number;
|
||||||
messages: Message[];
|
messages: Message[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +79,7 @@ export default function ChatView({
|
|||||||
return {
|
return {
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
title: resumedSession.metadata?.description || `ID: ${resumedSession.session_id}`,
|
title: resumedSession.metadata?.description || `ID: ${resumedSession.session_id}`,
|
||||||
|
messageHistoryIndex: convertedMessages.length,
|
||||||
messages: convertedMessages,
|
messages: convertedMessages,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -98,6 +102,7 @@ export default function ChatView({
|
|||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
title: 'Chat 1',
|
title: 'Chat 1',
|
||||||
messages: [],
|
messages: [],
|
||||||
|
messageHistoryIndex: 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const [messageMetadata, setMessageMetadata] = useState<Record<string, string[]>>({});
|
const [messageMetadata, setMessageMetadata] = useState<Record<string, string[]>>({});
|
||||||
@@ -319,10 +324,15 @@ export default function ChatView({
|
|||||||
<UserMessage message={message} />
|
<UserMessage message={message} />
|
||||||
) : (
|
) : (
|
||||||
<GooseMessage
|
<GooseMessage
|
||||||
|
messageHistoryIndex={chat?.messageHistoryIndex}
|
||||||
message={message}
|
message={message}
|
||||||
messages={messages}
|
messages={messages}
|
||||||
metadata={messageMetadata[message.id || '']}
|
metadata={messageMetadata[message.id || '']}
|
||||||
append={(text) => append(createUserMessage(text))}
|
append={(text) => append(createUserMessage(text))}
|
||||||
|
appendMessage={(newMessage) => {
|
||||||
|
const updatedMessages = [...messages, newMessage];
|
||||||
|
setMessages(updatedMessages);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo, useRef } from 'react';
|
import React, { useEffect, useMemo, useRef } from 'react';
|
||||||
import LinkPreview from './LinkPreview';
|
import LinkPreview from './LinkPreview';
|
||||||
import GooseResponseForm from './GooseResponseForm';
|
import GooseResponseForm from './GooseResponseForm';
|
||||||
import { extractUrls } from '../utils/urlUtils';
|
import { extractUrls } from '../utils/urlUtils';
|
||||||
@@ -10,18 +10,28 @@ import {
|
|||||||
getToolRequests,
|
getToolRequests,
|
||||||
getToolResponses,
|
getToolResponses,
|
||||||
getToolConfirmationContent,
|
getToolConfirmationContent,
|
||||||
|
createToolErrorResponseMessage,
|
||||||
} from '../types/message';
|
} from '../types/message';
|
||||||
import ToolCallConfirmation from './ToolCallConfirmation';
|
import ToolCallConfirmation from './ToolCallConfirmation';
|
||||||
import MessageCopyLink from './MessageCopyLink';
|
import MessageCopyLink from './MessageCopyLink';
|
||||||
|
|
||||||
interface GooseMessageProps {
|
interface GooseMessageProps {
|
||||||
|
messageHistoryIndex: number;
|
||||||
message: Message;
|
message: Message;
|
||||||
messages: Message[];
|
messages: Message[];
|
||||||
metadata?: string[];
|
metadata?: string[];
|
||||||
append: (value: string) => void;
|
append: (value: string) => void;
|
||||||
|
appendMessage: (message: Message) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function GooseMessage({ message, metadata, messages, append }: GooseMessageProps) {
|
export default function GooseMessage({
|
||||||
|
messageHistoryIndex,
|
||||||
|
message,
|
||||||
|
metadata,
|
||||||
|
messages,
|
||||||
|
append,
|
||||||
|
appendMessage,
|
||||||
|
}: GooseMessageProps) {
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Extract text content from the message
|
// Extract text content from the message
|
||||||
@@ -64,6 +74,16 @@ export default function GooseMessage({ message, metadata, messages, append }: Go
|
|||||||
return responseMap;
|
return responseMap;
|
||||||
}, [messages, messageIndex, toolRequests]);
|
}, [messages, messageIndex, toolRequests]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// If the message is the last message in the resumed session and has tool confirmation, it means the tool confirmation
|
||||||
|
// is broken or cancelled, to contonue use the session, we need to append a tool response to avoid mismatch tool result error.
|
||||||
|
if (messageIndex == messageHistoryIndex - 1 && hasToolConfirmation) {
|
||||||
|
appendMessage(
|
||||||
|
createToolErrorResponseMessage(toolConfirmationContent.id, 'The tool call is cancelled.')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="goose-message flex w-[90%] justify-start opacity-0 animate-[appear_150ms_ease-in_forwards]">
|
<div className="goose-message flex w-[90%] justify-start opacity-0 animate-[appear_150ms_ease-in_forwards]">
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
@@ -86,17 +106,15 @@ export default function GooseMessage({ message, metadata, messages, append }: Go
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hasToolConfirmation && (
|
|
||||||
<ToolCallConfirmation
|
|
||||||
toolConfirmationId={toolConfirmationContent.id}
|
|
||||||
toolName={toolConfirmationContent.toolName}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{toolRequests.length > 0 && (
|
{toolRequests.length > 0 && (
|
||||||
<div className="goose-message-tool bg-bgApp border border-borderSubtle dark:border-gray-700 rounded-b-2xl px-4 pt-4 pb-2 mt-1">
|
<div className="goose-message-tool bg-bgApp border border-borderSubtle dark:border-gray-700 rounded-b-2xl px-4 pt-4 pb-2 mt-1">
|
||||||
{toolRequests.map((toolRequest) => (
|
{toolRequests.map((toolRequest) => (
|
||||||
<ToolCallWithResponse
|
<ToolCallWithResponse
|
||||||
|
// If the message is resumed and not matched tool response, it means the tool is broken or cancelled.
|
||||||
|
isCancelledMessage={
|
||||||
|
messageIndex < messageHistoryIndex &&
|
||||||
|
toolResponsesMap.get(toolRequest.id) == undefined
|
||||||
|
}
|
||||||
key={toolRequest.id}
|
key={toolRequest.id}
|
||||||
toolRequest={toolRequest}
|
toolRequest={toolRequest}
|
||||||
toolResponse={toolResponsesMap.get(toolRequest.id)}
|
toolResponse={toolResponsesMap.get(toolRequest.id)}
|
||||||
@@ -104,6 +122,15 @@ export default function GooseMessage({ message, metadata, messages, append }: Go
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{hasToolConfirmation && (
|
||||||
|
<ToolCallConfirmation
|
||||||
|
isCancelledMessage={messageIndex == messageHistoryIndex - 1}
|
||||||
|
isClicked={messageIndex < messageHistoryIndex - 1}
|
||||||
|
toolConfirmationId={toolConfirmationContent.id}
|
||||||
|
toolName={toolConfirmationContent.toolName}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* TODO(alexhancock): Re-enable link previews once styled well again */}
|
{/* TODO(alexhancock): Re-enable link previews once styled well again */}
|
||||||
|
|||||||
@@ -2,9 +2,14 @@ import React, { useState } from 'react';
|
|||||||
import { ConfirmToolRequest } from '../utils/toolConfirm';
|
import { ConfirmToolRequest } from '../utils/toolConfirm';
|
||||||
import { snakeToTitleCase } from '../utils';
|
import { snakeToTitleCase } from '../utils';
|
||||||
|
|
||||||
export default function ToolConfirmation({ toolConfirmationId, toolName }) {
|
export default function ToolConfirmation({
|
||||||
const [clicked, setClicked] = useState(false);
|
isCancelledMessage,
|
||||||
const [status, setStatus] = useState('');
|
isClicked,
|
||||||
|
toolConfirmationId,
|
||||||
|
toolName,
|
||||||
|
}) {
|
||||||
|
const [clicked, setClicked] = useState(isClicked);
|
||||||
|
const [status, setStatus] = useState('unknown');
|
||||||
|
|
||||||
const handleButtonClick = (confirmed) => {
|
const handleButtonClick = (confirmed) => {
|
||||||
setClicked(true);
|
setClicked(true);
|
||||||
@@ -12,7 +17,11 @@ export default function ToolConfirmation({ toolConfirmationId, toolName }) {
|
|||||||
ConfirmToolRequest(toolConfirmationId, confirmed);
|
ConfirmToolRequest(toolConfirmationId, confirmed);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return isCancelledMessage ? (
|
||||||
|
<div className="goose-message-content bg-bgSubtle rounded-2xl px-4 py-2 text-textStandard">
|
||||||
|
Tool call confirmation is cancelled.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="goose-message-content bg-bgSubtle rounded-2xl px-4 py-2 rounded-b-none text-textStandard">
|
<div className="goose-message-content bg-bgSubtle rounded-2xl px-4 py-2 rounded-b-none text-textStandard">
|
||||||
Goose would like to call the above tool. Allow?
|
Goose would like to call the above tool. Allow?
|
||||||
@@ -45,7 +54,9 @@ export default function ToolConfirmation({ toolConfirmationId, toolName }) {
|
|||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
<span className="ml-2 text-textStandard">
|
<span className="ml-2 text-textStandard">
|
||||||
{snakeToTitleCase(toolName.substring(toolName.lastIndexOf('__') + 2))} is {status}
|
{isClicked
|
||||||
|
? 'Tool confirmation is not available'
|
||||||
|
: `${snakeToTitleCase(toolName.substring(toolName.lastIndexOf('__') + 2))} is ${status}`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ import { Content, ToolRequestMessageContent, ToolResponseMessageContent } from '
|
|||||||
import { snakeToTitleCase } from '../utils';
|
import { snakeToTitleCase } from '../utils';
|
||||||
|
|
||||||
interface ToolCallWithResponseProps {
|
interface ToolCallWithResponseProps {
|
||||||
|
isCancelledMessage: boolean;
|
||||||
toolRequest: ToolRequestMessageContent;
|
toolRequest: ToolRequestMessageContent;
|
||||||
toolResponse?: ToolResponseMessageContent;
|
toolResponse?: ToolResponseMessageContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ToolCallWithResponse({
|
export default function ToolCallWithResponse({
|
||||||
|
isCancelledMessage,
|
||||||
toolRequest,
|
toolRequest,
|
||||||
toolResponse,
|
toolResponse,
|
||||||
}: ToolCallWithResponseProps) {
|
}: ToolCallWithResponseProps) {
|
||||||
@@ -27,7 +29,8 @@ export default function ToolCallWithResponse({
|
|||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Card className="">
|
<Card className="">
|
||||||
<ToolCallView toolCall={toolCall} />
|
<ToolCallView toolCall={toolCall} />
|
||||||
{toolResponse ? (
|
{!isCancelledMessage ? (
|
||||||
|
toolResponse ? (
|
||||||
<ToolResultView
|
<ToolResultView
|
||||||
result={
|
result={
|
||||||
toolResponse.toolResult.status === 'success'
|
toolResponse.toolResult.status === 'success'
|
||||||
@@ -37,7 +40,8 @@ export default function ToolCallWithResponse({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<LoadingPlaceholder />
|
<LoadingPlaceholder />
|
||||||
)}
|
)
|
||||||
|
) : undefined}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -166,6 +166,11 @@ const SessionHistoryView: React.FC<SessionHistoryViewProps> = ({
|
|||||||
<div className="goose-message-tool bg-bgApp border border-borderSubtle dark:border-gray-700 rounded-b-2xl px-4 pt-4 pb-2 mt-1">
|
<div className="goose-message-tool bg-bgApp border border-borderSubtle dark:border-gray-700 rounded-b-2xl px-4 pt-4 pb-2 mt-1">
|
||||||
{toolRequests.map((toolRequest) => (
|
{toolRequests.map((toolRequest) => (
|
||||||
<ToolCallWithResponse
|
<ToolCallWithResponse
|
||||||
|
// In the session history page, if no tool response found for given request, it means the tool call
|
||||||
|
// is broken or cancelled.
|
||||||
|
isCancelledMessage={
|
||||||
|
toolResponsesMap.get(toolRequest.id) == undefined
|
||||||
|
}
|
||||||
key={toolRequest.id}
|
key={toolRequest.id}
|
||||||
toolRequest={toolRequest}
|
toolRequest={toolRequest}
|
||||||
toolResponse={toolResponsesMap.get(toolRequest.id)}
|
toolResponse={toolResponsesMap.get(toolRequest.id)}
|
||||||
|
|||||||
Reference in New Issue
Block a user