134 lines
4.3 KiB
React
134 lines
4.3 KiB
React
// src/SessionTable.jsx
|
||
import React, { useEffect, useState } from "react";
|
||
import { getSessionId } from "./useSessionId";
|
||
|
||
export default function SessionTable({ userId, onSelectSession, onClosePanel }) {
|
||
const [sessions, setSessions] = useState([]);
|
||
const [loading, setLoading] = useState(false);
|
||
const [editingSessionId, setEditingSessionId] = useState(null);
|
||
const [editName, setEditName] = useState("");
|
||
const activeSessionId = getSessionId();
|
||
|
||
const fetchSessions = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const res = await fetch(`/v1/sessions?user_id=${userId}`);
|
||
const data = await res.json();
|
||
setSessions(data);
|
||
} catch (err) {
|
||
console.error("Failed to fetch sessions", err);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
if (userId) fetchSessions();
|
||
}, [userId]);
|
||
|
||
const startEditing = (session) => {
|
||
setEditingSessionId(session.session_id);
|
||
setEditName(session.session_name);
|
||
};
|
||
|
||
const saveName = async (sessionId) => {
|
||
try {
|
||
await fetch(`/v1/sessions/${sessionId}?user_id=${userId}`, {
|
||
method: "PATCH",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ session_name: editName }),
|
||
});
|
||
setEditingSessionId(null);
|
||
fetchSessions();
|
||
} catch (err) {
|
||
console.error("Failed to rename session", err);
|
||
}
|
||
};
|
||
|
||
const deleteSession = async (sessionId) => {
|
||
if (!window.confirm("Delete this session and its history?")) return;
|
||
try {
|
||
await fetch(`/v1/sessions/${sessionId}?user_id=${userId}`, { method: "DELETE" });
|
||
fetchSessions();
|
||
} catch (err) {
|
||
console.error("Failed to delete session", err);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="table-responsive session-table">
|
||
<table className="table table-striped table-hover align-middle">
|
||
<thead>
|
||
<tr>
|
||
<th></th>
|
||
<th>Session</th>
|
||
<th>Created</th>
|
||
<th className="text-nowrap"># mess</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{loading && <tr><td colSpan="5" className="text-center">Loading…</td></tr>}
|
||
{!loading && sessions.length === 0 && (
|
||
<tr><td colSpan="5" className="text-center">No sessions found</td></tr>
|
||
)}
|
||
{!loading && sessions.map((s) => (
|
||
<tr
|
||
key={s.session_id}
|
||
className={s.session_id === activeSessionId ? "table-primary" : ""}
|
||
>
|
||
<td className="text-end text-nowrap">
|
||
<button
|
||
className="btn btn-sm px-1 btn-outline-secondary me-1"
|
||
onClick={() => startEditing(s)}
|
||
title="Rename"
|
||
>✏️</button>
|
||
</td>
|
||
<td>
|
||
{editingSessionId === s.session_id ? (
|
||
<input
|
||
type="text"
|
||
className="form-control form-control-sm"
|
||
value={editName}
|
||
onChange={(e) => setEditName(e.target.value)}
|
||
onBlur={() => saveName(s.session_id)}
|
||
onKeyDown={(e) => {
|
||
if (e.key === "Enter") saveName(s.session_id);
|
||
if (e.key === "Escape") setEditingSessionId(null);
|
||
}}
|
||
autoFocus
|
||
/>
|
||
) : (
|
||
<span
|
||
role="button"
|
||
className="text-primary"
|
||
onClick={() => {
|
||
onSelectSession(s.session_id);
|
||
if (typeof onClosePanel === "function") onClosePanel();
|
||
}}
|
||
title="Click to load this session"
|
||
>
|
||
{s.session_name}
|
||
</span>
|
||
)}
|
||
</td>
|
||
<td>{new Date(s.created_at).toLocaleString()}</td>
|
||
<td title={s.history_size_bytes}>
|
||
{s.message_count}
|
||
</td>
|
||
<td className="text-end text-nowrap">
|
||
<button
|
||
className="btn btn-sm px-1 btn-outline-danger"
|
||
onClick={() => deleteSession(s.session_id)}
|
||
title="Delete"
|
||
>🗑️</button>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
);
|
||
}
|
||
|