Merge pull request #595 from kubet/feat/port-fix-and-panel-ajustments

feat: on delete delete sandbox
This commit is contained in:
kubet 2025-06-01 23:18:50 +02:00 committed by GitHub
commit fa2d7fd202
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 129 additions and 16 deletions

View File

@ -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(

View File

@ -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

View File

@ -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
},
{

View File

@ -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 };

View File

@ -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(