setContextMenuVisible(false)}
onDelete={onDelete && handleDelete}
+ onStop={
+ conversationStatus !== "STOPPED"
+ ? onStop && handleStop
+ : undefined
+ }
onEdit={onChangeTitle && handleEdit}
onDownloadViaVSCode={
conversationId && showOptions
diff --git a/frontend/src/components/features/conversation-panel/conversation-panel.tsx b/frontend/src/components/features/conversation-panel/conversation-panel.tsx
index decf9e6cd3..34b8ce9269 100644
--- a/frontend/src/components/features/conversation-panel/conversation-panel.tsx
+++ b/frontend/src/components/features/conversation-panel/conversation-panel.tsx
@@ -5,7 +5,9 @@ import { I18nKey } from "#/i18n/declaration";
import { ConversationCard } from "./conversation-card";
import { useUserConversations } from "#/hooks/query/use-user-conversations";
import { useDeleteConversation } from "#/hooks/mutation/use-delete-conversation";
+import { useStopConversation } from "#/hooks/mutation/use-stop-conversation";
import { ConfirmDeleteModal } from "./confirm-delete-modal";
+import { ConfirmStopModal } from "./confirm-stop-modal";
import { LoadingSpinner } from "#/components/shared/loading-spinner";
import { ExitConversationModal } from "./exit-conversation-modal";
import { useClickOutsideElement } from "#/hooks/use-click-outside-element";
@@ -22,6 +24,8 @@ export function ConversationPanel({ onClose }: ConversationPanelProps) {
const [confirmDeleteModalVisible, setConfirmDeleteModalVisible] =
React.useState(false);
+ const [confirmStopModalVisible, setConfirmStopModalVisible] =
+ React.useState(false);
const [
confirmExitConversationModalVisible,
setConfirmExitConversationModalVisible,
@@ -33,12 +37,18 @@ export function ConversationPanel({ onClose }: ConversationPanelProps) {
const { data: conversations, isFetching, error } = useUserConversations();
const { mutate: deleteConversation } = useDeleteConversation();
+ const { mutate: stopConversation } = useStopConversation();
const handleDeleteProject = (conversationId: string) => {
setConfirmDeleteModalVisible(true);
setSelectedConversationId(conversationId);
};
+ const handleStopConversation = (conversationId: string) => {
+ setConfirmStopModalVisible(true);
+ setSelectedConversationId(conversationId);
+ };
+
const handleConfirmDelete = () => {
if (selectedConversationId) {
deleteConversation(
@@ -54,6 +64,21 @@ export function ConversationPanel({ onClose }: ConversationPanelProps) {
}
};
+ const handleConfirmStop = () => {
+ if (selectedConversationId) {
+ stopConversation(
+ { conversationId: selectedConversationId },
+ {
+ onSuccess: () => {
+ if (selectedConversationId === currentConversationId) {
+ navigate("/");
+ }
+ },
+ },
+ );
+ }
+ };
+
return (
handleDeleteProject(project.conversation_id)}
+ onStop={() => handleStopConversation(project.conversation_id)}
title={project.title}
selectedRepository={project.selected_repository}
lastUpdatedAt={project.last_updated_at}
@@ -108,6 +134,16 @@ export function ConversationPanel({ onClose }: ConversationPanelProps) {
/>
)}
+ {confirmStopModalVisible && (
+ {
+ handleConfirmStop();
+ setConfirmStopModalVisible(false);
+ }}
+ onCancel={() => setConfirmStopModalVisible(false)}
+ />
+ )}
+
{confirmExitConversationModalVisible && (
{
diff --git a/frontend/src/hooks/mutation/use-stop-conversation.ts b/frontend/src/hooks/mutation/use-stop-conversation.ts
new file mode 100644
index 0000000000..ae7298d1b1
--- /dev/null
+++ b/frontend/src/hooks/mutation/use-stop-conversation.ts
@@ -0,0 +1,31 @@
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import OpenHands from "#/api/open-hands";
+
+export const useStopConversation = () => {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: (variables: { conversationId: string }) =>
+ OpenHands.stopConversation(variables.conversationId),
+ onMutate: async () => {
+ await queryClient.cancelQueries({ queryKey: ["user", "conversations"] });
+ const previousConversations = queryClient.getQueryData([
+ "user",
+ "conversations",
+ ]);
+
+ return { previousConversations };
+ },
+ onError: (_, __, context) => {
+ if (context?.previousConversations) {
+ queryClient.setQueryData(
+ ["user", "conversations"],
+ context.previousConversations,
+ );
+ }
+ },
+ onSettled: () => {
+ queryClient.invalidateQueries({ queryKey: ["user", "conversations"] });
+ },
+ });
+};
diff --git a/frontend/src/i18n/declaration.ts b/frontend/src/i18n/declaration.ts
index 8aba24b6f3..4134e56080 100644
--- a/frontend/src/i18n/declaration.ts
+++ b/frontend/src/i18n/declaration.ts
@@ -296,6 +296,8 @@ export enum I18nKey {
LANDING$UPLOAD_TRAJECTORY = "LANDING$UPLOAD_TRAJECTORY",
LANDING$RECENT_CONVERSATION = "LANDING$RECENT_CONVERSATION",
CONVERSATION$CONFIRM_DELETE = "CONVERSATION$CONFIRM_DELETE",
+ CONVERSATION$CONFIRM_STOP = "CONVERSATION$CONFIRM_STOP",
+ CONVERSATION$STOP_WARNING = "CONVERSATION$STOP_WARNING",
CONVERSATION$METRICS_INFO = "CONVERSATION$METRICS_INFO",
CONVERSATION$CREATED = "CONVERSATION$CREATED",
CONVERSATION$AGO = "CONVERSATION$AGO",
diff --git a/frontend/src/i18n/translation.json b/frontend/src/i18n/translation.json
index 0d159da214..7f977fc913 100644
--- a/frontend/src/i18n/translation.json
+++ b/frontend/src/i18n/translation.json
@@ -4735,6 +4735,38 @@
"de": "Löschen bestätigen",
"uk": "Підтвердити видалення"
},
+ "CONVERSATION$CONFIRM_STOP": {
+ "en": "Confirm Stop",
+ "ja": "停止の確認",
+ "zh-CN": "确认停止",
+ "zh-TW": "確認停止",
+ "ko-KR": "중지 확인",
+ "no": "Bekreft stopp",
+ "it": "Conferma arresto",
+ "pt": "Confirmar parada",
+ "es": "Confirmar detención",
+ "ar": "تأكيد الإيقاف",
+ "fr": "Confirmer l'arrêt",
+ "tr": "Durdurmayı Onayla",
+ "de": "Stopp bestätigen",
+ "uk": "Підтвердити зупинку"
+ },
+ "CONVERSATION$STOP_WARNING": {
+ "en": "Are you sure you want to stop this conversation?",
+ "ja": "この会話を停止してもよろしいですか?",
+ "zh-CN": "您确定要停止此对话吗?",
+ "zh-TW": "您確定要停止此對話嗎?",
+ "ko-KR": "이 대화를 중지하시겠습니까?",
+ "no": "Er du sikker på at du vil stoppe denne samtalen?",
+ "it": "Sei sicuro di voler fermare questa conversazione?",
+ "pt": "Tem certeza de que deseja parar esta conversa?",
+ "es": "¿Está seguro de que desea detener esta conversación?",
+ "ar": "هل أنت متأكد أنك تريد إيقاف هذه المحادثة؟",
+ "fr": "Êtes-vous sûr de vouloir arrêter cette conversation ?",
+ "tr": "Bu konuşmayı durdurmak istediğinizden emin misiniz?",
+ "de": "Sind Sie sicher, dass Sie dieses Gespräch stoppen möchten?",
+ "uk": "Ви впевнені, що хочете зупинити цю розмову?"
+ },
"CONVERSATION$METRICS_INFO": {
"en": "Conversation Metrics",
"ja": "会話メトリクス",