diff --git a/frontend/src/components/DeleteConfirmation.tsx b/frontend/src/components/DeleteConfirmation.tsx
index 9fdcad7..eac6e31 100644
--- a/frontend/src/components/DeleteConfirmation.tsx
+++ b/frontend/src/components/DeleteConfirmation.tsx
@@ -14,70 +14,127 @@ import { useTheme } from '@mui/material/styles';
import ResetIcon from '@mui/icons-material/History';
interface DeleteConfirmationProps {
- onDelete: () => void,
- disabled?: boolean,
- label?: string,
- color?: "inherit" | "default" | "primary" | "secondary" | "error" | "info" | "success" | "warning" | undefined
-};
+ // Legacy props for backward compatibility (uncontrolled mode)
+ onDelete?: () => void;
+ disabled?: boolean;
+ label?: string;
+ action?: "delete" | "reset";
+ color?: "inherit" | "default" | "primary" | "secondary" | "error" | "info" | "success" | "warning" | undefined;
-const DeleteConfirmation = (props : DeleteConfirmationProps) => {
- const { onDelete, disabled, label, color } = props;
- const [open, setOpen] = useState(false);
+ // New props for controlled mode
+ open?: boolean;
+ onClose?: () => void;
+ onConfirm?: () => void;
+ title?: string;
+ message?: string;
+
+ // Optional props for button customization in controlled mode
+ hideButton?: boolean;
+ confirmButtonText?: string;
+ cancelButtonText?: string;
+}
+
+function capitalizeFirstLetter(str: string) {
+ return str.charAt(0).toUpperCase() + str.slice(1);
+}
+
+const DeleteConfirmation = (props: DeleteConfirmationProps) => {
+ const {
+ // Legacy props
+ onDelete,
+ disabled,
+ label,
+ color,
+ action = "delete",
+ // New props
+ open: controlledOpen,
+ onClose: controlledOnClose,
+ onConfirm,
+ title,
+ message,
+ hideButton = false,
+ confirmButtonText,
+ cancelButtonText = "Cancel"
+ } = props;
+
+ // Internal state for uncontrolled mode
+ const [internalOpen, setInternalOpen] = useState(false);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
+ // Determine if we're in controlled or uncontrolled mode
+ const isControlled = controlledOpen !== undefined;
+ const isOpen = isControlled ? controlledOpen : internalOpen;
+
const handleClickOpen = () => {
- setOpen(true);
+ if (!isControlled) {
+ setInternalOpen(true);
+ }
};
const handleClose = () => {
- setOpen(false);
+ if (isControlled) {
+ controlledOnClose?.();
+ } else {
+ setInternalOpen(false);
+ }
};
- const handleConfirmReset = () => {
- onDelete();
- setOpen(false);
+ const handleConfirm = () => {
+ if (isControlled) {
+ onConfirm?.();
+ } else {
+ onDelete?.();
+ setInternalOpen(false);
+ }
};
+ // Determine dialog content based on mode
+ const dialogTitle = title || "Confirm Reset";
+ const dialogMessage = message || `This action will permanently ${capitalizeFirstLetter(action)} ${label ? label.toLowerCase() : "all data"} without the ability to recover it. Are you sure you want to continue?`;
+ const confirmText = confirmButtonText || `${capitalizeFirstLetter(action)} ${label || "Everything"}`;
+
return (
<>
-
- { /* This span is used to wrap the IconButton to ensure Tooltip works even when disabled */}
-
-
-
-
-
+ {/* Only show button if not hidden (for controlled mode) */}
+ {!hideButton && (
+
+ {/* This span is used to wrap the IconButton to ensure Tooltip works even when disabled */}
+
+
+
+
+
+ )}
diff --git a/frontend/src/pages/CandidateChatPage.tsx b/frontend/src/pages/CandidateChatPage.tsx
index 4e7b273..0f63c19 100644
--- a/frontend/src/pages/CandidateChatPage.tsx
+++ b/frontend/src/pages/CandidateChatPage.tsx
@@ -15,15 +15,21 @@ import {
Card,
CardContent,
Avatar,
- Drawer,
useTheme,
useMediaQuery,
- Fab
+ Fab,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Backdrop
} from '@mui/material';
import {
ChevronLeft,
ChevronRight,
- Chat as ChatIcon
+ Chat as ChatIcon,
+ Edit as EditIcon,
+ Delete as DeleteIcon
} from '@mui/icons-material';
import { useAuth } from 'hooks/AuthContext';
import { ChatMessageBase, ChatMessage, ChatSession, ChatStatusType, ChatMessageType, ChatMessageUser } from 'types/types';
@@ -38,12 +44,15 @@ import { useSelectedCandidate } from 'hooks/GlobalContext';
import PropagateLoader from 'react-spinners/PropagateLoader';
const DRAWER_WIDTH = 300;
-const HANDLE_WIDTH = 48;
+const FAB_WIDTH = 48;
+const FAB_HEIGHT = 64;
const defaultMessage: ChatMessage = {
type: "preparing", status: "done", sender: "system", sessionId: "", timestamp: new Date(), content: ""
};
+type HandlePosition = 'center' | 'top' | 'bottom';
+
const CandidateChatPage = forwardRef((props: BackstoryPageProps, ref) => {
const { apiClient } = useAuth();
const { selectedCandidate } = useSelectedCandidate()
@@ -66,13 +75,76 @@ const CandidateChatPage = forwardRef((pr
const [streaming, setStreaming] = useState(false);
const messagesEndRef = useRef(null);
- // Drawer state - defaults to open on md+ screens or when no session is selected
- const [drawerOpen, setDrawerOpen] = useState(() => isMdUp || !chatSession);
+ // Drawer state - defaults to open on md+ screens
+ const [drawerOpen, setDrawerOpen] = useState(() => isMdUp);
- // Update drawer state when screen size or session changes
+ // Handle position state for mobile scroll behavior
+ const [handlePosition, setHandlePosition] = useState('center');
+ const lastScrollY = useRef(0);
+ const scrollTimeout = useRef(null);
+
+ // Edit session state
+ const [editDialogOpen, setEditDialogOpen] = useState(false);
+ const [editingSession, setEditingSession] = useState(null);
+ const [editSessionTitle, setEditSessionTitle] = useState('');
+
+ // Delete confirmation state
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
+ const [sessionToDelete, setSessionToDelete] = useState(null);
+
+ // Update drawer state when screen size changes
useEffect(() => {
- setDrawerOpen(isMdUp || !chatSession);
- }, [isMdUp, chatSession]);
+ if (isMdUp && !drawerOpen) {
+ setDrawerOpen(true);
+ }
+ }, [isMdUp]);
+
+ // Scroll event handler for mobile handle positioning
+ useEffect(() => {
+ if (isMdUp) return; // Only for mobile
+
+ const handleScroll = () => {
+ const currentScrollY = window.scrollY;
+ const scrollDirection = currentScrollY > lastScrollY.current ? 'down' : 'up';
+
+ // Clear existing timeout
+ if (scrollTimeout.current) {
+ clearTimeout(scrollTimeout.current);
+ }
+
+ // Update handle position based on scroll direction
+ if (scrollDirection === 'down' && currentScrollY > 50) {
+ setHandlePosition('top');
+ } else if (scrollDirection === 'up' && currentScrollY > 50) {
+ setHandlePosition('bottom');
+ }
+
+ lastScrollY.current = currentScrollY;
+
+ // Reset to center after scrolling stops (with debounce)
+ scrollTimeout.current = setTimeout(() => {
+ if (currentScrollY <= 50) {
+ setHandlePosition('center');
+ }
+ }, 150);
+ };
+
+ window.addEventListener('scroll', handleScroll, { passive: true });
+
+ return () => {
+ window.removeEventListener('scroll', handleScroll);
+ if (scrollTimeout.current) {
+ clearTimeout(scrollTimeout.current);
+ }
+ };
+ }, [isMdUp]);
+
+ // Close drawer when clicking outside on mobile only
+ const handleBackdropClick = () => {
+ if (!isMdUp) {
+ setDrawerOpen(false);
+ }
+ };
// Load sessions for the selectedCandidate
const loadSessions = async () => {
@@ -113,7 +185,7 @@ const CandidateChatPage = forwardRef((pr
const newSession = await apiClient.createCandidateChatSession(
selectedCandidate.username,
'candidate_chat',
- `Interview Discussion - ${selectedCandidate.username}`
+ `Backstory chat about ${selectedCandidate.fullName}`
);
setChatSession(newSession);
setMessages([]);
@@ -127,6 +199,60 @@ const CandidateChatPage = forwardRef((pr
}
};
+ // Edit session
+ const handleEditSession = (session: ChatSession, event: React.MouseEvent) => {
+ event.stopPropagation(); // Prevent session selection
+ setEditingSession(session);
+ setEditSessionTitle(session.title || '');
+ setEditDialogOpen(true);
+ };
+
+ const handleSaveEdit = async () => {
+ if (!editingSession?.id || !editSessionTitle.trim()) return;
+
+ try {
+ // Assuming there's an API method to update session title
+ await apiClient.updateChatSession(editingSession.id, { title: editSessionTitle.trim() });
+ await loadSessions(); // Refresh sessions list
+ setEditDialogOpen(false);
+ setEditingSession(null);
+ setEditSessionTitle('');
+ setSnack('Session title updated successfully', 'success');
+ } catch (error) {
+ console.error('Failed to update session:', error);
+ setSnack('Failed to update session title', 'error');
+ }
+ };
+
+ // Delete session
+ const handleDeleteSession = (session: ChatSession, event: React.MouseEvent) => {
+ event.stopPropagation(); // Prevent session selection
+ setSessionToDelete(session);
+ setDeleteDialogOpen(true);
+ };
+
+ const handleConfirmDelete = async () => {
+ if (!sessionToDelete?.id) return;
+
+ try {
+ await apiClient.deleteChatSession(sessionToDelete.id);
+ await loadSessions(); // Refresh sessions list
+
+ // If we're deleting the currently selected session, clear it
+ if (chatSession?.id === sessionToDelete.id) {
+ setChatSession(null);
+ setMessages([]);
+ }
+
+ setDeleteDialogOpen(false);
+ setSessionToDelete(null);
+ setSnack('Session deleted successfully', 'success');
+ } catch (error) {
+ console.error('Failed to delete session:', error);
+ setSnack('Failed to delete session', 'error');
+ }
+ };
+
// Send message
const sendMessage = async () => {
if (!newMessage.trim() || !chatSession?.id || streaming) return;
@@ -215,37 +341,99 @@ const CandidateChatPage = forwardRef((pr
navigate('/find-a-candidate');
}
+ // Get handle positioning styles based on current position
+ const getHandleStyles = () => {
+ const baseStyles = {
+ position: 'absolute' as const,
+ left: DRAWER_WIDTH,
+ zIndex: theme.zIndex.drawer + 1,
+ };
+
+ switch (handlePosition) {
+ case 'top':
+ return {
+ ...baseStyles,
+ position: 'fixed' as const,
+ top: 20,
+ transform: 'translateY(0)',
+ };
+ case 'bottom':
+ return {
+ ...baseStyles,
+ position: 'fixed' as const,
+ bottom: 20,
+ transform: 'translateY(0)',
+ };
+ case 'center':
+ default:
+ return {
+ ...baseStyles,
+ top: '50%',
+ transform: 'translateY(-50%)',
+ };
+ }
+ };
+
const drawerContent = (
-
-
- Chat Sessions
- {sessions && (
-
- )}
-
+
+ {/* Fixed Header Section */}
+
+
+ Chat Sessions
+ {sessions && (
+
+ )}
+
-
- New Session
-
+
+ New Session
+
+
-
+ {/* Scrollable Sessions List */}
+
{sessions ? (
-
+
{sessions.sessions.data.map((session: any) => (
{
setChatSession(session);
- // Auto-close drawer on smaller screens when session is selected
+ // Auto-close drawer on mobile only when session is selected
if (!isMdUp) {
setDrawerOpen(false);
}
@@ -254,22 +442,54 @@ const CandidateChatPage = forwardRef((pr
mb: 1,
borderRadius: 1,
border: '1px solid',
- borderColor: chatSession?.id === session.id ? 'primary.main' : 'divider',
+ borderColor: chatSession?.id === session.id ? 'orange' : 'divider',
cursor: 'pointer',
+ backgroundColor: 'transparent',
'&:hover': {
- backgroundColor: 'action.hover'
+ backgroundColor: chatSession?.id === session.id ? 'primary.light' : 'action.hover'
}
}}
+ secondaryAction={
+
+ handleEditSession(session, e)}
+ sx={{
+ '&:hover': {
+ backgroundColor: 'action.hover',
+ color: 'primary.main'
+ }
+ }}
+ >
+
+
+ handleDeleteSession(session, e)}
+ sx={{
+ '&:hover': {
+ backgroundColor: 'error.light',
+ color: 'error.main'
+ }
+ }}
+ >
+
+
+
+ }
>
))}
) : (
-
+
Enter a username and click "Load Sessions"
)}
@@ -277,69 +497,106 @@ const CandidateChatPage = forwardRef((pr
);
- const drawerHandle = (
-
- setDrawerOpen(!drawerOpen)}
- sx={{
- borderRadius: '0 50% 50% 0',
- width: 32,
- height: 64,
- minHeight: 64,
- boxShadow: 2,
- '&:hover': {
- boxShadow: 4,
- }
- }}
- >
- {drawerOpen ? : }
-
-
- );
-
return (
-
- {selectedCandidate && }
+
+ {selectedCandidate && (
+
+ )}
-
- {/* Drawer */}
-
+
+ {/* Mobile Backdrop */}
+ {!isMdUp && drawerOpen && (
+
+ )}
+
+ {/* Drawer Container - Different behavior for mobile vs desktop */}
+
- {drawerContent}
-
+ {/* Drawer Content */}
+
+ {drawerContent}
+
- {/* Drawer Handle */}
- {drawerHandle}
+ {/* Integrated Fab Handle - Mobile Only */}
+ {!isMdUp && (
+
+ setDrawerOpen(!drawerOpen)}
+ sx={{
+ borderRadius: '0 50% 50% 0',
+ width: FAB_WIDTH,
+ height: FAB_HEIGHT,
+ minHeight: FAB_HEIGHT,
+ boxShadow: 2,
+ transition: theme.transitions.create(['box-shadow', 'background-color'], {
+ duration: theme.transitions.duration.short,
+ }),
+ '&:hover': {
+ boxShadow: 4,
+ }
+ }}
+ >
+ {drawerOpen ? : }
+
+
+ )}
+
{/* Chat Interface */}
((pr
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
- transition: theme.transitions.create('margin', {
- easing: theme.transitions.easing.sharp,
- duration: theme.transitions.duration.standard,
- }),
+ marginLeft: isMdUp ? 0 : 0,
+ width: isMdUp ? `calc(100% - ${DRAWER_WIDTH}px)` : '100%',
+ overflow: 'hidden',
+ minHeight: 0, // Important for flex child
}}
>
{chatSession?.id ? (
<>
-
- {
- messages.map((message: ChatMessageBase) => (
-
- ))
- }
- {
- processingMessage !== null &&
+ {/* Scrollable Messages Area */}
+
+ {messages.map((message: ChatMessageBase) => (
+
+ ))}
+ {processingMessage !== null && (
- }
- {
- streamingMessage !== null &&
+ )}
+ {streamingMessage !== null && (
- }
- {streaming &&
-
-
- }
+ )}
+ {streaming && (
+
+
+
+ )}
-
- {/* Message Input */}
-
+ {/* Fixed Message Input */}
+
((pr
)}
+
+ {/* Edit Session Dialog */}
+
+
+ {/* Delete Confirmation Dialog */}
+ setDeleteDialogOpen(false)}
+ onConfirm={handleConfirmDelete}
+ title="Delete Chat Session"
+ message={`Are you sure you want to delete the session "${sessionToDelete?.title}"? This action cannot be undone.`}
+ />
);
});
diff --git a/frontend/src/services/api-client.ts b/frontend/src/services/api-client.ts
index c320cd7..518c987 100644
--- a/frontend/src/services/api-client.ts
+++ b/frontend/src/services/api-client.ts
@@ -109,6 +109,13 @@ export interface CreateChatSessionRequest {
title?: string;
}
+export interface UpdateChatSessionRequest {
+ title?: string;
+ context?: Partial;
+ isArchived?: boolean;
+ systemPrompt?: string;
+}
+
export interface CandidateSessionsResponse {
candidate: {
id: string;
@@ -505,6 +512,34 @@ class ApiClient {
return this.handleApiResponseWithConversion(response, 'ChatSession');
}
+ /**
+ * Update a chat session's properties
+ */
+ async updateChatSession(
+ id: string,
+ updates: UpdateChatSessionRequest
+ ): Promise {
+ const response = await fetch(`${this.baseUrl}/chat/sessions/${id}`, {
+ method: 'PATCH',
+ headers: this.defaultHeaders,
+ body: JSON.stringify(formatApiRequest(updates))
+ });
+
+ return this.handleApiResponseWithConversion(response, 'ChatSession');
+ }
+
+ /**
+ * Delete a chat session
+ */
+ async deleteChatSession(id: string): Promise<{ success: boolean; message: string }> {
+ const response = await fetch(`${this.baseUrl}/chat/sessions/${id}`, {
+ method: 'DELETE',
+ headers: this.defaultHeaders
+ });
+
+ return handleApiResponse<{ success: boolean; message: string }>(response);
+ }
+
/**
* Send message with streaming response support and date conversion
*/
@@ -971,6 +1006,24 @@ try {
console.error('Failed to fetch jobs:', error);
}
+// Update and delete chat sessions with proper date handling
+try {
+ // Update a session title
+ const updatedSession = await apiClient.updateChatSession('session-id', {
+ title: 'New Session Title',
+ isArchived: false
+ });
+
+ console.log('Updated session:', updatedSession.title);
+ console.log('Last activity:', updatedSession.lastActivity.toLocaleString());
+
+ // Delete a session
+ const deleteResult = await apiClient.deleteChatSession('session-id');
+ console.log('Delete result:', deleteResult.message);
+} catch (error) {
+ console.error('Failed to manage session:', error);
+}
+
// Streaming with proper date conversion
const streamResponse = apiClient.sendMessageStream(sessionId, 'Tell me about job opportunities', {
onStreaming: (chunk) => {
diff --git a/src/backend/database.py b/src/backend/database.py
index b1492b6..2995ae2 100644
--- a/src/backend/database.py
+++ b/src/backend/database.py
@@ -417,12 +417,29 @@ class RedisDatabase:
result[session_id] = self._deserialize(value)
return result
-
- async def delete_chat_session(self, session_id: str):
- """Delete chat session"""
- key = f"{self.KEY_PREFIXES['chat_sessions']}{session_id}"
- await self.redis.delete(key)
-
+
+ async def delete_chat_session(self, session_id: str) -> bool:
+ '''Delete a chat session from Redis'''
+ try:
+ result = await self.redis.delete(f"chat_session:{session_id}")
+ return result > 0
+ except Exception as e:
+ logger.error(f"Error deleting chat session {session_id}: {e}")
+ raise
+
+ async def delete_chat_message(self, session_id: str, message_id: str) -> bool:
+ '''Delete a specific chat message from Redis'''
+ try:
+ # Remove from the session's message list
+ await self.redis.lrem(f"chat_messages:{session_id}", 0, message_id)
+
+ # Delete the message data itself
+ result = await self.redis.delete(f"chat_message:{message_id}")
+ return result > 0
+ except Exception as e:
+ logger.error(f"Error deleting chat message {message_id}: {e}")
+ raise
+
# Chat Messages operations (stored as lists)
async def get_chat_messages(self, session_id: str) -> List[Dict]:
"""Get chat messages for a session"""
diff --git a/src/backend/main.py b/src/backend/main.py
index 226e1fb..01a6d25 100644
--- a/src/backend/main.py
+++ b/src/backend/main.py
@@ -1576,6 +1576,164 @@ async def get_chat_session_messages(
content=create_error_response("FETCH_ERROR", str(e))
)
+@api_router.patch("/chat/sessions/{session_id}")
+async def update_chat_session(
+ session_id: str = Path(...),
+ updates: Dict[str, Any] = Body(...),
+ current_user = Depends(get_current_user),
+ database: RedisDatabase = Depends(get_database)
+):
+ """Update a chat session's properties"""
+ try:
+ # Get the existing session
+ session_data = await database.get_chat_session(session_id)
+ if not session_data:
+ return JSONResponse(
+ status_code=404,
+ content=create_error_response("NOT_FOUND", "Chat session not found")
+ )
+
+ session = ChatSession.model_validate(session_data)
+
+ # Check authorization - user can only update their own sessions
+ if session.user_id != current_user.id:
+ return JSONResponse(
+ status_code=403,
+ content=create_error_response("FORBIDDEN", "Cannot update another user's chat session")
+ )
+
+ # Validate and apply updates
+ allowed_fields = {"title", "context", "isArchived", "systemPrompt"}
+ filtered_updates = {k: v for k, v in updates.items() if k in allowed_fields}
+
+ if not filtered_updates:
+ return JSONResponse(
+ status_code=400,
+ content=create_error_response("INVALID_UPDATES", "No valid fields provided for update")
+ )
+
+ # Apply updates to session data
+ session_dict = session.model_dump()
+
+ # Handle special field mappings (camelCase to snake_case)
+ if "isArchived" in filtered_updates:
+ session_dict["is_archived"] = filtered_updates["isArchived"]
+ if "systemPrompt" in filtered_updates:
+ session_dict["system_prompt"] = filtered_updates["systemPrompt"]
+ if "title" in filtered_updates:
+ session_dict["title"] = filtered_updates["title"]
+ if "context" in filtered_updates:
+ # Merge context updates with existing context
+ existing_context = session_dict.get("context", {})
+ context_updates = filtered_updates["context"]
+
+ # Update specific context fields while preserving others
+ for context_key, context_value in context_updates.items():
+ if context_key == "additionalContext":
+ # Merge additional context
+ existing_additional = existing_context.get("additional_context", {})
+ existing_additional.update(context_value)
+ existing_context["additional_context"] = existing_additional
+ else:
+ # Convert camelCase to snake_case for context fields
+ snake_key = context_key
+ if context_key == "relatedEntityId":
+ snake_key = "related_entity_id"
+ elif context_key == "relatedEntityType":
+ snake_key = "related_entity_type"
+ elif context_key == "aiParameters":
+ snake_key = "ai_parameters"
+
+ existing_context[snake_key] = context_value
+
+ session_dict["context"] = existing_context
+
+ # Update last activity timestamp
+ session_dict["last_activity"] = datetime.now(UTC).isoformat()
+
+ # Validate the updated session
+ updated_session = ChatSession.model_validate(session_dict)
+
+ # Save to database
+ await database.set_chat_session(session_id, updated_session.model_dump())
+
+ logger.info(f"✅ Chat session {session_id} updated by user {current_user.id}")
+
+ return create_success_response(updated_session.model_dump(by_alias=True, exclude_unset=True))
+
+ except ValueError as ve:
+ logger.warning(f"⚠️ Validation error updating chat session: {ve}")
+ return JSONResponse(
+ status_code=400,
+ content=create_error_response("VALIDATION_ERROR", str(ve))
+ )
+ except Exception as e:
+ logger.error(f"❌ Update chat session error: {e}")
+ return JSONResponse(
+ status_code=500,
+ content=create_error_response("UPDATE_ERROR", str(e))
+ )
+
+@api_router.delete("/chat/sessions/{session_id}")
+async def delete_chat_session(
+ session_id: str = Path(...),
+ current_user = Depends(get_current_user),
+ database: RedisDatabase = Depends(get_database)
+):
+ """Delete a chat session and all its messages"""
+ try:
+ # Get the session to verify it exists and check ownership
+ session_data = await database.get_chat_session(session_id)
+ if not session_data:
+ return JSONResponse(
+ status_code=404,
+ content=create_error_response("NOT_FOUND", "Chat session not found")
+ )
+
+ session = ChatSession.model_validate(session_data)
+
+ # Check authorization - user can only delete their own sessions
+ if session.user_id != current_user.id:
+ return JSONResponse(
+ status_code=403,
+ content=create_error_response("FORBIDDEN", "Cannot delete another user's chat session")
+ )
+
+ # Delete all messages associated with this session
+ try:
+ chat_messages = await database.get_chat_messages(session_id)
+ message_count = len(chat_messages)
+
+ # Delete each message
+ for message_data in chat_messages:
+ message_id = message_data.get("id")
+ if message_id:
+ await database.delete_chat_message(session_id, message_id)
+
+ logger.info(f"🗑️ Deleted {message_count} messages from session {session_id}")
+
+ except Exception as e:
+ logger.warning(f"⚠️ Error deleting messages for session {session_id}: {e}")
+ # Continue with session deletion even if message deletion fails
+
+ # Delete the session itself
+ await database.delete_chat_session(session_id)
+
+ logger.info(f"🗑️ Chat session {session_id} deleted by user {current_user.id}")
+
+ return create_success_response({
+ "success": True,
+ "message": "Chat session deleted successfully",
+ "sessionId": session_id
+ })
+
+ except Exception as e:
+ logger.error(f"❌ Delete chat session error: {e}")
+ return JSONResponse(
+ status_code=500,
+ content=create_error_response("DELETE_ERROR", str(e))
+ )
+
@api_router.get("/candidates/{username}/chat-sessions")
async def get_candidate_chat_sessions(
username: str = Path(...),