@@ -173,6 +173,106 @@ func (m Main) HandleChats(w http.ResponseWriter, r *http.Request) {
173173 m .renderExistingChatResponse (w , messages , addedMessageIDs , am , aiMsgID )
174174}
175175
176+ // HandleRefreshTitle handles requests to regenerate a chat title. It accepts POST requests with a chat_id
177+ // parameter, retrieves the first user message from the chat history, and uses the title generator to create
178+ // a new title. The handler updates the chat title in the database and returns the new title to be displayed.
179+ //
180+ // The function expects a "chat_id" form field identifying which chat's title should be refreshed.
181+ // After updating the database, it asynchronously notifies all connected clients through Server-Sent Events (SSE)
182+ // to maintain UI consistency across sessions while immediately returning the new title text to the requesting client.
183+ //
184+ // The function returns appropriate HTTP error responses for invalid methods, missing required fields,
185+ // or when no messages are found for title generation. On success, it returns just the title text to be
186+ // inserted into the targeted span element via HTMX.
187+ func (m Main ) HandleRefreshTitle (w http.ResponseWriter , r * http.Request ) {
188+ if r .Method != http .MethodPost {
189+ m .logger .Error ("Method not allowed" , slog .String ("method" , r .Method ))
190+ http .Error (w , "Method not allowed" , http .StatusMethodNotAllowed )
191+ return
192+ }
193+
194+ chatID := r .FormValue ("chat_id" )
195+ if chatID == "" {
196+ m .logger .Error ("Chat ID is required" )
197+ http .Error (w , "Chat ID is required" , http .StatusBadRequest )
198+ return
199+ }
200+
201+ // Get messages to find first user message
202+ messages , err := m .store .Messages (r .Context (), chatID )
203+ if err != nil {
204+ m .logger .Error ("Failed to get messages" ,
205+ slog .String ("chatID" , chatID ),
206+ slog .String (errLoggerKey , err .Error ()))
207+ http .Error (w , err .Error (), http .StatusInternalServerError )
208+ return
209+ }
210+
211+ if len (messages ) == 0 {
212+ m .logger .Error ("No messages found for chat" , slog .String ("chatID" , chatID ))
213+ http .Error (w , "No messages found for chat" , http .StatusNotFound )
214+ return
215+ }
216+
217+ // Find first user message for title generation
218+ var firstUserMessage string
219+ for _ , msg := range messages {
220+ if msg .Role == models .RoleUser && len (msg .Contents ) > 0 && msg .Contents [0 ].Type == models .ContentTypeText {
221+ firstUserMessage = msg .Contents [0 ].Text
222+ break
223+ }
224+ }
225+
226+ if firstUserMessage == "" {
227+ m .logger .Error ("No user message found for title generation" , slog .String ("chatID" , chatID ))
228+ http .Error (w , "No user message found for title generation" , http .StatusInternalServerError )
229+ return
230+ }
231+
232+ // Generate and update title
233+ title , err := m .titleGenerator .GenerateTitle (r .Context (), firstUserMessage )
234+ if err != nil {
235+ m .logger .Error ("Error generating chat title" ,
236+ slog .String ("message" , firstUserMessage ),
237+ slog .String (errLoggerKey , err .Error ()))
238+ http .Error (w , "Failed to generate title" , http .StatusInternalServerError )
239+ return
240+ }
241+
242+ updatedChat := models.Chat {
243+ ID : chatID ,
244+ Title : title ,
245+ }
246+ if err := m .store .UpdateChat (r .Context (), updatedChat ); err != nil {
247+ m .logger .Error ("Failed to update chat title" ,
248+ slog .String (errLoggerKey , err .Error ()))
249+ http .Error (w , "Failed to update chat title" , http .StatusInternalServerError )
250+ return
251+ }
252+
253+ // Update all clients via SSE asynchronously
254+ go func () {
255+ divs , err := m .chatDivs (chatID )
256+ if err != nil {
257+ m .logger .Error ("Failed to generate chat divs" ,
258+ slog .String (errLoggerKey , err .Error ()))
259+ return
260+ }
261+
262+ msg := sse.Message {
263+ Type : chatsSSEType ,
264+ }
265+ msg .AppendData (divs )
266+ if err := m .sseSrv .Publish (& msg , chatsSSETopic ); err != nil {
267+ m .logger .Error ("Failed to publish chats" ,
268+ slog .String (errLoggerKey , err .Error ()))
269+ }
270+ }()
271+
272+ // Return just the title text for HTMX to insert into the span
273+ fmt .Fprintf (w , "%s" , title )
274+ }
275+
176276// processPromptInput handles prompt-based inputs, extracting arguments and retrieving
177277// prompt messages from the MCP client.
178278func (m Main ) processPromptInput (ctx context.Context , promptName , promptArgs string ) ([]models.Message , string , error ) {
@@ -334,106 +434,6 @@ func (m Main) renderExistingChatResponse(w http.ResponseWriter, messages []model
334434 }
335435}
336436
337- // HandleRefreshTitle handles requests to regenerate a chat title. It accepts POST requests with a chat_id
338- // parameter, retrieves the first user message from the chat history, and uses the title generator to create
339- // a new title. The handler updates the chat title in the database and returns the new title to be displayed.
340- //
341- // The function expects a "chat_id" form field identifying which chat's title should be refreshed.
342- // After updating the database, it asynchronously notifies all connected clients through Server-Sent Events (SSE)
343- // to maintain UI consistency across sessions while immediately returning the new title text to the requesting client.
344- //
345- // The function returns appropriate HTTP error responses for invalid methods, missing required fields,
346- // or when no messages are found for title generation. On success, it returns just the title text to be
347- // inserted into the targeted span element via HTMX.
348- func (m Main ) HandleRefreshTitle (w http.ResponseWriter , r * http.Request ) {
349- if r .Method != http .MethodPost {
350- m .logger .Error ("Method not allowed" , slog .String ("method" , r .Method ))
351- http .Error (w , "Method not allowed" , http .StatusMethodNotAllowed )
352- return
353- }
354-
355- chatID := r .FormValue ("chat_id" )
356- if chatID == "" {
357- m .logger .Error ("Chat ID is required" )
358- http .Error (w , "Chat ID is required" , http .StatusBadRequest )
359- return
360- }
361-
362- // Get messages to find first user message
363- messages , err := m .store .Messages (r .Context (), chatID )
364- if err != nil {
365- m .logger .Error ("Failed to get messages" ,
366- slog .String ("chatID" , chatID ),
367- slog .String (errLoggerKey , err .Error ()))
368- http .Error (w , err .Error (), http .StatusInternalServerError )
369- return
370- }
371-
372- if len (messages ) == 0 {
373- m .logger .Error ("No messages found for chat" , slog .String ("chatID" , chatID ))
374- http .Error (w , "No messages found for chat" , http .StatusNotFound )
375- return
376- }
377-
378- // Find first user message for title generation
379- var firstUserMessage string
380- for _ , msg := range messages {
381- if msg .Role == models .RoleUser && len (msg .Contents ) > 0 && msg .Contents [0 ].Type == models .ContentTypeText {
382- firstUserMessage = msg .Contents [0 ].Text
383- break
384- }
385- }
386-
387- if firstUserMessage == "" {
388- m .logger .Error ("No user message found for title generation" , slog .String ("chatID" , chatID ))
389- http .Error (w , "No user message found for title generation" , http .StatusInternalServerError )
390- return
391- }
392-
393- // Generate and update title
394- title , err := m .titleGenerator .GenerateTitle (r .Context (), firstUserMessage )
395- if err != nil {
396- m .logger .Error ("Error generating chat title" ,
397- slog .String ("message" , firstUserMessage ),
398- slog .String (errLoggerKey , err .Error ()))
399- http .Error (w , "Failed to generate title" , http .StatusInternalServerError )
400- return
401- }
402-
403- updatedChat := models.Chat {
404- ID : chatID ,
405- Title : title ,
406- }
407- if err := m .store .UpdateChat (r .Context (), updatedChat ); err != nil {
408- m .logger .Error ("Failed to update chat title" ,
409- slog .String (errLoggerKey , err .Error ()))
410- http .Error (w , "Failed to update chat title" , http .StatusInternalServerError )
411- return
412- }
413-
414- // Update all clients via SSE asynchronously
415- go func () {
416- divs , err := m .chatDivs (chatID )
417- if err != nil {
418- m .logger .Error ("Failed to generate chat divs" ,
419- slog .String (errLoggerKey , err .Error ()))
420- return
421- }
422-
423- msg := sse.Message {
424- Type : chatsSSEType ,
425- }
426- msg .AppendData (divs )
427- if err := m .sseSrv .Publish (& msg , chatsSSETopic ); err != nil {
428- m .logger .Error ("Failed to publish chats" ,
429- slog .String (errLoggerKey , err .Error ()))
430- }
431- }()
432-
433- // Return just the title text for HTMX to insert into the span
434- fmt .Fprintf (w , "%s" , title )
435- }
436-
437437func (m Main ) newChat () (string , error ) {
438438 newChat := models.Chat {
439439 ID : uuid .New ().String (),
0 commit comments