From 91892ea6194d70176803bc97af391e86db58f669 Mon Sep 17 00:00:00 2001 From: Teo Date: Sat, 14 Jun 2025 21:52:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(FilesPage):=20=E6=B7=BB=E5=8A=A0=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E5=88=A0=E9=99=A4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/src/i18n/locales/en-us.json | 7 +- src/renderer/src/i18n/locales/ja-jp.json | 7 +- src/renderer/src/i18n/locales/ru-ru.json | 7 +- src/renderer/src/i18n/locales/zh-cn.json | 7 +- src/renderer/src/i18n/locales/zh-tw.json | 6 +- src/renderer/src/pages/files/FilesPage.tsx | 151 +++++++++++++++------ 6 files changed, 135 insertions(+), 50 deletions(-) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 71c0a6339f..fa5586db37 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -470,7 +470,7 @@ "count": "files", "created_at": "Created At", "delete": "Delete", - "delete.content": "Deleting a file will delete its reference from all messages. Are you sure you want to delete this file?", + "delete.content": "Deleting a file will delete its reference from all messages. Are you sure you want to delete {{count}} files?", "delete.paintings.warning": "Image contains this file, deletion is not possible", "delete.title": "Delete File", "document": "Document", @@ -482,7 +482,9 @@ "size": "Size", "text": "Text", "title": "Files", - "type": "Type" + "type": "Type", + "batch_operation": "Batch Operation", + "batch_delete": "Batch Delete" }, "gpustack": { "keep_alive_time.description": "The time in minutes to keep the connection alive, default is 5 minutes.", @@ -1730,6 +1732,7 @@ "theme.light": "Light", "theme.title": "Theme", "theme.color_primary": "Primary Color", + "theme.window.style.opaque": "Opaque Window", "theme.window.style.title": "Window Style", "theme.window.style.transparent": "Transparent Window", "title": "Settings", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 65b84150c4..9ae56ed03f 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -470,7 +470,7 @@ "count": "ファイル", "created_at": "作成日", "delete": "削除", - "delete.content": "ファイルを削除すると、ファイルがすべてのメッセージで参照されることを削除します。このファイルを削除してもよろしいですか?", + "delete.content": "ファイルを削除すると、ファイルがすべてのメッセージで参照されることを削除します。この{{count}}ファイルを削除してもよろしいですか?", "delete.paintings.warning": "画像に含まれているため、削除できません", "delete.title": "ファイルを削除", "document": "ドキュメント", @@ -482,7 +482,9 @@ "size": "サイズ", "text": "テキスト", "title": "ファイル", - "type": "タイプ" + "type": "タイプ", + "batch_operation": "一括操作", + "batch_delete": "一括削除" }, "gpustack": { "keep_alive_time.description": "モデルがメモリに保持される時間(デフォルト:5分)", @@ -1718,6 +1720,7 @@ "theme.light": "ライト", "theme.title": "テーマ", "theme.color_primary": "テーマ色", + "theme.window.style.opaque": "不透明ウィンドウ", "theme.window.style.title": "ウィンドウスタイル", "theme.window.style.transparent": "透明ウィンドウ", "title": "設定", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index e54ae2cb1f..52289a23bd 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -470,7 +470,7 @@ "count": "файлов", "created_at": "Дата создания", "delete": "Удалить", - "delete.content": "Удаление файла удалит его из всех сообщений, вы уверены, что хотите удалить этот файл?", + "delete.content": "Удаление файла удалит его из всех сообщений, вы уверены, что хотите удалить этот {{count}} файл", "delete.paintings.warning": "В изображениях содержится этот файл, удаление невозможно", "delete.title": "Удалить файл", "document": "Документ", @@ -482,7 +482,9 @@ "size": "Размер", "text": "Текст", "title": "Файлы", - "type": "Тип" + "type": "Тип", + "batch_operation": "Пакетная операция", + "batch_delete": "Пакетное удаление" }, "gpustack": { "keep_alive_time.description": "Время в минутах, в течение которого модель остается активной, по умолчанию 5 минут.", @@ -1718,6 +1720,7 @@ "theme.light": "Светлая", "theme.title": "Тема", "theme.color_primary": "Цвет темы", + "theme.window.style.opaque": "Непрозрачное окно", "theme.window.style.title": "Стиль окна", "theme.window.style.transparent": "Прозрачное окно", "title": "Настройки", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 17a37b6af3..c484932b43 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -470,7 +470,7 @@ "count": "个文件", "created_at": "创建时间", "delete": "删除", - "delete.content": "删除文件会删除文件在所有消息中的引用,确定要删除此文件吗?", + "delete.content": "删除文件会删除文件在所有消息中的引用,确定要删除这{{count}}个文件吗?", "delete.paintings.warning": "绘图中包含该图片,暂时无法删除", "delete.title": "删除文件", "document": "文档", @@ -482,7 +482,9 @@ "size": "大小", "text": "文本", "title": "文件", - "type": "类型" + "type": "类型", + "batch_operation": "批量操作", + "batch_delete": "批量删除" }, "gpustack": { "keep_alive_time.description": "模型在内存中保持的时间(默认:5分钟)", @@ -1730,6 +1732,7 @@ "theme.light": "浅色", "theme.title": "主题", "theme.color_primary": "主题颜色", + "theme.window.style.opaque": "不透明窗口", "theme.window.style.title": "窗口样式", "theme.window.style.transparent": "透明窗口", "title": "设置", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 4e4265c3d3..43b327d811 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -470,7 +470,7 @@ "count": "個檔案", "created_at": "建立時間", "delete": "刪除", - "delete.content": "刪除檔案會刪除檔案在所有訊息中的引用,確定要刪除此檔案嗎?", + "delete.content": "刪除檔案會刪除檔案在所有訊息中的引用,確定要刪除這{{count}}個檔案嗎?", "delete.paintings.warning": "繪圖中包含該圖片,暫時無法刪除", "delete.title": "刪除檔案", "document": "文件", @@ -482,7 +482,9 @@ "size": "大小", "text": "文字", "title": "檔案", - "type": "類型" + "type": "類型", + "batch_operation": "批量操作", + "batch_delete": "批量刪除" }, "gpustack": { "keep_alive_time.description": "模型在記憶體中保持的時間(預設為 5 分鐘)", diff --git a/src/renderer/src/pages/files/FilesPage.tsx b/src/renderer/src/pages/files/FilesPage.tsx index fa4b220e42..319841b67a 100644 --- a/src/renderer/src/pages/files/FilesPage.tsx +++ b/src/renderer/src/pages/files/FilesPage.tsx @@ -15,11 +15,11 @@ import store from '@renderer/store' import { FileType, FileTypes } from '@renderer/types' import { Message } from '@renderer/types/newMessage' import { formatFileSize } from '@renderer/utils' -import { Button, Empty, Flex, Popconfirm } from 'antd' +import { Button, Checkbox, Dropdown, Empty, Flex, Popconfirm } from 'antd' import dayjs from 'dayjs' import { useLiveQuery } from 'dexie-react-hooks' import { File as FileIcon, FileImage, FileText, FileType as FileTypeIcon } from 'lucide-react' -import { FC, useState } from 'react' +import { FC, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -33,6 +33,11 @@ const FilesPage: FC = () => { const [fileType, setFileType] = useState('document') const [sortField, setSortField] = useState('created_at') const [sortOrder, setSortOrder] = useState('desc') + const [selectedFileIds, setSelectedFileIds] = useState([]) + + useEffect(() => { + setSelectedFileIds([]) + }, [fileType]) const tempFilesSort = (files: FileType[]) => { return files.sort((a, b) => { @@ -153,6 +158,8 @@ const FilesPage: FC = () => { }) Logger.log(`Deleted ${blockIdsToDelete.length} blocks and updated relevant topic messages for file ${fileId}.`) + + setSelectedFileIds((prev) => prev.filter((id) => id !== fileId)) } catch (error) { Logger.error(`Error updating topics or deleting blocks for file ${fileId}:`, error) window.modal.error({ content: t('files.delete.db_error'), centered: true }) // 提示数据库操作失败 @@ -170,6 +177,46 @@ const FilesPage: FC = () => { } } + const handleBatchDelete = async () => { + const selectedFiles = await Promise.all(selectedFileIds.map((id) => FileManager.getFile(id))) + const validFiles = selectedFiles.filter((file): file is FileType => file !== null && file !== undefined) + + const paintings = await store.getState().paintings.paintings + const paintingsFiles = paintings.flatMap((p) => p.files) + + const filesInPaintings = validFiles.filter((file) => paintingsFiles.some((p) => p.id === file.id)) + + if (filesInPaintings.length > 0) { + window.modal.warning({ + content: t('files.delete.paintings.warning', { count: filesInPaintings.length }), + centered: true + }) + return + } + + for (const fileId of selectedFileIds) { + await handleDelete(fileId) + } + + setSelectedFileIds([]) + } + + const handleSelectFile = (fileId: string, checked: boolean) => { + if (checked) { + setSelectedFileIds((prev) => [...prev, fileId]) + } else { + setSelectedFileIds((prev) => prev.filter((id) => id !== fileId)) + } + } + + const handleSelectAll = (checked: boolean) => { + if (checked) { + setSelectedFileIds(sortedFiles.map((file) => file.id)) + } else { + setSelectedFileIds([]) + } + } + const dataSource = sortedFiles?.map((file) => { return { key: file.id, @@ -186,13 +233,20 @@ const FilesPage: FC = () => { + ))} + + {fileType !== 'image' && ( + }> + {t('files.batch_delete')} ({selectedFileIds.length}) + + ) + } + ] + }} + trigger={['click']}> + 0 && selectedFileIds.length < sortedFiles.length} + checked={selectedFileIds.length === sortedFiles.length} + onChange={(e) => handleSelectAll(e.target.checked)}> + {t('files.batch_operation')} + + + )} {dataSource && dataSource?.length > 0 ? ( @@ -268,6 +359,7 @@ const MainContent = styled.div` const SortContainer = styled.div` display: flex; align-items: center; + justify-content: space-between; gap: 8px; padding: 8px 16px; border-bottom: 0.5px solid var(--color-border); @@ -315,25 +407,4 @@ const SideNav = styled.div` } ` -const SortButton = styled(Button)<{ active?: boolean }>` - display: flex; - align-items: center; - gap: 4px; - padding: 4px 12px; - height: 30px; - border-radius: var(--list-item-border-radius); - border: 0.5px solid ${(props) => (props.active ? 'var(--color-border)' : 'transparent')}; - background-color: ${(props) => (props.active ? 'var(--color-background-soft)' : 'transparent')}; - color: ${(props) => (props.active ? 'var(--color-text)' : 'var(--color-text-secondary)')}; - - &:hover { - background-color: var(--color-background-soft); - color: var(--color-text); - } - - .anticon { - font-size: 12px; - } -` - export default FilesPage