mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 05:37:20 +08:00
feat(frontend): update stop sandbox dialog to display conversations in sandbox (#13388)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
@@ -72,7 +72,7 @@ vi.mock("react-i18next", async () => {
|
||||
CONVERSATION$SHOW_SKILLS: "Show Skills",
|
||||
BUTTON$DISPLAY_COST: "Display Cost",
|
||||
COMMON$CLOSE_CONVERSATION_STOP_RUNTIME:
|
||||
"Close Conversation (Stop Runtime)",
|
||||
"Close Conversation (Stop Sandbox)",
|
||||
COMMON$DELETE_CONVERSATION: "Delete Conversation",
|
||||
CONVERSATION$SHARE_PUBLICLY: "Share Publicly",
|
||||
CONVERSATION$LINK_COPIED: "Link copied to clipboard",
|
||||
@@ -565,7 +565,7 @@ describe("ConversationNameContextMenu", () => {
|
||||
"Delete Conversation",
|
||||
);
|
||||
expect(screen.getByTestId("stop-button")).toHaveTextContent(
|
||||
"Close Conversation (Stop Runtime)",
|
||||
"Close Conversation (Stop Sandbox)",
|
||||
);
|
||||
expect(screen.getByTestId("display-cost-button")).toHaveTextContent(
|
||||
"Display Cost",
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
V1AppConversationStartTask,
|
||||
V1AppConversationStartTaskPage,
|
||||
V1AppConversation,
|
||||
V1AppConversationPage,
|
||||
GetSkillsResponse,
|
||||
V1RuntimeConversationInfo,
|
||||
} from "./v1-conversation-service.types";
|
||||
@@ -424,6 +425,28 @@ class V1ConversationService {
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for V1 conversations by sandbox ID
|
||||
*
|
||||
* @param sandboxId The sandbox ID to filter by
|
||||
* @param limit Maximum number of results (default: 100)
|
||||
* @returns Array of conversations in the specified sandbox
|
||||
*/
|
||||
static async searchConversationsBySandboxId(
|
||||
sandboxId: string,
|
||||
limit: number = 100,
|
||||
): Promise<V1AppConversation[]> {
|
||||
const params = new URLSearchParams();
|
||||
params.append("sandbox_id__eq", sandboxId);
|
||||
params.append("limit", limit.toString());
|
||||
|
||||
const { data } = await openHands.get<V1AppConversationPage>(
|
||||
`/api/v1/app-conversations/search?${params.toString()}`,
|
||||
);
|
||||
|
||||
return data.items;
|
||||
}
|
||||
}
|
||||
|
||||
export default V1ConversationService;
|
||||
|
||||
@@ -119,6 +119,11 @@ export interface V1AppConversation {
|
||||
public?: boolean;
|
||||
}
|
||||
|
||||
export interface V1AppConversationPage {
|
||||
items: V1AppConversation[];
|
||||
next_page_id: string | null;
|
||||
}
|
||||
|
||||
export interface Skill {
|
||||
name: string;
|
||||
type: "repo" | "knowledge" | "agentskills";
|
||||
|
||||
@@ -7,17 +7,71 @@ import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
|
||||
import { ModalBody } from "#/components/shared/modals/modal-body";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { useConversationsInSandbox } from "#/hooks/query/use-conversations-in-sandbox";
|
||||
|
||||
interface ConfirmStopModalProps {
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
sandboxId: string | null;
|
||||
}
|
||||
|
||||
function ConversationsList({
|
||||
conversations,
|
||||
isLoading,
|
||||
isError,
|
||||
t,
|
||||
}: {
|
||||
conversations: { id: string; title: string | null }[] | undefined;
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
t: (key: string) => string;
|
||||
}) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div
|
||||
className="text-sm text-content-secondary"
|
||||
data-testid="conversations-loading"
|
||||
>
|
||||
{t(I18nKey.HOME$LOADING)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<div className="text-sm text-danger" data-testid="conversations-error">
|
||||
{t(I18nKey.COMMON$ERROR)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (conversations && conversations.length > 0) {
|
||||
return (
|
||||
<ul
|
||||
className="list-disc list-inside text-sm text-content-secondary"
|
||||
data-testid="conversations-list"
|
||||
>
|
||||
{conversations.map((conv) => (
|
||||
<li key={conv.id}>{conv.title || conv.id}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function ConfirmStopModal({
|
||||
onConfirm,
|
||||
onCancel,
|
||||
sandboxId,
|
||||
}: ConfirmStopModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data: conversations,
|
||||
isLoading,
|
||||
isError,
|
||||
} = useConversationsInSandbox(sandboxId);
|
||||
|
||||
return (
|
||||
<ModalBackdrop onClose={onCancel}>
|
||||
@@ -29,6 +83,12 @@ export function ConfirmStopModal({
|
||||
<BaseModalDescription
|
||||
description={t(I18nKey.CONVERSATION$CLOSE_CONVERSATION_WARNING)}
|
||||
/>
|
||||
<ConversationsList
|
||||
conversations={conversations}
|
||||
isLoading={isLoading}
|
||||
isError={isError}
|
||||
t={t}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-col gap-2 w-full"
|
||||
|
||||
@@ -44,6 +44,9 @@ export function ConversationPanel({ onClose }: ConversationPanelProps) {
|
||||
React.useState<string | null>(null);
|
||||
const [selectedConversationVersion, setSelectedConversationVersion] =
|
||||
React.useState<"V0" | "V1" | undefined>(undefined);
|
||||
const [selectedSandboxId, setSelectedSandboxId] = React.useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [openContextMenuId, setOpenContextMenuId] = React.useState<
|
||||
string | null
|
||||
>(null);
|
||||
@@ -85,10 +88,12 @@ export function ConversationPanel({ onClose }: ConversationPanelProps) {
|
||||
const handleStopConversation = (
|
||||
conversationId: string,
|
||||
version?: "V0" | "V1",
|
||||
sandboxId?: string | null,
|
||||
) => {
|
||||
setConfirmStopModalVisible(true);
|
||||
setSelectedConversationId(conversationId);
|
||||
setSelectedConversationVersion(version);
|
||||
setSelectedSandboxId(sandboxId ?? null);
|
||||
};
|
||||
|
||||
const handleConversationTitleChange = async (
|
||||
@@ -185,6 +190,7 @@ export function ConversationPanel({ onClose }: ConversationPanelProps) {
|
||||
handleStopConversation(
|
||||
project.conversation_id,
|
||||
project.conversation_version,
|
||||
project.sandbox_id,
|
||||
)
|
||||
}
|
||||
onChangeTitle={(title) =>
|
||||
@@ -238,6 +244,7 @@ export function ConversationPanel({ onClose }: ConversationPanelProps) {
|
||||
setConfirmStopModalVisible(false);
|
||||
}}
|
||||
onCancel={() => setConfirmStopModalVisible(false)}
|
||||
sandboxId={selectedSandboxId}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -233,6 +233,7 @@ export function ConversationName() {
|
||||
<ConfirmStopModal
|
||||
onConfirm={handleConfirmStop}
|
||||
onCancel={() => setConfirmStopModalVisible(false)}
|
||||
sandboxId={conversation?.sandbox_id ?? null}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
15
frontend/src/hooks/query/use-conversations-in-sandbox.ts
Normal file
15
frontend/src/hooks/query/use-conversations-in-sandbox.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import V1ConversationService from "#/api/conversation-service/v1-conversation-service.api";
|
||||
|
||||
export const useConversationsInSandbox = (sandboxId: string | null) =>
|
||||
useQuery({
|
||||
queryKey: ["conversations", "sandbox", sandboxId],
|
||||
queryFn: () =>
|
||||
sandboxId
|
||||
? V1ConversationService.searchConversationsBySandboxId(sandboxId)
|
||||
: Promise.resolve([]),
|
||||
enabled: !!sandboxId,
|
||||
staleTime: 0, // Always consider data stale for confirmation dialogs
|
||||
gcTime: 1000 * 60, // 1 minute
|
||||
refetchOnMount: true, // Always fetch fresh data when modal opens
|
||||
});
|
||||
@@ -5856,36 +5856,36 @@
|
||||
"uk": "Ви впевнені, що хочете призупинити цю розмову?"
|
||||
},
|
||||
"CONVERSATION$CONFIRM_CLOSE_CONVERSATION": {
|
||||
"en": "Confirm Close Conversation",
|
||||
"ja": "会話終了の確認",
|
||||
"zh-CN": "确认关闭对话",
|
||||
"zh-TW": "確認關閉對話",
|
||||
"ko-KR": "대화 종료 확인",
|
||||
"no": "Bekreft avslutt samtale",
|
||||
"it": "Conferma chiusura conversazione",
|
||||
"pt": "Confirmar encerrar conversa",
|
||||
"es": "Confirmar cerrar conversación",
|
||||
"ar": "تأكيد إغلاق المحادثة",
|
||||
"fr": "Confirmer la fermeture de la conversation",
|
||||
"tr": "Konuşmayı Kapatmayı Onayla",
|
||||
"de": "Gespräch schließen bestätigen",
|
||||
"uk": "Підтвердити закриття розмови"
|
||||
"en": "Confirm Stop Sandbox",
|
||||
"ja": "サンドボックス停止の確認",
|
||||
"zh-CN": "确认停止沙盒",
|
||||
"zh-TW": "確認停止沙盒",
|
||||
"ko-KR": "샌드박스 중지 확인",
|
||||
"no": "Bekreft stopp sandkasse",
|
||||
"it": "Conferma arresto sandbox",
|
||||
"pt": "Confirmar parar sandbox",
|
||||
"es": "Confirmar detener sandbox",
|
||||
"ar": "تأكيد إيقاف صندوق الحماية",
|
||||
"fr": "Confirmer l'arrêt du sandbox",
|
||||
"tr": "Sandbox'ı Durdurmayı Onayla",
|
||||
"de": "Sandbox-Stopp bestätigen",
|
||||
"uk": "Підтвердити зупинку пісочниці"
|
||||
},
|
||||
"CONVERSATION$CLOSE_CONVERSATION_WARNING": {
|
||||
"en": "Are you sure you want to close this conversation and stop the runtime?",
|
||||
"ja": "この会話を終了してランタイムを停止してもよろしいですか?",
|
||||
"zh-CN": "您确定要关闭此对话并停止运行时吗?",
|
||||
"zh-TW": "您確定要關閉此對話並停止執行時嗎?",
|
||||
"ko-KR": "이 대화를 종료하고 런타임을 중지하시겠습니까?",
|
||||
"no": "Er du sikker på at du vil avslutte denne samtalen og stoppe kjøretiden?",
|
||||
"it": "Sei sicuro di voler chiudere questa conversazione e fermare il runtime?",
|
||||
"pt": "Tem certeza de que deseja encerrar esta conversa e parar o runtime?",
|
||||
"es": "¿Está seguro de que desea cerrar esta conversación y detener el runtime?",
|
||||
"ar": "هل أنت متأكد أنك تريد إغلاق هذه المحادثة وإيقاف وقت التشغيل؟",
|
||||
"fr": "Êtes-vous sûr de vouloir fermer cette conversation et arrêter le runtime ?",
|
||||
"tr": "Bu konuşmayı kapatmak ve çalışma zamanını durdurmak istediğinizden emin misiniz?",
|
||||
"de": "Sind Sie sicher, dass Sie dieses Gespräch schließen und die Laufzeit stoppen möchten?",
|
||||
"uk": "Ви впевнені, що хочете закрити цю розмову та зупинити час виконання?"
|
||||
"en": "This will stop the sandbox, and pause the following conversations:",
|
||||
"ja": "サンドボックスを停止し、以下の会話を一時停止します:",
|
||||
"zh-CN": "这将停止沙盒,并暂停以下对话:",
|
||||
"zh-TW": "這將停止沙盒,並暫停以下對話:",
|
||||
"ko-KR": "샌드박스를 중지하고 다음 대화를 일시 중지합니다:",
|
||||
"no": "Dette vil stoppe sandkassen og pause følgende samtaler:",
|
||||
"it": "Questo fermerà la sandbox e metterà in pausa le seguenti conversazioni:",
|
||||
"pt": "Isso irá parar o sandbox e pausar as seguintes conversas:",
|
||||
"es": "Esto detendrá el sandbox y pausará las siguientes conversaciones:",
|
||||
"ar": "سيؤدي هذا إلى إيقاف صندوق الحماية وإيقاف المحادثات التالية مؤقتًا:",
|
||||
"fr": "Cela arrêtera le sandbox et mettra en pause les conversations suivantes :",
|
||||
"tr": "Bu, sandbox'ı durduracak ve aşağıdaki konuşmaları duraklatacaktır:",
|
||||
"de": "Dies wird die Sandbox stoppen und die folgenden Gespräche pausieren:",
|
||||
"uk": "Це зупинить пісочницю та призупинить наступні розмови:"
|
||||
},
|
||||
"CONVERSATION$STOP_WARNING": {
|
||||
"en": "Are you sure you want to pause this conversation?",
|
||||
@@ -14964,20 +14964,20 @@
|
||||
"uk": "Натисніть тут"
|
||||
},
|
||||
"COMMON$CLOSE_CONVERSATION_STOP_RUNTIME": {
|
||||
"en": "Close Conversation (Stop Runtime)",
|
||||
"ja": "会話を閉じる(ランタイム停止)",
|
||||
"zh-CN": "关闭对话(停止运行时)",
|
||||
"zh-TW": "關閉對話(停止執行時)",
|
||||
"ko-KR": "대화 닫기(런타임 중지)",
|
||||
"no": "Lukk samtale (stopp kjøring)",
|
||||
"it": "Chiudi conversazione (Interrompi runtime)",
|
||||
"pt": "Fechar conversa (Parar execução)",
|
||||
"es": "Cerrar conversación (Detener ejecución)",
|
||||
"ar": "إغلاق المحادثة (إيقاف وقت التشغيل)",
|
||||
"fr": "Fermer la conversation (Arrêter l'exécution)",
|
||||
"tr": "Konuşmayı Kapat (Çalışma Zamanını Durdur)",
|
||||
"de": "Gespräch schließen (Laufzeit beenden)",
|
||||
"uk": "Закрити розмову (зупинити виконання)"
|
||||
"en": "Close Conversation (Stop Sandbox)",
|
||||
"ja": "会話を閉じる(サンドボックス停止)",
|
||||
"zh-CN": "关闭对话(停止沙盒)",
|
||||
"zh-TW": "關閉對話(停止沙盒)",
|
||||
"ko-KR": "대화 닫기(샌드박스 중지)",
|
||||
"no": "Lukk samtale (stopp sandkasse)",
|
||||
"it": "Chiudi conversazione (Interrompi sandbox)",
|
||||
"pt": "Fechar conversa (Parar sandbox)",
|
||||
"es": "Cerrar conversación (Detener sandbox)",
|
||||
"ar": "إغلاق المحادثة (إيقاف صندوق الحماية)",
|
||||
"fr": "Fermer la conversation (Arrêter le sandbox)",
|
||||
"tr": "Konuşmayı Kapat (Sandbox'ı Durdur)",
|
||||
"de": "Gespräch schließen (Sandbox beenden)",
|
||||
"uk": "Закрити розмову (зупинити пісочницю)"
|
||||
},
|
||||
"COMMON$CODE": {
|
||||
"en": "Code",
|
||||
|
||||
Reference in New Issue
Block a user