From a70b9ea2ce983036307a34e53d368aae032b0b0d Mon Sep 17 00:00:00 2001 From: JamesTheGiblet Date: Mon, 29 Dec 2025 13:43:37 +0000 Subject: [PATCH] feat: add session title management and rename functionality in the BuddAI interface --- buddai_v3.1.py | 46 ++++++++++++++++++++++++-- data/conversations.db | Bin 55033856 -> 55033856 bytes frontend/index.html | 74 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 112 insertions(+), 8 deletions(-) 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/data/conversations.db b/data/conversations.db index 4a9a3a6d72bd35f84d2e4989779cbf9a42fa6d87..c3fb93a728cc3ed81e4e930c11d16afcdd5e9917 100644 GIT binary patch delta 4160 zcmZA1Wl&Y&8irwd14;>sfGDV_2m*T(0v4#)Ep~TeVi$(c#9RFjG`SWK^1~$9o$~Ux$QLgHNo5L&ASg+o{uPTf4bh2AKcfQNAh3#YQI= zGglVMQduc$WupwrR@o_gl}%+=4$4tEDQD%Pawu2jrgExW%3bAF9?DaBsXWSCbHs-xSda2$jQT0)MRX^2V4NwEs zAT?MGQA5=*m86o@aFwD)sF7-v8m-2tv1*(euO_I8YLc3)rl_fEno3pE)eJRL%~G?~ z95q+XQ}fjVwNNcmi`5dfR4r4>)e5yztx~Ji8nsrfQ|r|RwNY(So7EPzRi&wIYP;H@ zcB)-!w@O!g)Lyku?N=GWn(8&Z+b2g1V?Ksmtn$ zx~i_J>*|KOscxy;>W;dr?y39gfqJMi)g$#-JyBWesd}cKs~75}dZk{gH|ni=r{1d% z>ZAIkKC3V4tNNzCs~_s8`lWuWKkDyz{ri&@Sb!y1fi>8G0c^nz>>(Ru2M2HjCvXNA z$N{e4200-YxI=F608j9OJm3v^As_gFFXV>;P!Rl}5EOMThAL1MszG(A0X3l()P_1x z7viBF)Q1Mp5E?;aXaY^488n9$&=OifYiI*)p&hh`4$u)gL1*X!T_FLwL3ii@J)sx$ zhD7KCeW4%phXF7U2Ekw$0z+XKBtbF^hZGnABViPbhA}V}#=&@)025&nOok~i6{bNd zOotgT6K26|m;-ZR9?XXYun-o(Vpsx8VHqrk6|fRk!D?6oYhfL%hYhe1Ho<1t0$U*s zw!wDT0XtzA?1psM1AAc~?1v0E00-d^9EKxs6pq1hH~}Z&6r6@La2C$NdAI-<;SyYi zD{vLA!F9L+H{llChC6THUH0$K1Bp22f?0WaYdyoNXM7T&>o_y8Z_ z6MTj*@D;wnclZH6;TQadKk(OF*Pn$3bFctQumWqa0Rz~A9oR!Q$PNzR2u|P(E|3FU z!3}akE^vq3-~pcC1$n?5@x0DpZ5&Py=d0EvOB3pf1Ei zJ*W>2pdmDZ#?SM4;`Q*bb`*%1-e24bc62D1A0O)=naX` z2l_%k=nn&6APj=RFa(CeFi3)A7!D~g0!G3p7!6}!ER2KkFaajQB$y0SU@A<5RG1Dk zU?$9h*)Rv@!aSG{3t%BEg2k`|mclYv4l7_Ktb*0B2G+tlSPvUuBW!}rum!e48f=5@ zumg6&F4zs}um|?SKG+W#Z~zX%Avg?2;3yn}<8T5_!YMcnXW%THgY$3!F2W_a3|HVP zT!ZUy18%}CxD9vUF5H9r@Bkh{COm@2@C35pDLjMc@B&`KD|iiW;4Qp^_wWHe!YB9) zU*IczgYWPIe!?&K4S(RTg|0tK4d!40mS6?eU;_rQ1v{{ZY>*usz!99l8C)O-xPlwx zgk0bbxxoWG!3*+$H{^wU-~+yp9|}N0@Pk577>a;D6omi?gntVLF$F^i7{LUg5C-88 m0mUH_q97VdKuIVCrJ)SOKrEDna!?-Ppn_$Fu0P9R)BXb)h|W3y delta 4066 zcmYM!RZvxJ7=~eB0~=6UkubI*2#P3S7ocJ*c8lF$cY)YN+1=e3nAqK&*xil&Kes3U znz^reXVyAc@4@%Q#VzuTQ%PRV>1Jl3!_EGGtdj#m9MjDL$NAekW|`UAWSTkL-V!_5 zILj1fot2hp^V`ZYzMHu*?Vf{=xxr-Xnrd_k&v1SpWO3==C*RXtJw8|)O!o1qMpO7s zm;2GZtTIe`eb|^BWlW7J>X?vJbbL}#b7i3n%2HV=Yh_e6%2wH_Y%065R}RWiIVor5 zqFhxDl~cK?T*_T}C{N|3a;rQluga%PD!(eAyj4NvqkL5%<){2rVO2y0DE>K66;s7k zkP22IDpZwFB~>X^T9r{{RXJ5&RZtaGB^9Q^RfMXns;H_eQdLvcRSi{B)l#)p9aUG= zQ&FnEYM>gbMyj!DqME8^s<~>RTB=s6wQ8fHRa@0gwO1WfN7YGnR$WwA)lGF*JycH> zqheJr)m!yZeN{izUky+L)gU!k4N*hYFg09_P$ShS6{q6WXqBMGsIh9C8m}g(iE5IX ztfr``YMM$^Nh(>Ts8lsw%}_JdEHzurQFGNiHD4`I3)LdESS?XY)iSkQtxzk~Dz#dz zQESyYwO(yd8`UPYS#42URhrtSwyPa#r%G45)NZv$?N$5KewCpPsDtW|I;@VUqw1JC zu1=_v>XbUI&Zx8MoI0;AsEg{7x~#6KtLmD%u5PHC>Xy2#?x?#eQ{7Yd)dTfVJyMU= z6ZKR*Q_s~4^-{f3uhkp%R=rd2)d%%aeNvy*7xh(rQ(5Y}`k{WRU+TB|qyA3T*-unp z0S2%HE3gJ5*nlnAK{m(^_TT`H-~`U#0q0HDnLc31Yr;k5l|Vb zKvjr@YET_&KuxFxwV@8wg?bPL^`QYYghtR9nm|)%2F;-bw1igB8rncKw1sxi9y&lr z=medi3v`8U&>ea}Pl$n7=mou@5A=n8&>sfCKo|srVF(O`VK5veSg>|qVHo!*M1e;+CY=tz~2HRl=?1XgK1-oGn?1g=>A2Q$o9E3w~7>>YE zI0nbz1e}CZa2n3QSvUvh;R0NQOK=&kz*V>g*Wm`-gj;YM?!aBhgnMuw9>7C*1drhf zJcVcQ9A3ancm=QF4ZMYS@E$(ENB9Jv;R}3)Z;%Dw;RpPLU+^3Lz+ZD+e-;|d!2%3m z307bYMz8@}u!C%n9qhpY9Ki{k!3A6)2jm1d$OZ1;0iNImxgihag?wOw{7?YAp&8Lm%i1{h&V#fPpXw2Ez~-3d3MHjDV3a3gRFhMneLOfw3?S z#=``d2$NtkOo6E|4H6*+QtbmoU z3Rc4!SPSc5J#2uDun9K97T5}Duno4u4%i9lunTs>9@q=}U_WHQ0XPVU;4mD4qi_t4 z!wEPEr{FZ4fwOQ9&cg+`2$$e8T!E`_4X(otxCyu5Hr#=`kO}wTK0JVj@CY8m6L<>G z;5od2m+%T+!y9-D@8CUrfRFGAKEoII3f~|LzQYgr3BTYs{DHp~y8a9r%)tT-UF<-~pcC1-T&);eD) 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)}>🗑️ +
+ + )} ))}