From a9304d5a4f5be5255b9a7bb78f108d8b4d996e9e Mon Sep 17 00:00:00 2001 From: Autumn-27 <53844290+Autumn-27@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:08:18 -0400 Subject: [PATCH] =?UTF-8?q?feat(chat):=20=E4=BC=9A=E8=AF=9D=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E5=A4=9A=E9=80=89=E6=94=B9=E4=B8=BA=E3=80=8C=E5=A4=9A?= =?UTF-8?q?=E9=80=89=E3=80=8D=E6=A8=A1=E5=BC=8F=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 默认列表不再常驻每行勾选框(观感更干净),表头改为「共 N 个 + 多选」按钮。 点「多选」进入选择模式:显示每行勾选框、全选、批量删除,「完成」退出并清空 选择;批量删除全部成功后自动退出(有失败则留在选择模式便于重试)。单条的 重命名/置顶/删除仍走每行的 ⋯ 菜单。 --- web/src/app/(main)/chat/page.tsx | 87 +++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/web/src/app/(main)/chat/page.tsx b/web/src/app/(main)/chat/page.tsx index 04a4616..8e85fb3 100644 --- a/web/src/app/(main)/chat/page.tsx +++ b/web/src/app/(main)/chat/page.tsx @@ -6,6 +6,7 @@ import { ArrowUpIcon, Bot, ChevronDownIcon, + ListChecksIcon, Loader2Icon, MoreHorizontalIcon, PaperclipIcon, @@ -762,6 +763,7 @@ function ConversationItem({ onCancelRename, onTogglePinned, onDelete, + selectionMode, selectedForDelete, onSelectedForDeleteChange, }: { @@ -777,6 +779,7 @@ function ConversationItem({ onCancelRename: () => void; onTogglePinned: () => void; onDelete: () => void; + selectionMode: boolean; selectedForDelete: boolean; onSelectedForDeleteChange: (checked: boolean) => void; }) { @@ -799,12 +802,14 @@ function ConversationItem({ active ? "bg-accent text-accent-foreground" : "hover:bg-accent/50", )} > - onSelectedForDeleteChange(checked === true)} - aria-label={`选择对话「${conv.title || "新对话"}」`} - className="ml-1 shrink-0" - /> + {selectionMode && ( + onSelectedForDeleteChange(checked === true)} + aria-label={`选择对话「${conv.title || "新对话"}」`} + className="ml-1 shrink-0" + /> + )} {renaming ? ( (null); const [renameText, setRenameText] = React.useState(""); const [selectedConversationIds, setSelectedConversationIds] = React.useState>(() => new Set()); + // selectionMode gates the multi-select UI: off by default (clean list, no + // checkboxes); the header "多选" button turns it on, "完成" turns it off and + // clears the selection. + const [selectionMode, setSelectionMode] = React.useState(false); const [bulkDeleteOpen, setBulkDeleteOpen] = React.useState(false); const [bulkDeleting, setBulkDeleting] = React.useState(false); // Pending text + uploaded attachments handed off from a draft that created a @@ -991,6 +1000,11 @@ export default function ChatPage() { setSelectedConversationIds(checked ? new Set(convs.map((conversation) => conversation.id)) : new Set()); } + function exitSelectionMode() { + setSelectionMode(false); + setSelectedConversationIds(new Set()); + } + async function del(id: number) { try { await api.deleteConversation(id); @@ -1036,6 +1050,9 @@ export default function ChatPage() { toast.error(`${failed.length} 个对话删除失败:${details}${failed.length > 3 ? " 等" : ""}`); } setBulkDeleteOpen(false); + // Fully successful → return to the clean list; keep selection mode on if + // some failed so the user can retry the remaining ones. + if (failed.length === 0) setSelectionMode(false); reloadConvs(); } catch (error) { toast.error(`批量删除失败:${(error as Error).message}`); @@ -1084,29 +1101,48 @@ export default function ChatPage() { - {convs.length > 0 && ( -
- toggleAllConversations(checked === true)} - aria-label="选择全部对话" - /> - - {selectedConversationCount > 0 ? `已选 ${selectedConversationCount} 个` : `共 ${convs.length} 个`} - - {selectedConversationCount > 0 && ( + {convs.length > 0 && + (selectionMode ? ( +
+ toggleAllConversations(checked === true)} + aria-label="选择全部对话" + /> + + {selectedConversationCount > 0 ? `已选 ${selectedConversationCount} 个` : `共 ${convs.length} 个`} + + {selectedConversationCount > 0 && ( + + )} + +
+ ) : ( +
+ + 共 {convs.length} 个 + - )} -
- )} +
+ ))}
@@ -1126,6 +1162,7 @@ export default function ChatPage() { onCancelRename={() => setRenamingId(null)} onTogglePinned={() => void togglePinned(c)} onDelete={() => del(c.id)} + selectionMode={selectionMode} selectedForDelete={selectedConversationIds.has(c.id)} onSelectedForDeleteChange={(checked) => toggleConversationSelected(c.id, checked)} />