diff --git a/buddai_v3.1.py b/buddai_v3.1.py index e782f45..f8e4f2c 100644 --- a/buddai_v3.1.py +++ b/buddai_v3.1.py @@ -275,10 +275,16 @@ class BuddAI: CREATE TABLE IF NOT EXISTS sessions ( session_id TEXT PRIMARY KEY, started_at TIMESTAMP, - ended_at TIMESTAMP + ended_at TIMESTAMP, + title TEXT ) """) + try: + cursor.execute("ALTER TABLE sessions ADD COLUMN title TEXT") + except sqlite3.OperationalError: + pass + cursor.execute(""" CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -799,10 +805,27 @@ float applyForge(float current, float target, float k) {{ return target + (curre """Retrieve recent sessions from DB""" conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() - cursor.execute("SELECT session_id, started_at FROM sessions ORDER BY started_at DESC LIMIT ?", (limit,)) + cursor.execute("SELECT session_id, started_at, title FROM sessions ORDER BY started_at DESC LIMIT ?", (limit,)) rows = cursor.fetchall() conn.close() - return [{"id": r[0], "date": r[1]} for r in rows] + return [{"id": r[0], "date": r[1], "title": r[2]} for r in rows] + + def rename_session(self, session_id, new_title): + """Rename a session""" + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + cursor.execute("UPDATE sessions SET title = ? WHERE session_id = ?", (new_title, session_id)) + conn.commit() + conn.close() + + def delete_session(self, session_id): + """Delete a session and its messages""" + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + cursor.execute("DELETE FROM sessions WHERE session_id = ?", (session_id,)) + cursor.execute("DELETE FROM messages WHERE session_id = ?", (session_id,)) + conn.commit() + conn.close() def load_session(self, session_id): """Load a specific session context""" @@ -900,6 +923,13 @@ if SERVER_AVAILABLE: class SessionLoadRequest(BaseModel): session_id: str + class SessionRenameRequest(BaseModel): + session_id: str + title: str + + class SessionDeleteRequest(BaseModel): + session_id: str + # Initialize server instance server_buddai = BuddAI(server_mode=True) @@ -953,6 +983,16 @@ if SERVER_AVAILABLE: history = server_buddai.load_session(req.session_id) return {"history": history, "session_id": req.session_id} + @app.post("/api/session/rename") + async def rename_session_endpoint(req: SessionRenameRequest): + server_buddai.rename_session(req.session_id, req.title) + return {"status": "success"} + + @app.post("/api/session/delete") + async def delete_session_endpoint(req: SessionDeleteRequest): + server_buddai.delete_session(req.session_id) + return {"status": "success"} + @app.post("/api/session/new") async def new_session_endpoint(): new_id = server_buddai.start_new_session() diff --git a/frontend/index.html b/frontend/index.html index 90bc925..55d191e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -42,9 +42,16 @@ --code-border: #d1d5da; --code-text: #24292e; } - .sidebar-left { width: 260px; background: var(--header-bg); border-right: 1px solid var(--border-color); display: flex; flex-direction: column; flex-shrink: 0; } + .sidebar-left { width: 260px; background: var(--header-bg); border-right: 1px solid var(--border-color); display: flex; flex-direction: column; flex-shrink: 0; transition: width 0.3s, opacity 0.3s; overflow: hidden; } + .sidebar-left.collapsed { width: 0; opacity: 0; border: none; } .session-list { flex: 1; overflow-y: auto; } - .session-item { padding: 12px 15px; cursor: pointer; border-bottom: 1px solid var(--border-color); font-size: 0.85em; color: var(--text-color); transition: background 0.2s; } + .session-item { padding: 12px 15px; cursor: pointer; border-bottom: 1px solid var(--border-color); font-size: 0.85em; color: var(--text-color); transition: background 0.2s; display: flex; justify-content: space-between; align-items: center; } + .session-info { flex: 1; overflow: hidden; } + .rename-input { width: 100%; background: var(--input-bg); color: var(--text-color); border: 1px solid var(--btn-bg); padding: 4px; border-radius: 3px; font-family: inherit; } + .edit-icon, .delete-icon { opacity: 0; cursor: pointer; padding: 4px; font-size: 1.1em; margin-left: 2px; } + .session-item:hover .edit-icon, .session-item:hover .delete-icon { opacity: 0.5; } + .edit-icon:hover, .delete-icon:hover { opacity: 1; } + .delete-icon:hover { color: #ff4444; } .session-item:hover { background: var(--bg-color); } .session-item.active { background: var(--btn-bg); color: white; border-color: var(--btn-bg); } .new-chat-btn { margin: 15px; padding: 10px; background: var(--btn-bg); color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; } @@ -187,6 +194,9 @@ const [history, setHistory] = useState([]); const [sessions, setSessions] = useState([]); const [currentSessionId, setCurrentSessionId] = useState(null); + const [showSidebar, setShowSidebar] = useState(true); + const [editingSession, setEditingSession] = useState(null); + const [renameText, setRenameText] = useState(""); const [input, setInput] = useState(""); const [loading, setLoading] = useState(false); const [status, setStatus] = useState("connecting"); @@ -248,6 +258,35 @@ return () => clearInterval(timer); }, []); + const handleRename = async (e) => { + if (e.key === 'Enter') { + await fetch("/api/session/rename", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ session_id: editingSession, title: renameText }) + }); + setEditingSession(null); + fetchSessions(); + } + }; + + const handleDeleteSession = async (e, sessionId) => { + e.stopPropagation(); + if (!window.confirm("Delete this chat?")) return; + + await fetch("/api/session/delete", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ session_id: sessionId }) + }); + + if (sessionId === currentSessionId) { + setHistory([]); + setCurrentSessionId(null); + } + fetchSessions(); + }; + const loadSession = async (sessionId) => { setLoading(true); try { @@ -358,6 +397,7 @@ onChange={handleFileUpload} /> + setRenameText(e.target.value)} + onKeyDown={handleRename} + onBlur={() => setEditingSession(null)} + autoFocus + onClick={e => e.stopPropagation()} + /> + ) : ( + <> +
+ {s.title || (new Date(s.date).toLocaleDateString() + ' ' + new Date(s.date).toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}))} + {!s.title && {s.id}} +
+
+ { + e.stopPropagation(); + setEditingSession(s.id); + setRenameText(s.title || ""); + }}>✏️ + handleDeleteSession(e, s.id)}>🗑️ +
+ + )} ))}