Newer
Older
cortex-hub / ui / client-app / src / components / SessionSidebar.js
import React, { useState, useEffect } from 'react';
import {
    getUserSessions,
    deleteSession,
    deleteAllSessions,
    getSessionTokenStatus
} from '../services/apiService';
import './SessionSidebar.css';

const SessionSidebar = ({ featureName, currentSessionId, onSwitchSession, onNewSession }) => {
    const [isOpen, setIsOpen] = useState(false);
    const [sessions, setSessions] = useState([]);
    const [tokenHoverData, setTokenHoverData] = useState({});
    const [isLoading, setIsLoading] = useState(false);

    useEffect(() => {
        if (isOpen) fetchSessions();
    }, [isOpen, featureName, currentSessionId]);

    const fetchSessions = async () => {
        setIsLoading(true);
        try {
            const data = await getUserSessions(featureName);
            setSessions(data || []);
        } catch (err) {
            console.error('Failed to fetch sessions:', err);
        } finally {
            setIsLoading(false);
        }
    };

    const handleMouseEnter = async (sessionId) => {
        if (tokenHoverData[sessionId]) return;
        try {
            const data = await getSessionTokenStatus(sessionId);
            setTokenHoverData(prev => ({ ...prev, [sessionId]: data }));
        } catch (err) { /* silent */ }
    };

    const handleDelete = async (e, sessionId) => {
        e.stopPropagation();
        if (!window.confirm('Delete this session?')) return;
        try {
            await deleteSession(sessionId);
            fetchSessions();
            if (Number(currentSessionId) === sessionId) {
                localStorage.removeItem(`sessionId_${featureName}`);
                if (onNewSession) onNewSession();
            }
        } catch { alert('Failed to delete session.'); }
    };

    const handleDeleteAll = async () => {
        if (!window.confirm('Delete ALL history for this feature?')) return;
        try {
            await deleteAllSessions(featureName);
            fetchSessions();
            if (onNewSession) onNewSession();
        } catch { alert('Failed to delete all sessions.'); }
    };

    const formatDate = (iso) => {
        const d = new Date(iso);
        const now = new Date();
        const diffDays = Math.floor((now - d) / 86400000);
        if (diffDays === 0) return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
        if (diffDays === 1) return 'Yesterday';
        if (diffDays < 7) return d.toLocaleDateString([], { weekday: 'short' });
        return d.toLocaleDateString([], { month: 'short', day: 'numeric' });
    };

    const prettyFeatureName = featureName
        .split('_')
        .map(w => w.charAt(0).toUpperCase() + w.slice(1))
        .join(' ');

    return (
        <div className={`session-sidebar ${isOpen ? 'open' : ''}`}>
            {/* ▶/◀ Tab handle */}
            <div className="sidebar-toggle" onClick={() => setIsOpen(!isOpen)}>
                <span className="sidebar-toggle-arrow">{isOpen ? '◀' : '▶'}</span>
                <span className="sidebar-toggle-label">History</span>
            </div>

            {isOpen && (
                <div className="sidebar-content">
                    <div className="sidebar-header">
                        <h3>{prettyFeatureName} History</h3>
                        <button className="delete-all" onClick={handleDeleteAll}>
                            Clear All
                        </button>
                    </div>

                    <div className="sidebar-list">
                        {isLoading ? (
                            <p className="sidebar-loading">Loading sessions…</p>
                        ) : sessions.length === 0 ? (
                            <p className="sidebar-empty">No past sessions yet.</p>
                        ) : (
                            sessions.map(s => {
                                const isActive = Number(currentSessionId) === s.id;
                                const td = tokenHoverData[s.id];
                                const tooltip = td
                                    ? `Context: ${td.token_count.toLocaleString()} / ${td.token_limit.toLocaleString()} tokens (${td.percentage}%)`
                                    : 'Hover to load token usage';

                                // Derive a display title: prefer session.title, fall back gracefully
                                const displayTitle = s.title &&
                                    s.title !== 'New Chat Session'
                                    ? s.title
                                    : `Session #${s.id}`;

                                return (
                                    <div
                                        key={s.id}
                                        className={`sidebar-item ${isActive ? 'active' : ''}`}
                                        onClick={() => onSwitchSession(s.id)}
                                        onMouseEnter={() => handleMouseEnter(s.id)}
                                        title={tooltip}
                                    >
                                        <div className="sidebar-item-info">
                                            <span className="sidebar-item-title">{displayTitle}</span>
                                            <div className="sidebar-item-meta">
                                                <span className="sidebar-item-date">{formatDate(s.created_at)}</span>
                                                {s.provider_name && (
                                                    <span className="sidebar-item-provider">{s.provider_name}</span>
                                                )}
                                            </div>
                                        </div>
                                        <button
                                            className="sidebar-item-delete"
                                            onClick={(e) => handleDelete(e, s.id)}
                                            title="Delete this session"
                                        >
                                            ×
                                        </button>
                                    </div>
                                );
                            })
                        )}
                    </div>
                </div>
            )}
        </div>
    );
};

export default SessionSidebar;