Skip to content
Open
37 changes: 24 additions & 13 deletions backend/routes/sessions.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
"""Sessions routes — /api/sessions — full CRUD"""
"""Sessions routes — /api/sessions — full CRUD + reorder"""

import uuid
from typing import List
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from models.schemas import SessionCreate, SessionUpdate, BulkSessionRenameRequest
from services import db_service
# from backend.models.schemas import BulkSessionRenameRequest # Adjust if other items are imported from here

router = APIRouter()


# ─── Request models for reorder ─────────────────────────────
class ReorderSessionsRequest(BaseModel):
session_ids: List[str]


@router.get("/")
async def list_sessions():
return db_service.get_all_sessions()
Expand All @@ -20,6 +26,7 @@ async def create_session(body: SessionCreate):
session = db_service.create_session(sid, title=body.title, model=body.model)
return session


@router.patch("/bulk-rename")
async def bulk_rename_sessions(body: BulkSessionRenameRequest):
try:
Expand Down Expand Up @@ -62,8 +69,21 @@ async def bulk_rename_sessions(body: BulkSessionRenameRequest):
status_code=500,
detail=f"Bulk rename failed: {str(e)}"
)




@router.patch("/reorder")
async def reorder_sessions(req: ReorderSessionsRequest):
"""
Update the order of sessions for drag‑and‑drop.
Expects a list of session IDs in the desired order (top to bottom).
"""
try:
db_service.update_sessions_order(req.session_ids)
return {"success": True, "message": "Session order updated"}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Reorder failed: {str(e)}")


@router.get("/{session_id}")
async def get_session(session_id: str):
s = db_service.get_session(session_id)
Expand All @@ -83,7 +103,6 @@ async def delete_session(session_id: str):
db_service.delete_session(session_id)
try:
from services import rag_service

rag_service.delete_session_index(session_id)
except Exception:
pass
Expand Down Expand Up @@ -112,15 +131,7 @@ async def get_documents(session_id: str):
async def rag_stats(session_id: str):
try:
from services import rag_service

count = rag_service.get_indexed_count(session_id)
except Exception:
count = 0
return {"session_id": session_id, "indexed_chunks": count}


@router.delete("/")
async def clear_all_sessions():
db_service.clear_all_sessions()
return {"message": "All sessions cleared"}

32 changes: 25 additions & 7 deletions backend/services/db_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ def get_db():
if conn:
conn.close()


def ensure_order_index_column():
"""Add order_index column to sessions table if not exists (for drag‑and‑drop reordering)."""
with get_db() as conn:
try:
conn.execute("ALTER TABLE sessions ADD COLUMN order_index INTEGER DEFAULT 0")
except sqlite3.OperationalError:
pass # column already exists


def init_db():
"""Create all tables on startup."""
with get_db() as conn:
Expand Down Expand Up @@ -122,12 +132,8 @@ def init_db():
prompt TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
);

""")
try:
conn.execute("ALTER TABLE documents ADD COLUMN status TEXT DEFAULT 'completed'")
except sqlite3.OperationalError:
pass # column already exists
ensure_order_index_column() # <-- add order_index column

cols = [row[1] for row in conn.execute("PRAGMA table_info(messages)").fetchall()]
if "benchmarks" not in cols:
Expand Down Expand Up @@ -168,9 +174,10 @@ def clear_all_sessions():


def get_all_sessions() -> list[dict]:
"""Fetch sessions sorted by manual order (order_index) then by last updated."""
with get_db() as conn:
rows = conn.execute(
"SELECT * FROM sessions ORDER BY updated_at DESC"
"SELECT * FROM sessions ORDER BY order_index ASC, updated_at DESC"
).fetchall()
return [dict(r) for r in rows]

Expand Down Expand Up @@ -349,4 +356,15 @@ def update_prompt_template(template_id: int, prompt_title: str = None, prompt: s
def delete_prompt_template(template_id: int):
"""Delete a prompt template by ID."""
with get_db() as conn:
conn.execute("DELETE FROM prompt_templates WHERE id = ?", (template_id,))
conn.execute("DELETE FROM prompt_templates WHERE id = ?", (template_id,))


# ─── Drag‑and‑drop reordering───────────
def update_sessions_order(session_ids: list):
"""Update order_index for each session based on its position in the list (0,1,2...)."""
with get_db() as conn:
for idx, session_id in enumerate(session_ids):
conn.execute(
"UPDATE sessions SET order_index = ? WHERE id = ?",
(idx, session_id)
)
69 changes: 62 additions & 7 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
"screenshot": "node scripts/capture-screenshots.js"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"react": "^18.3.0",
"react-dom": "^18.3.0",
"uuid": "^10.0.0"
Expand Down
43 changes: 26 additions & 17 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function App() {
const [loading, setLoading] = useState(false);
const [streaming, setStreaming] = useState(false);
const [panel, setPanel] = useState(null); // "upload"|"plugins"|"settings"|null
const [view, setView] = useState("chat"); // "chat"|"prompts"
const [view, setView] = useState("chat"); // "chat"|"prompts"
const [language, setLanguage] = useState("en");
const [ollamaOk, setOllamaOk] = useState(null);
const [settings, setSettings] = useState({});
Expand All @@ -30,7 +30,6 @@ export default function App() {
// ── Global keyboard shortcut: Ctrl+Shift+N (or Cmd+Shift+N on Mac) → New Chat ──
useEffect(() => {
const handleKeyDown = (e) => {
console.log("Key pressed:", e.key, "Ctrl:", e.ctrlKey, "Shift:", e.shiftKey); // ADD THIS
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "N") {
e.preventDefault();
newChat();
Expand All @@ -57,11 +56,17 @@ export default function App() {
}

const refreshSessions = useCallback(async () => {
try { const s = await api.getSessions(); setSessions(s || []); } catch { }
try {
const s = await api.getSessions();
setSessions(s || []);
} catch {}
}, []);

const refreshDocuments = useCallback(async (sid) => {
try { const d = await api.getDocuments(sid); setDocuments(d.documents || []); } catch { }
try {
const d = await api.getDocuments(sid);
setDocuments(d.documents || []);
} catch {}
}, []);

async function sendMessage(text) {
Expand Down Expand Up @@ -103,16 +108,16 @@ export default function App() {
}

async function newChat() {
const sid = uuidv4();
try {
await api.createSession({ title: "New Chat", model });
} catch {}
setSessionId(sid);
setMessages([]);
setDocuments([]);
setPanel(null);
refreshSessions();
}
const sid = uuidv4();
try {
await api.createSession({ title: "New Chat", model });
} catch {}
setSessionId(sid);
setMessages([]);
setDocuments([]);
setPanel(null);
refreshSessions();
}

async function loadSession(sid) {
setSessionId(sid);
Expand All @@ -126,7 +131,11 @@ export default function App() {

async function handleDeleteSession(sid) {
await api.deleteSession(sid);
if (sid === sessionId) { setSessionId(uuidv4()); setMessages([]); setDocuments([]); }
if (sid === sessionId) {
setSessionId(uuidv4());
setMessages([]);
setDocuments([]);
}
refreshSessions();
}

Expand Down Expand Up @@ -154,7 +163,7 @@ export default function App() {
onNewChat={newChat}
onLoadSession={loadSession}
onDeleteSession={handleDeleteSession}
onClearAllSessions={handleClearAllSessions}
refreshSessions={refreshSessions} // ✅ ADD THIS LINE
model={model}
models={models}
onModelChange={setModel}
Expand Down Expand Up @@ -208,4 +217,4 @@ export default function App() {
</div>
</div>
);
}
}
Loading
Loading