From aa60239bbe81a84a1ac0e40a6a98a6c4994a25f0 Mon Sep 17 00:00:00 2001 From: Unclebigbay Date: Wed, 7 Jan 2026 19:45:14 +0100 Subject: [PATCH] feat: add manual chat title rename functionality --- app/(chat)/actions.ts | 11 ++++ components/sidebar-history-item.tsx | 14 ++++ components/sidebar-history.tsx | 99 +++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+) diff --git a/app/(chat)/actions.ts b/app/(chat)/actions.ts index 19f73c1acb..b6cb7ddbff 100644 --- a/app/(chat)/actions.ts +++ b/app/(chat)/actions.ts @@ -8,6 +8,7 @@ import { getTitleModel } from "@/lib/ai/providers"; import { deleteMessagesByChatIdAfterTimestamp, getMessageById, + updateChatTitleById, updateChatVisibilityById, } from "@/lib/db/queries"; import { getTextFromMessage } from "@/lib/utils"; @@ -49,3 +50,13 @@ export async function updateChatVisibility({ }) { await updateChatVisibilityById({ chatId, visibility }); } + +export async function updateChatTitle({ + chatId, + title, +}: { + chatId: string; + title: string; +}) { + await updateChatTitleById({ chatId, title }); +} diff --git a/components/sidebar-history-item.tsx b/components/sidebar-history-item.tsx index 7a20c61a20..b02a6c35b6 100644 --- a/components/sidebar-history-item.tsx +++ b/components/sidebar-history-item.tsx @@ -7,6 +7,7 @@ import { GlobeIcon, LockIcon, MoreHorizontalIcon, + PencilEditIcon, ShareIcon, TrashIcon, } from "./icons"; @@ -30,11 +31,13 @@ const PureChatItem = ({ chat, isActive, onDelete, + onRename, setOpenMobile, }: { chat: Chat; isActive: boolean; onDelete: (chatId: string) => void; + onRename: (chatId: string, currentTitle: string) => void; setOpenMobile: (open: boolean) => void; }) => { const { visibilityType, setVisibilityType } = useChatVisibility({ @@ -62,6 +65,14 @@ const PureChatItem = ({ + onRename(chat.id, chat.title)} + > + + Rename + + @@ -116,5 +127,8 @@ export const ChatItem = memo(PureChatItem, (prevProps, nextProps) => { if (prevProps.isActive !== nextProps.isActive) { return false; } + if (prevProps.chat.title !== nextProps.chat.title) { + return false; + } return true; }); diff --git a/components/sidebar-history.tsx b/components/sidebar-history.tsx index 9ac1ab5e03..14234215e9 100644 --- a/components/sidebar-history.tsx +++ b/components/sidebar-history.tsx @@ -7,6 +7,7 @@ import type { User } from "next-auth"; import { useState } from "react"; import { toast } from "sonner"; import useSWRInfinite from "swr/infinite"; +import { updateChatTitle } from "@/app/(chat)/actions"; import { AlertDialog, AlertDialogAction, @@ -17,6 +18,16 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; import { SidebarGroup, SidebarGroupContent, @@ -116,6 +127,10 @@ export function SidebarHistory({ user }: { user: User | undefined }) { const [deleteId, setDeleteId] = useState(null); const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [renameId, setRenameId] = useState(null); + const [showRenameDialog, setShowRenameDialog] = useState(false); + const [newTitle, setNewTitle] = useState(""); + const hasReachedEnd = paginatedChatHistories ? paginatedChatHistories.some((page) => page.hasMore === false) : false; @@ -159,6 +174,32 @@ export function SidebarHistory({ user }: { user: User | undefined }) { }); }; + const handleRename = () => { + if (!renameId || !newTitle.trim()) { + setShowRenameDialog(false); + return; + } + + const trimmedTitle = newTitle.trim(); + setShowRenameDialog(false); + + mutate( + (chatHistories) => { + if (chatHistories) { + return chatHistories.map((chatHistory) => ({ + ...chatHistory, + chats: chatHistory.chats.map((chat) => + chat.id === renameId ? { ...chat, title: trimmedTitle } : chat + ), + })); + } + }, + { revalidate: false } + ); + + updateChatTitle({ chatId: renameId, title: trimmedTitle }); + }; + if (!user) { return ( @@ -241,6 +282,11 @@ export function SidebarHistory({ user }: { user: User | undefined }) { setDeleteId(chatId); setShowDeleteDialog(true); }} + onRename={(chatId, currentTitle) => { + setRenameId(chatId); + setNewTitle(currentTitle); + setShowRenameDialog(true); + }} setOpenMobile={setOpenMobile} /> ))} @@ -261,6 +307,11 @@ export function SidebarHistory({ user }: { user: User | undefined }) { setDeleteId(chatId); setShowDeleteDialog(true); }} + onRename={(chatId, currentTitle) => { + setRenameId(chatId); + setNewTitle(currentTitle); + setShowRenameDialog(true); + }} setOpenMobile={setOpenMobile} /> ))} @@ -281,6 +332,11 @@ export function SidebarHistory({ user }: { user: User | undefined }) { setDeleteId(chatId); setShowDeleteDialog(true); }} + onRename={(chatId, currentTitle) => { + setRenameId(chatId); + setNewTitle(currentTitle); + setShowRenameDialog(true); + }} setOpenMobile={setOpenMobile} /> ))} @@ -301,6 +357,11 @@ export function SidebarHistory({ user }: { user: User | undefined }) { setDeleteId(chatId); setShowDeleteDialog(true); }} + onRename={(chatId, currentTitle) => { + setRenameId(chatId); + setNewTitle(currentTitle); + setShowRenameDialog(true); + }} setOpenMobile={setOpenMobile} /> ))} @@ -321,6 +382,11 @@ export function SidebarHistory({ user }: { user: User | undefined }) { setDeleteId(chatId); setShowDeleteDialog(true); }} + onRename={(chatId, currentTitle) => { + setRenameId(chatId); + setNewTitle(currentTitle); + setShowRenameDialog(true); + }} setOpenMobile={setOpenMobile} /> ))} @@ -371,6 +437,39 @@ export function SidebarHistory({ user }: { user: User | undefined }) { + + + + + Rename chat + + Enter a new name for this chat. + + + setNewTitle(e.target.value)} + placeholder="Chat title" + onKeyDown={(e) => { + if (e.key === "Enter") { + handleRename(); + } + }} + /> + + + + + + ); }