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 fastapi.responses import Response
|
||||||
from pydantic import BaseModel
|
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.logger import logger
|
||||||
from utils.auth_utils import get_optional_user_id
|
from utils.auth_utils import get_optional_user_id
|
||||||
from services.supabase import DBConnection
|
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)}")
|
logger.error(f"Error deleting file in sandbox {sandbox_id}: {str(e)}")
|
||||||
raise HTTPException(status_code=500, detail=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
|
# Should happen on server-side fully
|
||||||
@router.post("/project/{project_id}/sandbox/ensure-active")
|
@router.post("/project/{project_id}/sandbox/ensure-active")
|
||||||
async def ensure_project_sandbox_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")
|
logger.debug(f"Sandbox environment successfully initialized")
|
||||||
return sandbox
|
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
|
// Store threadToDelete in a local variable since it might be cleared
|
||||||
const deletedThread = { ...threadToDelete };
|
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
|
// Log operation start
|
||||||
console.log('DELETION - Starting thread deletion process', {
|
console.log('DELETION - Starting thread deletion process', {
|
||||||
threadId: deletedThread.id,
|
threadId: deletedThread.id,
|
||||||
isCurrentThread: isActive,
|
isCurrentThread: isActive,
|
||||||
|
sandboxId
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use the centralized deletion system with completion callback
|
// Use the centralized deletion system with completion callback
|
||||||
|
@ -236,9 +242,9 @@ export function NavAgents() {
|
||||||
threadId,
|
threadId,
|
||||||
isActive,
|
isActive,
|
||||||
async () => {
|
async () => {
|
||||||
// Delete the thread using the mutation
|
// Delete the thread using the mutation with sandbox ID
|
||||||
deleteThreadMutation(
|
deleteThreadMutation(
|
||||||
{ threadId },
|
{ threadId, sandboxId },
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// Invalidate queries to refresh the list
|
// Invalidate queries to refresh the list
|
||||||
|
@ -282,6 +288,13 @@ export function NavAgents() {
|
||||||
deleteMultipleThreadsMutation(
|
deleteMultipleThreadsMutation(
|
||||||
{
|
{
|
||||||
threadIds: threadIdsToDelete,
|
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
|
onProgress: handleDeletionProgress
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -33,12 +33,13 @@ export const useThreads = createQueryHook(
|
||||||
|
|
||||||
interface DeleteThreadVariables {
|
interface DeleteThreadVariables {
|
||||||
threadId: string;
|
threadId: string;
|
||||||
|
sandboxId?: string;
|
||||||
isNavigateAway?: boolean;
|
isNavigateAway?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDeleteThread = createMutationHook(
|
export const useDeleteThread = createMutationHook(
|
||||||
async ({ threadId }: DeleteThreadVariables) => {
|
async ({ threadId, sandboxId }: DeleteThreadVariables) => {
|
||||||
return await deleteThread(threadId);
|
return await deleteThread(threadId, sandboxId);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
@ -48,16 +49,18 @@ export const useDeleteThread = createMutationHook(
|
||||||
|
|
||||||
interface DeleteMultipleThreadsVariables {
|
interface DeleteMultipleThreadsVariables {
|
||||||
threadIds: string[];
|
threadIds: string[];
|
||||||
|
threadSandboxMap?: Record<string, string>;
|
||||||
onProgress?: (completed: number, total: number) => void;
|
onProgress?: (completed: number, total: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useDeleteMultipleThreads = createMutationHook(
|
export const useDeleteMultipleThreads = createMutationHook(
|
||||||
async ({ threadIds, onProgress }: DeleteMultipleThreadsVariables) => {
|
async ({ threadIds, threadSandboxMap, onProgress }: DeleteMultipleThreadsVariables) => {
|
||||||
let completedCount = 0;
|
let completedCount = 0;
|
||||||
const results = await Promise.all(
|
const results = await Promise.all(
|
||||||
threadIds.map(async (threadId) => {
|
threadIds.map(async (threadId) => {
|
||||||
try {
|
try {
|
||||||
const result = await deleteThread(threadId);
|
const sandboxId = threadSandboxMap?.[threadId];
|
||||||
|
const result = await deleteThread(threadId, sandboxId);
|
||||||
completedCount++;
|
completedCount++;
|
||||||
onProgress?.(completedCount, threadIds.length);
|
onProgress?.(completedCount, threadIds.length);
|
||||||
return { success: true, threadId };
|
return { success: true, threadId };
|
||||||
|
|
|
@ -74,41 +74,99 @@ export const toggleThreadPublicStatus = async (
|
||||||
return updateThread(threadId, { is_public: isPublic });
|
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 {
|
try {
|
||||||
const supabase = createClient();
|
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}`);
|
console.log(`Deleting all agent runs for thread ${threadId}`);
|
||||||
const { error: agentRunsError } = await supabase
|
const { error: agentRunsError } = await supabase
|
||||||
.from('agent_runs')
|
.from('agent_runs')
|
||||||
.delete()
|
.delete()
|
||||||
.eq('thread_id', threadId);
|
.eq('thread_id', threadId);
|
||||||
|
|
||||||
if (agentRunsError) {
|
if (agentRunsError) {
|
||||||
console.error('Error deleting agent runs:', agentRunsError);
|
console.error('Error deleting agent runs:', agentRunsError);
|
||||||
throw new Error(`Error deleting agent runs: ${agentRunsError.message}`);
|
throw new Error(`Error deleting agent runs: ${agentRunsError.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Deleting all messages for thread ${threadId}`);
|
console.log(`Deleting all messages for thread ${threadId}`);
|
||||||
const { error: messagesError } = await supabase
|
const { error: messagesError } = await supabase
|
||||||
.from('messages')
|
.from('messages')
|
||||||
.delete()
|
.delete()
|
||||||
.eq('thread_id', threadId);
|
.eq('thread_id', threadId);
|
||||||
|
|
||||||
if (messagesError) {
|
if (messagesError) {
|
||||||
console.error('Error deleting messages:', messagesError);
|
console.error('Error deleting messages:', messagesError);
|
||||||
throw new Error(`Error deleting messages: ${messagesError.message}`);
|
throw new Error(`Error deleting messages: ${messagesError.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Deleting thread ${threadId}`);
|
console.log(`Deleting thread ${threadId}`);
|
||||||
const { error: threadError } = await supabase
|
const { error: threadError2 } = await supabase
|
||||||
.from('threads')
|
.from('threads')
|
||||||
.delete()
|
.delete()
|
||||||
.eq('thread_id', threadId);
|
.eq('thread_id', threadId);
|
||||||
|
|
||||||
if (threadError) {
|
if (threadError2) {
|
||||||
console.error('Error deleting thread:', threadError);
|
console.error('Error deleting thread:', threadError2);
|
||||||
throw new Error(`Error deleting thread: ${threadError.message}`);
|
throw new Error(`Error deleting thread: ${threadError2.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
|
|
Loading…
Reference in New Issue