diff --git a/frontend/src/app/(dashboard)/layout.tsx b/frontend/src/app/(dashboard)/layout.tsx
index 74be50d8..19f180b1 100644
--- a/frontend/src/app/(dashboard)/layout.tsx
+++ b/frontend/src/app/(dashboard)/layout.tsx
@@ -4,15 +4,15 @@ import { useEffect, useState } from 'react';
import { SidebarLeft } from '@/components/sidebar/sidebar-left';
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
// import { PricingAlert } from "@/components/billing/pricing-alert"
-import { MaintenanceAlert } from "@/components/maintenance-alert"
-import { useAccounts } from "@/hooks/use-accounts"
-import { useAuth } from "@/components/AuthProvider"
-import { useRouter } from "next/navigation"
-import { Loader2 } from "lucide-react"
-import { checkApiHealth } from "@/lib/api"
-import { MaintenancePage } from "@/components/maintenance/maintenance-page"
-import { DeleteOperationProvider } from "@/contexts/DeleteOperationContext"
-import { StatusOverlay } from "@/components/ui/status-overlay"
+import { MaintenanceAlert } from '@/components/maintenance-alert';
+import { useAccounts } from '@/hooks/use-accounts';
+import { useAuth } from '@/components/AuthProvider';
+import { useRouter } from 'next/navigation';
+import { Loader2 } from 'lucide-react';
+import { checkApiHealth } from '@/lib/api';
+import { MaintenancePage } from '@/components/maintenance/maintenance-page';
+import { DeleteOperationProvider } from '@/contexts/DeleteOperationContext';
+import { StatusOverlay } from '@/components/ui/status-overlay';
interface DashboardLayoutProps {
children: React.ReactNode;
@@ -84,27 +84,25 @@ export default function DashboardLayout({ children }: DashboardLayoutProps) {
-
- {children}
-
+ {children}
-
+
{/* */}
-
+
-
+
{/* Status overlay for deletion operations */}
- )
+ );
}
diff --git a/frontend/src/components/home/sections/pricing-section.tsx b/frontend/src/components/home/sections/pricing-section.tsx
index 01b7dfdf..383aa02f 100644
--- a/frontend/src/components/home/sections/pricing-section.tsx
+++ b/frontend/src/components/home/sections/pricing-section.tsx
@@ -666,15 +666,15 @@ export function PricingSection({
}
};
- // if (isLocalMode()) {
- // return (
- //
- //
- // Running in local development mode - billing features are disabled
- //
- //
- // );
- // }
+ if (isLocalMode()) {
+ return (
+
+
+ Running in local development mode - billing features are disabled
+
+
+ );
+ }
return (
([])
- const [isLoading, setIsLoading] = useState(true)
- const [loadingThreadId, setLoadingThreadId] = useState(null)
- const pathname = usePathname()
- const router = useRouter()
- const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
- const [threadToDelete, setThreadToDelete] = useState<{ id: string; name: string } | null>(null)
- const [isDeleting, setIsDeleting] = useState(false)
- const isNavigatingRef = useRef(false)
+ const { isMobile, state } = useSidebar();
+ const [threads, setThreads] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [loadingThreadId, setLoadingThreadId] = useState(null);
+ const pathname = usePathname();
+ const router = useRouter();
+ const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
+ const [threadToDelete, setThreadToDelete] = useState<{
+ id: string;
+ name: string;
+ } | null>(null);
+ const [isDeleting, setIsDeleting] = useState(false);
+ const isNavigatingRef = useRef(false);
const { performDelete, isOperationInProgress } = useDeleteOperation();
const isPerformingActionRef = useRef(false);
@@ -209,33 +212,37 @@ export function NavAgents() {
// Add event handler for completed navigation
useEffect(() => {
const handleNavigationComplete = () => {
- console.log("NAVIGATION - Navigation event completed");
- document.body.style.pointerEvents = "auto";
+ console.log('NAVIGATION - Navigation event completed');
+ document.body.style.pointerEvents = 'auto';
isNavigatingRef.current = false;
};
-
- window.addEventListener("popstate", handleNavigationComplete);
-
+
+ window.addEventListener('popstate', handleNavigationComplete);
+
return () => {
- window.removeEventListener("popstate", handleNavigationComplete);
+ window.removeEventListener('popstate', handleNavigationComplete);
// Ensure we clean up any leftover styles
- document.body.style.pointerEvents = "auto";
+ document.body.style.pointerEvents = 'auto';
};
}, []);
-
+
// Reset isNavigatingRef when pathname changes
useEffect(() => {
isNavigatingRef.current = false;
- document.body.style.pointerEvents = "auto";
+ document.body.style.pointerEvents = 'auto';
}, [pathname]);
// Function to handle thread click with loading state
- const handleThreadClick = (e: React.MouseEvent, threadId: string, url: string) => {
- e.preventDefault()
- setLoadingThreadId(threadId)
- router.push(url)
- }
-
+ const handleThreadClick = (
+ e: React.MouseEvent,
+ threadId: string,
+ url: string,
+ ) => {
+ e.preventDefault();
+ setLoadingThreadId(threadId);
+ router.push(url);
+ };
+
// Function to handle thread deletion
const handleDeleteThread = async (threadId: string, threadName: string) => {
setThreadToDelete({ id: threadId, name: threadName });
@@ -244,25 +251,25 @@ export function NavAgents() {
const confirmDelete = async () => {
if (!threadToDelete || isPerformingActionRef.current) return;
-
+
// Mark action in progress
isPerformingActionRef.current = true;
-
+
// Close dialog first for immediate feedback
setIsDeleteDialogOpen(false);
-
+
const threadId = threadToDelete.id;
const isActive = pathname?.includes(threadId);
-
+
// Store threadToDelete in a local variable since it might be cleared
const deletedThread = { ...threadToDelete };
-
+
// Log operation start
- console.log("DELETION - Starting thread deletion process", {
+ console.log('DELETION - Starting thread deletion process', {
threadId: deletedThread.id,
- isCurrentThread: isActive
+ isCurrentThread: isActive,
});
-
+
// Use the centralized deletion system with completion callback
await performDelete(
threadId,
@@ -270,19 +277,19 @@ export function NavAgents() {
async () => {
// Delete the thread
await deleteThread(threadId);
-
+
// Update the thread list
- setThreads(prev => prev.filter(t => t.threadId !== threadId));
-
+ setThreads((prev) => prev.filter((t) => t.threadId !== threadId));
+
// Show success message
- toast.success("Conversation deleted successfully");
+ toast.success('Conversation deleted successfully');
},
// Completion callback to reset local state
() => {
setThreadToDelete(null);
setIsDeleting(false);
isPerformingActionRef.current = false;
- }
+ },
);
};
@@ -428,7 +435,14 @@ export function NavAgents() {
- handleDeleteThread(thread.threadId, thread.projectName)}>
+
+ handleDeleteThread(
+ thread.threadId,
+ thread.projectName,
+ )
+ }
+ >
Delete
diff --git a/frontend/src/components/thread/DeleteConfirmationDialog.tsx b/frontend/src/components/thread/DeleteConfirmationDialog.tsx
index 8118edbf..ea2c595d 100644
--- a/frontend/src/components/thread/DeleteConfirmationDialog.tsx
+++ b/frontend/src/components/thread/DeleteConfirmationDialog.tsx
@@ -1,7 +1,7 @@
-"use client"
+'use client';
-import React from "react"
-import { Loader2 } from "lucide-react"
+import React from 'react';
+import { Loader2 } from 'lucide-react';
import {
AlertDialog,
@@ -12,14 +12,14 @@ import {
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
-} from "@/components/ui/alert-dialog"
+} from '@/components/ui/alert-dialog';
interface DeleteConfirmationDialogProps {
- isOpen: boolean
- onClose: () => void
- onConfirm: () => void
- threadName: string
- isDeleting: boolean
+ isOpen: boolean;
+ onClose: () => void;
+ onConfirm: () => void;
+ threadName: string;
+ isDeleting: boolean;
}
/**
@@ -38,7 +38,7 @@ export function DeleteConfirmationDialog({
Delete conversation
- Are you sure you want to delete the conversation{" "}
+ Are you sure you want to delete the conversation{' '}
"{threadName}"?
This action cannot be undone.
@@ -48,8 +48,8 @@ export function DeleteConfirmationDialog({
Cancel
{
- e.preventDefault()
- onConfirm()
+ e.preventDefault();
+ onConfirm();
}}
disabled={isDeleting}
className="bg-destructive text-white hover:bg-destructive/90"
@@ -60,11 +60,11 @@ export function DeleteConfirmationDialog({
Deleting...
>
) : (
- "Delete"
+ 'Delete'
)}
- )
-}
\ No newline at end of file
+ );
+}
diff --git a/frontend/src/components/thread/chat-input/model-selector.tsx b/frontend/src/components/thread/chat-input/model-selector.tsx
index e25c6610..57c66a1a 100644
--- a/frontend/src/components/thread/chat-input/model-selector.tsx
+++ b/frontend/src/components/thread/chat-input/model-selector.tsx
@@ -46,7 +46,7 @@ export const ModelSelector: React.FC = ({
case 'base-only':
return {
icon: ,
- tooltip: 'Requires Base plan or higher',
+ tooltip: 'Requires Pro plan or higher',
};
case 'extra-only':
return {
diff --git a/frontend/src/components/ui/status-overlay.tsx b/frontend/src/components/ui/status-overlay.tsx
index defa9ef4..30840a6e 100644
--- a/frontend/src/components/ui/status-overlay.tsx
+++ b/frontend/src/components/ui/status-overlay.tsx
@@ -4,9 +4,9 @@ import { useDeleteOperation } from '@/contexts/DeleteOperationContext';
export function StatusOverlay() {
const { state } = useDeleteOperation();
-
+
if (state.operation === 'none' || !state.isDeleting) return null;
-
+
return (
{state.operation === 'pending' && (
@@ -15,14 +15,14 @@ export function StatusOverlay() {
Processing...
>
)}
-
+
{state.operation === 'success' && (
<>
Completed
>
)}
-
+
{state.operation === 'error' && (
<>
@@ -31,4 +31,4 @@ export function StatusOverlay() {
)}
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/contexts/DeleteOperationContext.tsx b/frontend/src/contexts/DeleteOperationContext.tsx
index cde3146e..b3e5579e 100644
--- a/frontend/src/contexts/DeleteOperationContext.tsx
+++ b/frontend/src/contexts/DeleteOperationContext.tsx
@@ -1,4 +1,10 @@
-import React, { createContext, useContext, useReducer, useEffect, useRef } from 'react';
+import React, {
+ createContext,
+ useContext,
+ useReducer,
+ useEffect,
+ useRef,
+} from 'react';
type DeleteState = {
isDeleting: boolean;
@@ -7,7 +13,7 @@ type DeleteState = {
operation: 'none' | 'pending' | 'success' | 'error';
};
-type DeleteAction =
+type DeleteAction =
| { type: 'START_DELETE'; id: string; isActive: boolean }
| { type: 'DELETE_SUCCESS' }
| { type: 'DELETE_ERROR' }
@@ -17,7 +23,7 @@ const initialState: DeleteState = {
isDeleting: false,
targetId: null,
isActive: false,
- operation: 'none'
+ operation: 'none',
};
function deleteReducer(state: DeleteState, action: DeleteAction): DeleteState {
@@ -28,18 +34,18 @@ function deleteReducer(state: DeleteState, action: DeleteAction): DeleteState {
isDeleting: true,
targetId: action.id,
isActive: action.isActive,
- operation: 'pending'
+ operation: 'pending',
};
case 'DELETE_SUCCESS':
return {
...state,
- operation: 'success'
+ operation: 'success',
};
case 'DELETE_ERROR':
return {
...state,
isDeleting: false,
- operation: 'error'
+ operation: 'error',
};
case 'RESET':
return initialState;
@@ -52,20 +58,26 @@ type DeleteOperationContextType = {
state: DeleteState;
dispatch: React.Dispatch;
performDelete: (
- id: string,
- isActive: boolean,
+ id: string,
+ isActive: boolean,
deleteFunction: () => Promise,
- onComplete?: () => void
+ onComplete?: () => void,
) => Promise;
isOperationInProgress: React.MutableRefObject;
};
-const DeleteOperationContext = createContext(undefined);
+const DeleteOperationContext = createContext<
+ DeleteOperationContextType | undefined
+>(undefined);
-export function DeleteOperationProvider({ children }: { children: React.ReactNode }) {
+export function DeleteOperationProvider({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
const [state, dispatch] = useReducer(deleteReducer, initialState);
const isOperationInProgress = useRef(false);
-
+
// Listen for state changes to handle navigation
useEffect(() => {
if (state.operation === 'success' && state.isActive) {
@@ -75,112 +87,114 @@ export function DeleteOperationProvider({ children }: { children: React.ReactNod
// Use window.location for reliable navigation
window.location.pathname = '/dashboard';
} catch (error) {
- console.error("Navigation error:", error);
+ console.error('Navigation error:', error);
}
}, 500);
return () => clearTimeout(timer);
}
}, [state.operation, state.isActive]);
-
+
// Auto-reset after operations complete
useEffect(() => {
if (state.operation === 'success' && !state.isActive) {
const timer = setTimeout(() => {
dispatch({ type: 'RESET' });
// Ensure pointer events are restored
- document.body.style.pointerEvents = "auto";
+ document.body.style.pointerEvents = 'auto';
isOperationInProgress.current = false;
-
+
// Restore sidebar menu interactivity
- const sidebarMenu = document.querySelector(".sidebar-menu");
+ const sidebarMenu = document.querySelector('.sidebar-menu');
if (sidebarMenu) {
- sidebarMenu.classList.remove("pointer-events-none");
+ sidebarMenu.classList.remove('pointer-events-none');
}
}, 1000);
return () => clearTimeout(timer);
}
-
+
if (state.operation === 'error') {
// Reset on error immediately
- document.body.style.pointerEvents = "auto";
+ document.body.style.pointerEvents = 'auto';
isOperationInProgress.current = false;
-
+
// Restore sidebar menu interactivity
- const sidebarMenu = document.querySelector(".sidebar-menu");
+ const sidebarMenu = document.querySelector('.sidebar-menu');
if (sidebarMenu) {
- sidebarMenu.classList.remove("pointer-events-none");
+ sidebarMenu.classList.remove('pointer-events-none');
}
}
}, [state.operation, state.isActive]);
-
+
const performDelete = async (
- id: string,
- isActive: boolean,
+ id: string,
+ isActive: boolean,
deleteFunction: () => Promise,
- onComplete?: () => void
+ onComplete?: () => void,
) => {
// Prevent multiple operations
if (isOperationInProgress.current) return;
isOperationInProgress.current = true;
-
+
// Disable pointer events during operation
- document.body.style.pointerEvents = "none";
-
+ document.body.style.pointerEvents = 'none';
+
// Disable sidebar menu interactions
- const sidebarMenu = document.querySelector(".sidebar-menu");
+ const sidebarMenu = document.querySelector('.sidebar-menu');
if (sidebarMenu) {
- sidebarMenu.classList.add("pointer-events-none");
+ sidebarMenu.classList.add('pointer-events-none');
}
-
+
dispatch({ type: 'START_DELETE', id, isActive });
-
+
try {
// Execute the delete operation
await deleteFunction();
-
+
// Use precise timing for UI updates
setTimeout(() => {
dispatch({ type: 'DELETE_SUCCESS' });
-
+
// For non-active threads, restore interaction with delay
if (!isActive) {
setTimeout(() => {
- document.body.style.pointerEvents = "auto";
-
+ document.body.style.pointerEvents = 'auto';
+
if (sidebarMenu) {
- sidebarMenu.classList.remove("pointer-events-none");
+ sidebarMenu.classList.remove('pointer-events-none');
}
-
+
// Call the completion callback
if (onComplete) onComplete();
}, 100);
}
}, 50);
} catch (error) {
- console.error("Delete operation failed:", error);
-
+ console.error('Delete operation failed:', error);
+
// Reset states on error
- document.body.style.pointerEvents = "auto";
+ document.body.style.pointerEvents = 'auto';
isOperationInProgress.current = false;
-
+
if (sidebarMenu) {
- sidebarMenu.classList.remove("pointer-events-none");
+ sidebarMenu.classList.remove('pointer-events-none');
}
-
+
dispatch({ type: 'DELETE_ERROR' });
-
+
// Call the completion callback
if (onComplete) onComplete();
}
};
-
+
return (
-
+
{children}
);
@@ -189,7 +203,9 @@ export function DeleteOperationProvider({ children }: { children: React.ReactNod
export function useDeleteOperation() {
const context = useContext(DeleteOperationContext);
if (context === undefined) {
- throw new Error('useDeleteOperation must be used within a DeleteOperationProvider');
+ throw new Error(
+ 'useDeleteOperation must be used within a DeleteOperationProvider',
+ );
}
return context;
-}
\ No newline at end of file
+}
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index 766e26d9..db6cd8d4 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -1247,44 +1247,46 @@ export const toggleThreadPublicStatus = async (
export const deleteThread = async (threadId: string): Promise => {
try {
const supabase = createClient();
-
+
// First delete all agent runs associated with this thread
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}`);
}
-
+
// Then delete all messages associated with the thread
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}`);
}
-
+
// Finally, delete the thread itself
console.log(`Deleting thread ${threadId}`);
const { error: threadError } = 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}`);
}
-
- console.log(`Thread ${threadId} successfully deleted with all related items`);
+
+ console.log(
+ `Thread ${threadId} successfully deleted with all related items`,
+ );
} catch (error) {
console.error('Error deleting thread and related items:', error);
throw error;