mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-10-07 14:58:39 +08:00
fix(chat): allow dismissing failed pending user messages (#16142)
Co-authored-by: syf2211 <syf2211@users.noreply.github.com>
This commit is contained in:
@@ -209,6 +209,27 @@ describe("ChatMessage", () => {
|
||||
expect(onStop).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("shows retry and dismiss controls for a failed user message", async () => {
|
||||
const onRetry = vi.fn();
|
||||
const onDismiss = vi.fn();
|
||||
render(
|
||||
<ChatMessage
|
||||
type="user"
|
||||
message="Failed to deliver"
|
||||
pendingStatus="error"
|
||||
onRetry={onRetry}
|
||||
onDismiss={onDismiss}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("chat-message-error")).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByTestId("chat-message-retry"));
|
||||
fireEvent.click(screen.getByTestId("chat-message-dismiss"));
|
||||
|
||||
expect(onRetry).toHaveBeenCalledTimes(1);
|
||||
expect(onDismiss).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("renders a literal angle-bracket user message as visible text", () => {
|
||||
// Regression: user messages were rendered with raw-HTML parsing enabled,
|
||||
// so a message like "<something>" was parsed as an unknown HTML tag and
|
||||
|
||||
@@ -132,6 +132,31 @@ describe("PendingUserMessages", () => {
|
||||
expect(message).toHaveAttribute("data-pending-status", "error");
|
||||
expect(screen.getByTestId("chat-message-error")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("chat-message-retry")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("chat-message-dismiss")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("removes a failed message when dismiss is clicked", async () => {
|
||||
const id = useOptimisticUserMessageStore
|
||||
.getState()
|
||||
.enqueuePendingMessage({
|
||||
conversationId: ACTIVE_CONVO,
|
||||
text: "dismiss me",
|
||||
});
|
||||
useOptimisticUserMessageStore
|
||||
.getState()
|
||||
.markPendingMessageError(id, "Server unavailable");
|
||||
|
||||
renderWithProviders(<PendingUserMessages />);
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.click(screen.getByTestId("chat-message-dismiss"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("user-message")).not.toBeInTheDocument();
|
||||
});
|
||||
expect(useOptimisticUserMessageStore.getState().pendingMessages).toHaveLength(
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
it("re-sends and flips back to 'sending' when retry is clicked", async () => {
|
||||
|
||||
@@ -27,6 +27,7 @@ interface ChatMessageProps {
|
||||
isFromPlanningAgent?: boolean;
|
||||
pendingStatus?: ChatMessagePendingStatus;
|
||||
onRetry?: () => void;
|
||||
onDismiss?: () => void;
|
||||
onStop?: () => void;
|
||||
}
|
||||
|
||||
@@ -38,6 +39,7 @@ export function ChatMessage({
|
||||
isFromPlanningAgent = false,
|
||||
pendingStatus,
|
||||
onRetry,
|
||||
onDismiss,
|
||||
onStop,
|
||||
}: React.PropsWithChildren<ChatMessageProps>) {
|
||||
const { t } = useTranslation("openhands");
|
||||
@@ -256,6 +258,16 @@ export function ChatMessage({
|
||||
{t(I18nKey.CHAT_INTERFACE$MESSAGE_RETRY)}
|
||||
</button>
|
||||
) : null}
|
||||
{onDismiss ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onDismiss}
|
||||
className="cursor-pointer rounded-md border border-[var(--oh-border)] px-2 py-1 text-xs font-normal text-[var(--oh-foreground)] hover:bg-[var(--oh-interactive-hover)]"
|
||||
data-testid="chat-message-dismiss"
|
||||
>
|
||||
{t(I18nKey.CHAT_INTERFACE$MESSAGE_DISMISS)}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -92,6 +92,13 @@ export function PendingUserMessages() {
|
||||
[restoreMessageToInputIfEmpty, removePendingMessage],
|
||||
);
|
||||
|
||||
const handleDismiss = React.useCallback(
|
||||
(id: string) => {
|
||||
removePendingMessage(id);
|
||||
},
|
||||
[removePendingMessage],
|
||||
);
|
||||
|
||||
if (visibleMessages.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -109,6 +116,11 @@ export function PendingUserMessages() {
|
||||
? () => handleRetry(message.id)
|
||||
: undefined
|
||||
}
|
||||
onDismiss={
|
||||
message.status === "error"
|
||||
? () => handleDismiss(message.id)
|
||||
: undefined
|
||||
}
|
||||
onStop={
|
||||
message.status === "sending"
|
||||
? () => handleStop(message.id, message.text)
|
||||
|
||||
@@ -9859,6 +9859,23 @@
|
||||
"uk": "Повторити",
|
||||
"ca": "Torna-ho a provar"
|
||||
},
|
||||
"CHAT_INTERFACE$MESSAGE_DISMISS": {
|
||||
"en": "Dismiss",
|
||||
"ja": "閉じる",
|
||||
"zh-CN": "关闭",
|
||||
"zh-TW": "關閉",
|
||||
"ko-KR": "닫기",
|
||||
"no": "Lukk",
|
||||
"it": "Chiudi",
|
||||
"pt": "Dispensar",
|
||||
"es": "Descartar",
|
||||
"ar": "إغلاق",
|
||||
"fr": "Fermer",
|
||||
"tr": "Kapat",
|
||||
"de": "Schließen",
|
||||
"uk": "Закрити",
|
||||
"ca": "Tanca"
|
||||
},
|
||||
"CHAT_INTERFACE$AUGMENTED_PROMPT_FILES_TITLE": {
|
||||
"en": "NEW FILES ADDED",
|
||||
"de": "NEUE DATEIEN HINZUGEFÜGT",
|
||||
|
||||
Reference in New Issue
Block a user