mirror of https://github.com/kortix-ai/suna.git
Merge pull request #595 from kubet/feat/port-fix-and-panel-ajustments
feat: on delete delete sandbox
This commit is contained in:
commit
fa2d7fd202
|
@ -6,7 +6,7 @@ from fastapi import FastAPI, UploadFile, File, HTTPException, APIRouter, Form, D
|
|||
from fastapi.responses import Response
|
||||
from pydantic import BaseModel
|
||||
|
||||
from sandbox.sandbox import get_or_start_sandbox
|
||||
from sandbox.sandbox import get_or_start_sandbox, delete_sandbox
|
||||
from utils.logger import logger
|
||||
from utils.auth_utils import get_optional_user_id
|
||||
from services.supabase import DBConnection
|
||||
|
@ -305,6 +305,28 @@ async def delete_file(
|
|||
logger.error(f"Error deleting file in sandbox {sandbox_id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.delete("/sandboxes/{sandbox_id}")
|
||||
async def delete_sandbox_route(
|
||||
sandbox_id: str,
|
||||
request: Request = None,
|
||||
user_id: Optional[str] = Depends(get_optional_user_id)
|
||||
):
|
||||
"""Delete an entire sandbox"""
|
||||
logger.info(f"Received sandbox delete request for sandbox {sandbox_id}, user_id: {user_id}")
|
||||
client = await db.client
|
||||
|
||||
# Verify the user has access to this sandbox
|
||||
await verify_sandbox_access(client, sandbox_id, user_id)
|
||||
|
||||
try:
|
||||
# Delete the sandbox using the sandbox module function
|
||||
await delete_sandbox(sandbox_id)
|
||||
|
||||
return {"status": "success", "deleted": True, "sandbox_id": sandbox_id}
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting sandbox {sandbox_id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
# Should happen on server-side fully
|
||||
@router.post("/project/{project_id}/sandbox/ensure-active")
|
||||
async def ensure_project_sandbox_active(
|
||||
|
|
|
@ -125,3 +125,20 @@ def create_sandbox(password: str, project_id: str = None):
|
|||
logger.debug(f"Sandbox environment successfully initialized")
|
||||
return sandbox
|
||||
|
||||
async def delete_sandbox(sandbox_id: str):
|
||||
"""Delete a sandbox by its ID."""
|
||||
logger.info(f"Deleting sandbox with ID: {sandbox_id}")
|
||||
|
||||
try:
|
||||
# Get the sandbox
|
||||
sandbox = daytona.get_current_sandbox(sandbox_id)
|
||||
|
||||
# Delete the sandbox
|
||||
daytona.remove(sandbox)
|
||||
|
||||
logger.info(f"Successfully deleted sandbox {sandbox_id}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting sandbox {sandbox_id}: {str(e)}")
|
||||
raise e
|
||||
|
||||
|
|
|
@ -225,10 +225,16 @@ export function NavAgents() {
|
|||
// Store threadToDelete in a local variable since it might be cleared
|
||||
const deletedThread = { ...threadToDelete };
|
||||
|
||||
// Get sandbox ID from projects data
|
||||
const thread = combinedThreads.find(t => t.threadId === threadId);
|
||||
const project = projects.find(p => p.id === thread?.projectId);
|
||||
const sandboxId = project?.sandbox?.id;
|
||||
|
||||
// Log operation start
|
||||
console.log('DELETION - Starting thread deletion process', {
|
||||
threadId: deletedThread.id,
|
||||
isCurrentThread: isActive,
|
||||
sandboxId
|
||||
});
|
||||
|
||||
// Use the centralized deletion system with completion callback
|
||||
|
@ -236,9 +242,9 @@ export function NavAgents() {
|
|||
threadId,
|
||||
isActive,
|
||||
async () => {
|
||||
// Delete the thread using the mutation
|
||||
// Delete the thread using the mutation with sandbox ID
|
||||
deleteThreadMutation(
|
||||
{ threadId },
|
||||
{ threadId, sandboxId },
|
||||
{
|
||||
onSuccess: () => {
|
||||
// Invalidate queries to refresh the list
|
||||
|
@ -282,6 +288,13 @@ export function NavAgents() {
|
|||
deleteMultipleThreadsMutation(
|
||||
{
|
||||
threadIds: threadIdsToDelete,
|
||||
threadSandboxMap: Object.fromEntries(
|
||||
threadIdsToDelete.map(threadId => {
|
||||
const thread = combinedThreads.find(t => t.threadId === threadId);
|
||||
const project = projects.find(p => p.id === thread?.projectId);
|
||||
return [threadId, project?.sandbox?.id || ''];
|
||||
}).filter(([, sandboxId]) => sandboxId)
|
||||
),
|
||||
onProgress: handleDeletionProgress
|
||||
},
|
||||
{
|
||||
|
|
|
@ -33,12 +33,13 @@ export const useThreads = createQueryHook(
|
|||
|
||||
interface DeleteThreadVariables {
|
||||
threadId: string;
|
||||
sandboxId?: string;
|
||||
isNavigateAway?: boolean;
|
||||
}
|
||||
|
||||
export const useDeleteThread = createMutationHook(
|
||||
async ({ threadId }: DeleteThreadVariables) => {
|
||||
return await deleteThread(threadId);
|
||||
async ({ threadId, sandboxId }: DeleteThreadVariables) => {
|
||||
return await deleteThread(threadId, sandboxId);
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
|
@ -48,16 +49,18 @@ export const useDeleteThread = createMutationHook(
|
|||
|
||||
interface DeleteMultipleThreadsVariables {
|
||||
threadIds: string[];
|
||||
threadSandboxMap?: Record<string, string>;
|
||||
onProgress?: (completed: number, total: number) => void;
|
||||
}
|
||||
|
||||
export const useDeleteMultipleThreads = createMutationHook(
|
||||
async ({ threadIds, onProgress }: DeleteMultipleThreadsVariables) => {
|
||||
async ({ threadIds, threadSandboxMap, onProgress }: DeleteMultipleThreadsVariables) => {
|
||||
let completedCount = 0;
|
||||
const results = await Promise.all(
|
||||
threadIds.map(async (threadId) => {
|
||||
try {
|
||||
const result = await deleteThread(threadId);
|
||||
const sandboxId = threadSandboxMap?.[threadId];
|
||||
const result = await deleteThread(threadId, sandboxId);
|
||||
completedCount++;
|
||||
onProgress?.(completedCount, threadIds.length);
|
||||
return { success: true, threadId };
|
||||
|
|
|
@ -74,41 +74,99 @@ export const toggleThreadPublicStatus = async (
|
|||
return updateThread(threadId, { is_public: isPublic });
|
||||
};
|
||||
|
||||
export const deleteThread = async (threadId: string): Promise<void> => {
|
||||
const deleteSandbox = async (sandboxId: string): Promise<void> => {
|
||||
try {
|
||||
const supabase = createClient();
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession();
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
if (session?.access_token) {
|
||||
headers['Authorization'] = `Bearer ${session.access_token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}/sandboxes/${sandboxId}`, {
|
||||
method: 'DELETE',
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn('Failed to delete sandbox, continuing with thread deletion');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Error deleting sandbox, continuing with thread deletion:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteThread = async (threadId: string, sandboxId?: string): Promise<void> => {
|
||||
try {
|
||||
const supabase = createClient();
|
||||
|
||||
|
||||
// If sandbox ID is provided, delete it directly
|
||||
if (sandboxId) {
|
||||
await deleteSandbox(sandboxId);
|
||||
} else {
|
||||
// Otherwise, get the thread to find its project and sandbox
|
||||
const { data: thread, error: threadError } = await supabase
|
||||
.from('threads')
|
||||
.select('project_id')
|
||||
.eq('thread_id', threadId)
|
||||
.single();
|
||||
|
||||
if (threadError) {
|
||||
console.error('Error fetching thread:', threadError);
|
||||
throw new Error(`Error fetching thread: ${threadError.message}`);
|
||||
}
|
||||
|
||||
// If thread has a project, get sandbox ID and delete it
|
||||
if (thread?.project_id) {
|
||||
const { data: project } = await supabase
|
||||
.from('projects')
|
||||
.select('sandbox')
|
||||
.eq('project_id', thread.project_id)
|
||||
.single();
|
||||
|
||||
if (project?.sandbox?.id) {
|
||||
await deleteSandbox(project.sandbox.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Deleting all agent runs for thread ${threadId}`);
|
||||
const { error: agentRunsError } = await supabase
|
||||
.from('agent_runs')
|
||||
.delete()
|
||||
.eq('thread_id', threadId);
|
||||
|
||||
|
||||
if (agentRunsError) {
|
||||
console.error('Error deleting agent runs:', agentRunsError);
|
||||
throw new Error(`Error deleting agent runs: ${agentRunsError.message}`);
|
||||
}
|
||||
|
||||
|
||||
console.log(`Deleting all messages for thread ${threadId}`);
|
||||
const { error: messagesError } = await supabase
|
||||
.from('messages')
|
||||
.delete()
|
||||
.eq('thread_id', threadId);
|
||||
|
||||
|
||||
if (messagesError) {
|
||||
console.error('Error deleting messages:', messagesError);
|
||||
throw new Error(`Error deleting messages: ${messagesError.message}`);
|
||||
}
|
||||
|
||||
console.log(`Deleting thread ${threadId}`);
|
||||
const { error: threadError } = await supabase
|
||||
const { error: threadError2 } = await supabase
|
||||
.from('threads')
|
||||
.delete()
|
||||
.eq('thread_id', threadId);
|
||||
|
||||
if (threadError) {
|
||||
console.error('Error deleting thread:', threadError);
|
||||
throw new Error(`Error deleting thread: ${threadError.message}`);
|
||||
if (threadError2) {
|
||||
console.error('Error deleting thread:', threadError2);
|
||||
throw new Error(`Error deleting thread: ${threadError2.message}`);
|
||||
}
|
||||
|
||||
console.log(
|
||||
|
|
Loading…
Reference in New Issue