diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json
index 232b5d06..feb079fa 100644
--- a/src/renderer/src/i18n/locales/en-us.json
+++ b/src/renderer/src/i18n/locales/en-us.json
@@ -184,7 +184,12 @@
"open": "Open",
"size": "Size",
"text": "Text",
- "title": "Files"
+ "title": "Files",
+ "edit": "Edit",
+ "delete": "Delete",
+ "delete.title": "Delete File",
+ "delete.content": "Deleting a file will delete its reference from all messages. Are you sure you want to delete this file?",
+ "delete.paintings.warning": "Image contains this file, deletion is not possible"
},
"history": {
"continue_chat": "Continue Chatting",
diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json
index 5570313f..e3303a80 100644
--- a/src/renderer/src/i18n/locales/ja-jp.json
+++ b/src/renderer/src/i18n/locales/ja-jp.json
@@ -184,7 +184,12 @@
"open": "開く",
"size": "サイズ",
"text": "テキスト",
- "title": "ファイル"
+ "title": "ファイル",
+ "edit": "編集",
+ "delete": "削除",
+ "delete.title": "ファイルを削除",
+ "delete.content": "ファイルを削除すると、ファイルがすべてのメッセージで参照されることを削除します。このファイルを削除してもよろしいですか?",
+ "delete.paintings.warning": "画像に含まれているため、削除できません"
},
"history": {
"continue_chat": "チャットを続ける",
diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json
index 3f758652..8f4fa371 100644
--- a/src/renderer/src/i18n/locales/ru-ru.json
+++ b/src/renderer/src/i18n/locales/ru-ru.json
@@ -184,7 +184,12 @@
"open": "Открыть",
"size": "Размер",
"text": "Текст",
- "title": "Файлы"
+ "title": "Файлы",
+ "edit": "Редактировать",
+ "delete": "Удалить",
+ "delete.title": "Удалить файл",
+ "delete.content": "Удаление файла удалит его из всех сообщений, вы уверены, что хотите удалить этот файл?",
+ "delete.paintings.warning": "В изображениях содержится этот файл, удаление невозможно"
},
"history": {
"continue_chat": "Продолжить чат",
diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json
index 4073cc95..c7b6fb40 100644
--- a/src/renderer/src/i18n/locales/zh-cn.json
+++ b/src/renderer/src/i18n/locales/zh-cn.json
@@ -185,7 +185,12 @@
"open": "打开",
"size": "大小",
"text": "文本",
- "title": "文件"
+ "title": "文件",
+ "edit": "编辑",
+ "delete": "删除",
+ "delete.title": "删除文件",
+ "delete.content": "删除文件会删除文件在所有消息中的引用,确定要删除此文件吗?",
+ "delete.paintings.warning": "绘图中包含该图片,暂时无法删除"
},
"history": {
"continue_chat": "继续聊天",
diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json
index 87b51b32..dbb2ad93 100644
--- a/src/renderer/src/i18n/locales/zh-tw.json
+++ b/src/renderer/src/i18n/locales/zh-tw.json
@@ -184,7 +184,12 @@
"open": "打開",
"size": "大小",
"text": "文本",
- "title": "檔案"
+ "title": "檔案",
+ "edit": "編輯",
+ "delete": "刪除",
+ "delete.title": "刪除檔案",
+ "delete.content": "刪除檔案會刪除檔案在所有消息中的引用,確定要刪除此檔案嗎?",
+ "delete.paintings.warning": "繪圖中包含該圖片,暫時無法刪除"
},
"history": {
"continue_chat": "繼續聊天",
diff --git a/src/renderer/src/pages/files/FilesPage.tsx b/src/renderer/src/pages/files/FilesPage.tsx
index 79dbd9d1..89fcc089 100644
--- a/src/renderer/src/pages/files/FilesPage.tsx
+++ b/src/renderer/src/pages/files/FilesPage.tsx
@@ -1,11 +1,21 @@
-import { FileImageOutlined, FilePdfOutlined, FileTextOutlined } from '@ant-design/icons'
+import {
+ DeleteOutlined,
+ EditOutlined,
+ EllipsisOutlined,
+ FileImageOutlined,
+ FilePdfOutlined,
+ FileTextOutlined
+} from '@ant-design/icons'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
+import TextEditPopup from '@renderer/components/Popups/TextEditPopup'
import Scrollbar from '@renderer/components/Scrollbar'
import db from '@renderer/databases'
import FileManager from '@renderer/services/FileManager'
+import store from '@renderer/store'
import { FileType, FileTypes } from '@renderer/types'
import { formatFileSize } from '@renderer/utils'
-import { Col, Image, Menu, Row, Spin, Table } from 'antd'
+import type { MenuProps } from 'antd'
+import { Button, Col, Dropdown, Image, Menu, Row, Spin, Table } from 'antd'
import dayjs from 'dayjs'
import { useLiveQuery } from 'dexie-react-hooks'
import { FC, useState } from 'react'
@@ -23,14 +33,88 @@ const FilesPage: FC = () => {
return db.files.where('type').equals(fileType).sortBy('count')
}, [fileType])
+ const handleDelete = async (fileId: string) => {
+ const file = await FileManager.getFile(fileId)
+
+ const paintings = await store.getState().paintings.paintings
+ const paintingsFiles = paintings.flatMap((p) => p.files)
+
+ if (paintingsFiles.some((p) => p.id === fileId)) {
+ window.modal.warning({ content: t('files.delete.paintings.warning'), centered: true })
+ return
+ }
+
+ if (file) {
+ await FileManager.deleteFile(fileId, true)
+ }
+
+ const topics = await db.topics
+ .filter((topic) => topic.messages.some((message) => message.files?.some((f) => f.id === fileId)))
+ .toArray()
+
+ if (topics.length > 0) {
+ for (const topic of topics) {
+ const updatedMessages = topic.messages.map((message) => ({
+ ...message,
+ files: message.files?.filter((f) => f.id !== fileId)
+ }))
+ await db.topics.update(topic.id, { messages: updatedMessages })
+ }
+ }
+ }
+
+ const handleRename = async (fileId: string) => {
+ const file = await FileManager.getFile(fileId)
+ if (file) {
+ const newName = await TextEditPopup.show({ text: file.origin_name })
+ if (newName) {
+ FileManager.updateFile({ ...file, origin_name: newName })
+ }
+ }
+ }
+
+ const getActionMenu = (fileId: string): MenuProps['items'] => [
+ {
+ key: 'rename',
+ icon: ,
+ label: t('files.edit'),
+ onClick: () => handleRename(fileId)
+ },
+ {
+ key: 'delete',
+ icon: ,
+ label: t('files.delete'),
+ danger: true,
+ onClick: () => {
+ window.modal.confirm({
+ title: t('files.delete.title'),
+ content: t('files.delete.content'),
+ centered: true,
+ okButtonProps: { danger: true },
+ onOk: () => handleDelete(fileId)
+ })
+ }
+ }
+ ]
+
const dataSource = files?.map((file) => {
return {
key: file.id,
- file: {file.origin_name},
+ file: (
+ window.api.file.openPath(file.path)}>
+ {file.origin_name}
+
+ ),
size: formatFileSize(file),
+ size_bytes: file.size,
count: file.count,
created_at: dayjs(file.created_at).format('MM-DD HH:mm'),
- actions: {t('files.open')}
+ created_at_unix: dayjs(file.created_at).unix(),
+ actions: (
+
+ } />
+
+ )
}
})
@@ -45,19 +129,25 @@ const FilesPage: FC = () => {
title: t('files.size'),
dataIndex: 'size',
key: 'size',
- width: '80px'
+ width: '80px',
+ sorter: (a: { size_bytes: number }, b: { size_bytes: number }) => b.size_bytes - a.size_bytes,
+ align: 'center'
},
{
title: t('files.count'),
dataIndex: 'count',
key: 'count',
- width: '60px'
+ width: '60px',
+ sorter: (a: { count: number }, b: { count: number }) => b.count - a.count,
+ align: 'center'
},
{
title: t('files.created_at'),
dataIndex: 'created_at',
key: 'created_at',
- width: '120px'
+ width: '120px',
+ align: 'center',
+ sorter: (a: { created_at_unix: number }, b: { created_at_unix: number }) => b.created_at_unix - a.created_at_unix
},
{
title: t('files.actions'),
@@ -149,6 +239,7 @@ const TableContainer = styled(Scrollbar)`
const FileNameText = styled.div`
font-size: 14px;
color: var(--color-text);
+ cursor: pointer;
`
const ImageWrapper = styled.div`
diff --git a/src/renderer/src/services/FileManager.ts b/src/renderer/src/services/FileManager.ts
index d2b8ac89..55a397ef 100644
--- a/src/renderer/src/services/FileManager.ts
+++ b/src/renderer/src/services/FileManager.ts
@@ -57,20 +57,29 @@ class FileManager {
return file
}
- static async deleteFile(id: string): Promise {
+ static async deleteFile(id: string, force: boolean = false): Promise {
const file = await this.getFile(id)
+ console.debug('[FileManager] Deleting file:', file)
+
if (!file) {
return
}
- if (file.count > 1) {
- await db.files.update(id, { ...file, count: file.count - 1 })
- return
+ if (!force) {
+ if (file.count > 1) {
+ await db.files.update(id, { ...file, count: file.count - 1 })
+ return
+ }
}
await db.files.delete(id)
- await window.api.file.delete(id + file.ext)
+
+ try {
+ await window.api.file.delete(id + file.ext)
+ } catch (error) {
+ console.error('[FileManager] Failed to delete file:', error)
+ }
}
static async deleteFiles(files: FileType[]): Promise {
@@ -93,6 +102,14 @@ class FileManager {
const filesPath = store.getState().runtime.filesPath
return 'file://' + filesPath + '/' + file.name
}
+
+ static async updateFile(file: FileType) {
+ if (!file.origin_name.includes(file.ext)) {
+ file.origin_name = file.origin_name + file.ext
+ }
+
+ await db.files.update(file.id, file)
+ }
}
export default FileManager