feat: add translations and file management features

This commit is contained in:
kangfenmao 2025-01-02 18:29:36 +08:00
parent dba1f76db7
commit 4cbdd563e8
7 changed files with 150 additions and 17 deletions

View File

@ -184,7 +184,12 @@
"open": "Open", "open": "Open",
"size": "Size", "size": "Size",
"text": "Text", "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": { "history": {
"continue_chat": "Continue Chatting", "continue_chat": "Continue Chatting",

View File

@ -184,7 +184,12 @@
"open": "開く", "open": "開く",
"size": "サイズ", "size": "サイズ",
"text": "テキスト", "text": "テキスト",
"title": "ファイル" "title": "ファイル",
"edit": "編集",
"delete": "削除",
"delete.title": "ファイルを削除",
"delete.content": "ファイルを削除すると、ファイルがすべてのメッセージで参照されることを削除します。このファイルを削除してもよろしいですか?",
"delete.paintings.warning": "画像に含まれているため、削除できません"
}, },
"history": { "history": {
"continue_chat": "チャットを続ける", "continue_chat": "チャットを続ける",

View File

@ -184,7 +184,12 @@
"open": "Открыть", "open": "Открыть",
"size": "Размер", "size": "Размер",
"text": "Текст", "text": "Текст",
"title": "Файлы" "title": "Файлы",
"edit": "Редактировать",
"delete": "Удалить",
"delete.title": "Удалить файл",
"delete.content": "Удаление файла удалит его из всех сообщений, вы уверены, что хотите удалить этот файл?",
"delete.paintings.warning": "В изображениях содержится этот файл, удаление невозможно"
}, },
"history": { "history": {
"continue_chat": "Продолжить чат", "continue_chat": "Продолжить чат",

View File

@ -185,7 +185,12 @@
"open": "打开", "open": "打开",
"size": "大小", "size": "大小",
"text": "文本", "text": "文本",
"title": "文件" "title": "文件",
"edit": "编辑",
"delete": "删除",
"delete.title": "删除文件",
"delete.content": "删除文件会删除文件在所有消息中的引用,确定要删除此文件吗?",
"delete.paintings.warning": "绘图中包含该图片,暂时无法删除"
}, },
"history": { "history": {
"continue_chat": "继续聊天", "continue_chat": "继续聊天",

View File

@ -184,7 +184,12 @@
"open": "打開", "open": "打開",
"size": "大小", "size": "大小",
"text": "文本", "text": "文本",
"title": "檔案" "title": "檔案",
"edit": "編輯",
"delete": "刪除",
"delete.title": "刪除檔案",
"delete.content": "刪除檔案會刪除檔案在所有消息中的引用,確定要刪除此檔案嗎?",
"delete.paintings.warning": "繪圖中包含該圖片,暫時無法刪除"
}, },
"history": { "history": {
"continue_chat": "繼續聊天", "continue_chat": "繼續聊天",

View File

@ -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 { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import TextEditPopup from '@renderer/components/Popups/TextEditPopup'
import Scrollbar from '@renderer/components/Scrollbar' import Scrollbar from '@renderer/components/Scrollbar'
import db from '@renderer/databases' import db from '@renderer/databases'
import FileManager from '@renderer/services/FileManager' import FileManager from '@renderer/services/FileManager'
import store from '@renderer/store'
import { FileType, FileTypes } from '@renderer/types' import { FileType, FileTypes } from '@renderer/types'
import { formatFileSize } from '@renderer/utils' 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 dayjs from 'dayjs'
import { useLiveQuery } from 'dexie-react-hooks' import { useLiveQuery } from 'dexie-react-hooks'
import { FC, useState } from 'react' import { FC, useState } from 'react'
@ -23,14 +33,88 @@ const FilesPage: FC = () => {
return db.files.where('type').equals(fileType).sortBy('count') return db.files.where('type').equals(fileType).sortBy('count')
}, [fileType]) }, [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: <EditOutlined />,
label: t('files.edit'),
onClick: () => handleRename(fileId)
},
{
key: 'delete',
icon: <DeleteOutlined />,
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) => { const dataSource = files?.map((file) => {
return { return {
key: file.id, key: file.id,
file: <FileNameText className="text-nowrap">{file.origin_name}</FileNameText>, file: (
<FileNameText className="text-nowrap" onClick={() => window.api.file.openPath(file.path)}>
{file.origin_name}
</FileNameText>
),
size: formatFileSize(file), size: formatFileSize(file),
size_bytes: file.size,
count: file.count, count: file.count,
created_at: dayjs(file.created_at).format('MM-DD HH:mm'), created_at: dayjs(file.created_at).format('MM-DD HH:mm'),
actions: <a href={'file://' + FileManager.getSafePath(file)}>{t('files.open')}</a> created_at_unix: dayjs(file.created_at).unix(),
actions: (
<Dropdown menu={{ items: getActionMenu(file.id) }} trigger={['click']}>
<Button type="text" size="small" icon={<EllipsisOutlined />} />
</Dropdown>
)
} }
}) })
@ -45,19 +129,25 @@ const FilesPage: FC = () => {
title: t('files.size'), title: t('files.size'),
dataIndex: 'size', dataIndex: 'size',
key: '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'), title: t('files.count'),
dataIndex: 'count', dataIndex: 'count',
key: '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'), title: t('files.created_at'),
dataIndex: 'created_at', dataIndex: 'created_at',
key: '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'), title: t('files.actions'),
@ -149,6 +239,7 @@ const TableContainer = styled(Scrollbar)`
const FileNameText = styled.div` const FileNameText = styled.div`
font-size: 14px; font-size: 14px;
color: var(--color-text); color: var(--color-text);
cursor: pointer;
` `
const ImageWrapper = styled.div` const ImageWrapper = styled.div`

View File

@ -57,20 +57,29 @@ class FileManager {
return file return file
} }
static async deleteFile(id: string): Promise<void> { static async deleteFile(id: string, force: boolean = false): Promise<void> {
const file = await this.getFile(id) const file = await this.getFile(id)
console.debug('[FileManager] Deleting file:', file)
if (!file) { if (!file) {
return return
} }
if (file.count > 1) { if (!force) {
await db.files.update(id, { ...file, count: file.count - 1 }) if (file.count > 1) {
return await db.files.update(id, { ...file, count: file.count - 1 })
return
}
} }
await db.files.delete(id) 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<void> { static async deleteFiles(files: FileType[]): Promise<void> {
@ -93,6 +102,14 @@ class FileManager {
const filesPath = store.getState().runtime.filesPath const filesPath = store.getState().runtime.filesPath
return 'file://' + filesPath + '/' + file.name 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 export default FileManager