Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,29 @@ export async function POST(req: NextRequest) {
);

if (mode === "agent" && sandboxFiles && sandboxFiles.length > 0) {
await uploadSandboxFiles(sandboxFiles, ensureSandbox);
// Send upload start notification
writer.write({
type: "data-upload-status",
data: {
message: "Uploading attachments to the computer",
isUploading: true,
},
transient: true,
});

try {
await uploadSandboxFiles(sandboxFiles, ensureSandbox);
} finally {
// Send upload complete notification
writer.write({
type: "data-upload-status",
data: {
message: "",
isUploading: false,
},
transient: true,
});
}
}

// Generate title in parallel only for non-temporary new chats
Expand Down
61 changes: 61 additions & 0 deletions app/api/delete-sandboxes/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Sandbox } from "@e2b/code-interpreter";
import { getUserIDAndPro } from "@/lib/auth/get-user-id";
import { NextRequest } from "next/server";

export const maxDuration = 60;

export async function POST(req: NextRequest) {
try {
const { userId, subscription } = await getUserIDAndPro(req);

if (!userId) {
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}

// Only allow subscribed users to delete sandboxes
if (subscription === "free") {
return new Response(JSON.stringify({ error: "Subscription required" }), {
status: 403,
headers: { "Content-Type": "application/json" },
});
}

// List all sandboxes for this user
const paginator = Sandbox.list({
query: {
metadata: {
userID: userId,
},
},
});

const sandboxes = await paginator.nextItems();

// Kill each sandbox
for (const sandbox of sandboxes) {
try {
await Sandbox.kill(sandbox.sandboxId);
} catch (error) {
console.error(`Failed to kill sandbox ${sandbox.sandboxId}:`, error);
throw error;
}
}

return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
console.error("Error deleting sandboxes:", error);
return new Response(
JSON.stringify({ error: "Failed to delete sandboxes" }),
{
status: 500,
headers: { "Content-Type": "application/json" },
},
);
}
}
74 changes: 0 additions & 74 deletions app/components/AccountTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,47 +14,17 @@ import {
import { X, ChevronDown } from "lucide-react";
import { proFeatures, ultraFeatures } from "@/lib/pricing/features";
import DeleteAccountDialog from "./DeleteAccountDialog";
import { useMutation } from "convex/react";
import { api } from "@/convex/_generated/api";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";

const AccountTab = () => {
const { subscription } = useGlobalState();
const [showDeleteAccount, setShowDeleteAccount] = useState(false);
const [showDeleteChats, setShowDeleteChats] = useState(false);
const [isDeletingChats, setIsDeletingChats] = useState(false);

const deleteAllChats = useMutation(api.chats.deleteAllChats);

const currentPlanFeatures = proFeatures;

const handleCancelSubscription = () => {
redirectToBillingPortal();
};

const handleDeleteAllChats = async () => {
if (isDeletingChats) return;
setIsDeletingChats(true);
try {
await deleteAllChats();
} catch (error) {
console.error("Failed to delete all chats:", error);
} finally {
setShowDeleteChats(false);
window.location.href = "/";
setIsDeletingChats(false);
}
};

return (
<div className="space-y-6 min-h-0">
{/* Subscription Section */}
Expand Down Expand Up @@ -148,23 +118,6 @@ const AccountTab = () => {
</div>
)}

{/* Delete All Chats Section */}
<div>
<div className="flex items-center justify-between py-3">
<div>
<div className="font-medium">Delete all chats</div>
</div>
<Button
variant="destructive"
size="sm"
onClick={() => setShowDeleteChats(true)}
aria-label="Delete all chats"
>
Delete all
</Button>
</div>
</div>

{/* Delete Account Section */}
<div>
<div className="flex items-center justify-between py-3">
Expand All @@ -186,33 +139,6 @@ const AccountTab = () => {
open={showDeleteAccount}
onOpenChange={setShowDeleteAccount}
/>

{/* Delete All Chats Confirmation Dialog */}
<AlertDialog open={showDeleteChats} onOpenChange={setShowDeleteChats}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Clear your chat history - are you sure?
</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete all
your chats and remove all associated data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isDeletingChats}>
Cancel
</AlertDialogCancel>
<AlertDialogAction
onClick={handleDeleteAllChats}
disabled={isDeletingChats}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{isDeletingChats ? "Deleting..." : "Confirm deletion"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
};
Expand Down
168 changes: 168 additions & 0 deletions app/components/DataControlsTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
"use client";

import React, { useState } from "react";
import { Button } from "@/components/ui/button";
import { useMutation } from "convex/react";
import { api } from "@/convex/_generated/api";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { toast } from "sonner";
import { useGlobalState } from "@/app/contexts/GlobalState";

const DataControlsTab = () => {
const { subscription } = useGlobalState();
const [showDeleteChats, setShowDeleteChats] = useState(false);
const [isDeletingChats, setIsDeletingChats] = useState(false);
const [showDeleteSandboxes, setShowDeleteSandboxes] = useState(false);
const [isDeletingSandboxes, setIsDeletingSandboxes] = useState(false);

const deleteAllChats = useMutation(api.chats.deleteAllChats);

const handleDeleteAllChats = async () => {
if (isDeletingChats) return;
setIsDeletingChats(true);
try {
await deleteAllChats();
} catch (error) {
console.error("Failed to delete all chats:", error);
} finally {
setShowDeleteChats(false);
window.location.href = "/";
setIsDeletingChats(false);
}
};

const handleDeleteSandboxes = async () => {
if (isDeletingSandboxes) return;
setIsDeletingSandboxes(true);
try {
const response = await fetch("/api/delete-sandboxes", {
method: "POST",
});

if (!response.ok) {
const data = await response.json();
throw new Error(data.error || "Failed to delete sandbox");
}

toast.success("Successfully deleted terminal sandbox");
} catch (error) {
console.error("Failed to delete sandbox:", error);
toast.error("Failed to delete terminal sandbox");
} finally {
setShowDeleteSandboxes(false);
setIsDeletingSandboxes(false);
}
};

return (
<div className="space-y-6 min-h-0">
{/* Delete All Chats Section */}
<div>
<div className="flex items-center justify-between py-3">
<div>
<div className="font-medium">Delete all chats</div>
</div>
<Button
variant="destructive"
size="sm"
onClick={() => setShowDeleteChats(true)}
aria-label="Delete all chats"
>
Delete all
</Button>
</div>
</div>

{/* Delete Terminal Sandbox Section - Only for subscribed users */}
{subscription !== "free" && (
<div>
<div className="flex items-center justify-between py-3">
<div>
<div className="font-medium">Delete terminal sandbox</div>
<div className="text-sm text-muted-foreground mt-1">
Remove all files and data from terminal
</div>
</div>
<Button
variant="destructive"
size="sm"
onClick={() => setShowDeleteSandboxes(true)}
aria-label="Delete terminal sandbox"
>
Delete
</Button>
</div>
</div>
)}

{/* Delete All Chats Confirmation Dialog */}
<AlertDialog open={showDeleteChats} onOpenChange={setShowDeleteChats}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Clear your chat history - are you sure?
</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete all
your chats and remove all associated data from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isDeletingChats}>
Cancel
</AlertDialogCancel>
<AlertDialogAction
onClick={handleDeleteAllChats}
disabled={isDeletingChats}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{isDeletingChats ? "Deleting..." : "Confirm deletion"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>

{/* Delete Terminal Sandbox Confirmation Dialog */}
<AlertDialog
open={showDeleteSandboxes}
onOpenChange={setShowDeleteSandboxes}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Delete terminal sandbox - are you sure?
</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently remove all
files and data from your terminal sandbox. Any running processes
will be stopped.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isDeletingSandboxes}>
Cancel
</AlertDialogCancel>
<AlertDialogAction
onClick={handleDeleteSandboxes}
disabled={isDeletingSandboxes}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{isDeletingSandboxes ? "Deleting..." : "Delete"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
};

export { DataControlsTab };
Loading