Revert "refactor(FileItem, FileList, FilesPage, i18n): enhance file management UI and localization"

This reverts commit edeb9f84f9.
This commit is contained in:
Teo 2025-06-14 15:48:47 +08:00
parent f1804bc3a0
commit 20b3db0c01
10 changed files with 169 additions and 458 deletions

View File

@ -470,7 +470,7 @@
"count": "files", "count": "files",
"created_at": "Created At", "created_at": "Created At",
"delete": "Delete", "delete": "Delete",
"delete.content": "Deleting a file will delete its reference from all messages. Are you sure you want to delete {{count}} files?", "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", "delete.paintings.warning": "Image contains this file, deletion is not possible",
"delete.title": "Delete File", "delete.title": "Delete File",
"document": "Document", "document": "Document",

View File

@ -470,7 +470,7 @@
"count": "ファイル", "count": "ファイル",
"created_at": "作成日", "created_at": "作成日",
"delete": "削除", "delete": "削除",
"delete.content": "ファイルを削除すると、ファイルがすべてのメッセージで参照されることを削除します。この{{count}}ファイルを削除してもよろしいですか?", "delete.content": "ファイルを削除すると、ファイルがすべてのメッセージで参照されることを削除します。このファイルを削除してもよろしいですか?",
"delete.paintings.warning": "画像に含まれているため、削除できません", "delete.paintings.warning": "画像に含まれているため、削除できません",
"delete.title": "ファイルを削除", "delete.title": "ファイルを削除",
"document": "ドキュメント", "document": "ドキュメント",

View File

@ -470,7 +470,7 @@
"count": "файлов", "count": "файлов",
"created_at": "Дата создания", "created_at": "Дата создания",
"delete": "Удалить", "delete": "Удалить",
"delete.content": "Удаление файла удалит его из всех сообщений, вы уверены, что хотите удалить этот {{count}} файл", "delete.content": "Удаление файла удалит его из всех сообщений, вы уверены, что хотите удалить этот файл?",
"delete.paintings.warning": "В изображениях содержится этот файл, удаление невозможно", "delete.paintings.warning": "В изображениях содержится этот файл, удаление невозможно",
"delete.title": "Удалить файл", "delete.title": "Удалить файл",
"document": "Документ", "document": "Документ",

View File

@ -470,7 +470,7 @@
"count": "个文件", "count": "个文件",
"created_at": "创建时间", "created_at": "创建时间",
"delete": "删除", "delete": "删除",
"delete.content": "删除文件会删除文件在所有消息中的引用,确定要删除这{{count}}个文件吗?", "delete.content": "删除文件会删除文件在所有消息中的引用,确定要删除文件吗?",
"delete.paintings.warning": "绘图中包含该图片,暂时无法删除", "delete.paintings.warning": "绘图中包含该图片,暂时无法删除",
"delete.title": "删除文件", "delete.title": "删除文件",
"document": "文档", "document": "文档",

View File

@ -470,7 +470,7 @@
"count": "個檔案", "count": "個檔案",
"created_at": "建立時間", "created_at": "建立時間",
"delete": "刪除", "delete": "刪除",
"delete.content": "刪除檔案會刪除檔案在所有訊息中的引用,確定要刪除這{{count}}個檔案嗎?", "delete.content": "刪除檔案會刪除檔案在所有訊息中的引用,確定要刪除檔案嗎?",
"delete.paintings.warning": "繪圖中包含該圖片,暫時無法刪除", "delete.paintings.warning": "繪圖中包含該圖片,暫時無法刪除",
"delete.title": "刪除檔案", "delete.title": "刪除檔案",
"document": "文件", "document": "文件",

View File

@ -12,7 +12,7 @@ import {
GlobalOutlined, GlobalOutlined,
LinkOutlined LinkOutlined
} from '@ant-design/icons' } from '@ant-design/icons'
import { t } from 'i18next' import { Flex } from 'antd'
import React, { memo } from 'react' import React, { memo } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
@ -21,14 +21,10 @@ interface FileItemProps {
icon?: React.ReactNode icon?: React.ReactNode
name: React.ReactNode | string name: React.ReactNode | string
ext: string ext: string
size: string extra?: React.ReactNode | string
created_at: string
count?: number
checkbox?: React.ReactNode
actions: React.ReactNode actions: React.ReactNode
} }
style?: React.CSSProperties style?: React.CSSProperties
gridTemplate?: string
} }
const getFileIcon = (type?: string) => { const getFileIcon = (type?: string) => {
@ -79,30 +75,19 @@ const getFileIcon = (type?: string) => {
return <FileUnknownFilled /> return <FileUnknownFilled />
} }
const FileItem: React.FC<FileItemProps> = ({ fileInfo, style, gridTemplate = '' }) => { const FileItem: React.FC<FileItemProps> = ({ fileInfo, style }) => {
const { name, ext, size, created_at, count, actions, icon, checkbox } = fileInfo const { name, ext, extra, actions, icon } = fileInfo
return ( return (
<FileItemCard style={style}> <FileItemCard style={style}>
<FileGrid style={{ gridTemplateColumns: gridTemplate }}> <CardContent>
{checkbox && <FileCell>{checkbox}</FileCell>} <FileIcon>{icon || getFileIcon(ext)}</FileIcon>
<FileCell> <Flex vertical justify="center" gap={0} flex={1} style={{ width: '0px' }}>
<FileIcon>{icon || getFileIcon(ext)}</FileIcon> <FileName>{name}</FileName>
</FileCell> {extra && <FileInfo>{extra}</FileInfo>}
<FileCell> </Flex>
<FileNameColumn> <FileActions>{actions}</FileActions>
<FileName>{name}</FileName> </CardContent>
{count && (
<FileCount>
{count} {t('files.count')}
</FileCount>
)}
</FileNameColumn>
</FileCell>
<FileCell style={{ textAlign: 'right' }}>{size}</FileCell>
<FileCell style={{ textAlign: 'right' }}>{created_at}</FileCell>
<FileCell style={{ justifyContent: 'center' }}>{actions}</FileCell>
</FileGrid>
</FileItemCard> </FileItemCard>
) )
} }
@ -126,36 +111,20 @@ const FileItemCard = styled.div`
} }
` `
const FileGrid = styled.div` const CardContent = styled.div`
display: grid;
gap: 8px;
padding: 8px 8px 8px 16px; padding: 8px 8px 8px 16px;
align-items: center;
`
const FileCell = styled.div`
display: flex; display: flex;
align-items: center; align-items: stretch;
min-width: 0; gap: 16px;
` `
const FileIcon = styled.div` const FileIcon = styled.div`
max-height: 44px; max-height: 44px;
width: 100%;
color: var(--color-text-3); color: var(--color-text-3);
font-size: 32px; font-size: 32px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-shrink: 0;
`
const FileNameColumn = styled.div`
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
width: 100%;
` `
const FileName = styled.div` const FileName = styled.div`
@ -173,9 +142,16 @@ const FileName = styled.div`
} }
` `
const FileCount = styled.div` const FileInfo = styled.div`
font-size: 13px; font-size: 13px;
color: var(--color-text-2); color: var(--color-text-2);
` `
const FileActions = styled.div`
max-height: 44px;
display: flex;
align-items: center;
justify-content: center;
`
export default memo(FileItem) export default memo(FileItem)

View File

@ -1,6 +1,11 @@
import FileManager from '@renderer/services/FileManager'
import { FileType, FileTypes } from '@renderer/types' import { FileType, FileTypes } from '@renderer/types'
import { formatFileSize } from '@renderer/utils'
import { Col, Image, Row, Spin } from 'antd'
import { t } from 'i18next'
import VirtualList from 'rc-virtual-list' import VirtualList from 'rc-virtual-list'
import React, { memo } from 'react' import React, { memo } from 'react'
import styled from 'styled-components'
import FileItem from './FileItem' import FileItem from './FileItem'
@ -14,47 +19,43 @@ interface FileItemProps {
size: string size: string
ext: string ext: string
created_at: string created_at: string
checkbox?: React.ReactNode
actions: React.ReactNode actions: React.ReactNode
}[] }[]
files?: FileType[] files?: FileType[]
selectedFileIds?: string[]
onFileSelect?: (fileId: string, checked: boolean) => void
columnWidths?: string
} }
const FileList: React.FC<FileItemProps> = ({ list, columnWidths }) => { const FileList: React.FC<FileItemProps> = ({ id, list, files }) => {
// if (id === FileTypes.IMAGE && files?.length && files?.length > 0) { if (id === FileTypes.IMAGE && files?.length && files?.length > 0) {
// return ( return (
// <div style={{ padding: 16, overflowY: 'auto' }}> <div style={{ padding: 16, overflowY: 'auto' }}>
// <Image.PreviewGroup> <Image.PreviewGroup>
// <Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
// {files?.map((file) => ( {files?.map((file) => (
// <Col key={file.id} xs={24} sm={12} md={8} lg={4} xl={3}> <Col key={file.id} xs={24} sm={12} md={8} lg={4} xl={3}>
// <ImageWrapper> <ImageWrapper>
// <LoadingWrapper> <LoadingWrapper>
// <Spin /> <Spin />
// </LoadingWrapper> </LoadingWrapper>
// <Image <Image
// src={FileManager.getFileUrl(file)} src={FileManager.getFileUrl(file)}
// style={{ height: '100%', objectFit: 'cover', cursor: 'pointer' }} style={{ height: '100%', objectFit: 'cover', cursor: 'pointer' }}
// preview={{ mask: false }} preview={{ mask: false }}
// onLoad={(e) => { onLoad={(e) => {
// const img = e.target as HTMLImageElement const img = e.target as HTMLImageElement
// img.parentElement?.classList.add('loaded') img.parentElement?.classList.add('loaded')
// }} }}
// /> />
// <ImageInfo> <ImageInfo>
// <div>{formatFileSize(file.size)}</div> <div>{formatFileSize(file.size)}</div>
// </ImageInfo> </ImageInfo>
// </ImageWrapper> </ImageWrapper>
// </Col> </Col>
// ))} ))}
// </Row> </Row>
// </Image.PreviewGroup> </Image.PreviewGroup>
// </div> </div>
// ) )
// } }
return ( return (
<VirtualList <VirtualList
@ -62,7 +63,7 @@ const FileList: React.FC<FileItemProps> = ({ list, columnWidths }) => {
height={window.innerHeight - 100} height={window.innerHeight - 100}
itemHeight={75} itemHeight={75}
itemKey="key" itemKey="key"
style={{ padding: '0 0 16px 0' }} style={{ padding: '0 16px 16px 16px' }}
styles={{ styles={{
verticalScrollBar: { verticalScrollBar: {
width: 6 width: 6
@ -75,21 +76,16 @@ const FileList: React.FC<FileItemProps> = ({ list, columnWidths }) => {
<div <div
style={{ style={{
height: '75px', height: '75px',
paddingTop: '8px', paddingTop: '12px'
margin: '0 16px'
}}> }}>
<FileItem <FileItem
key={item.key} key={item.key}
fileInfo={{ fileInfo={{
name: item.file, name: item.file,
ext: item.ext, ext: item.ext,
size: item.size, extra: `${item.created_at} · ${item.count}${t('files.count')} · ${item.size}`,
created_at: item.created_at,
count: item.count,
checkbox: item.checkbox,
actions: item.actions actions: item.actions
}} }}
gridTemplate={columnWidths}
/> />
</div> </div>
)} )}
@ -97,70 +93,70 @@ const FileList: React.FC<FileItemProps> = ({ list, columnWidths }) => {
) )
} }
// const ImageWrapper = styled.div` const ImageWrapper = styled.div`
// position: relative; position: relative;
// aspect-ratio: 1; aspect-ratio: 1;
// overflow: hidden; overflow: hidden;
// border-radius: 8px; border-radius: 8px;
// background-color: var(--color-background-soft); background-color: var(--color-background-soft);
// display: flex; display: flex;
// align-items: center; align-items: center;
// justify-content: center; justify-content: center;
// border: 0.5px solid var(--color-border); border: 0.5px solid var(--color-border);
// .ant-image { .ant-image {
// height: 100%; height: 100%;
// width: 100%; width: 100%;
// opacity: 0; opacity: 0;
// transition: transition:
// opacity 0.3s ease, opacity 0.3s ease,
// transform 0.3s ease; transform 0.3s ease;
// &.loaded { &.loaded {
// opacity: 1; opacity: 1;
// } }
// } }
// &:hover { &:hover {
// .ant-image.loaded { .ant-image.loaded {
// transform: scale(1.05); transform: scale(1.05);
// } }
// div:last-child { div:last-child {
// opacity: 1; opacity: 1;
// } }
// } }
// ` `
// const LoadingWrapper = styled.div` const LoadingWrapper = styled.div`
// position: absolute; position: absolute;
// top: 0; top: 0;
// left: 0; left: 0;
// right: 0; right: 0;
// bottom: 0; bottom: 0;
// display: flex; display: flex;
// align-items: center; align-items: center;
// justify-content: center; justify-content: center;
// background-color: var(--color-background-soft); background-color: var(--color-background-soft);
// ` `
// const ImageInfo = styled.div` const ImageInfo = styled.div`
// position: absolute; position: absolute;
// bottom: 0; bottom: 0;
// left: 0; left: 0;
// right: 0; right: 0;
// background: rgba(0, 0, 0, 0.6); background: rgba(0, 0, 0, 0.6);
// color: white; color: white;
// padding: 5px 8px; padding: 5px 8px;
// opacity: 0; opacity: 0;
// transition: opacity 0.3s ease; transition: opacity 0.3s ease;
// font-size: 12px; font-size: 12px;
// > div:first-child { > div:first-child {
// white-space: nowrap; white-space: nowrap;
// overflow: hidden; overflow: hidden;
// text-overflow: ellipsis; text-overflow: ellipsis;
// } }
// ` `
export default memo(FileList) export default memo(FileList)

View File

@ -15,7 +15,7 @@ import store from '@renderer/store'
import { FileType, FileTypes } from '@renderer/types' import { FileType, FileTypes } from '@renderer/types'
import { Message } from '@renderer/types/newMessage' import { Message } from '@renderer/types/newMessage'
import { formatFileSize } from '@renderer/utils' import { formatFileSize } from '@renderer/utils'
import { Button, Checkbox, Empty, Flex, Popconfirm } from 'antd' import { Button, Empty, Flex, Popconfirm } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useLiveQuery } from 'dexie-react-hooks' import { useLiveQuery } from 'dexie-react-hooks'
import { File as FileIcon, FileImage, FileText, FileType as FileTypeIcon } from 'lucide-react' import { File as FileIcon, FileImage, FileText, FileType as FileTypeIcon } from 'lucide-react'
@ -25,8 +25,6 @@ import styled from 'styled-components'
import FileList from './FileList' import FileList from './FileList'
const GRID_TEMPLATE = 'auto 60px 1fr 120px 140px 100px'
type SortField = 'created_at' | 'size' | 'name' type SortField = 'created_at' | 'size' | 'name'
type SortOrder = 'asc' | 'desc' type SortOrder = 'asc' | 'desc'
@ -35,7 +33,6 @@ const FilesPage: FC = () => {
const [fileType, setFileType] = useState<string>('document') const [fileType, setFileType] = useState<string>('document')
const [sortField, setSortField] = useState<SortField>('created_at') const [sortField, setSortField] = useState<SortField>('created_at')
const [sortOrder, setSortOrder] = useState<SortOrder>('desc') const [sortOrder, setSortOrder] = useState<SortOrder>('desc')
const [selectedFileIds, setSelectedFileIds] = useState<string[]>([])
const tempFilesSort = (files: FileType[]) => { const tempFilesSort = (files: FileType[]) => {
return files.sort((a, b) => { return files.sort((a, b) => {
@ -173,38 +170,6 @@ const FilesPage: FC = () => {
} }
} }
// 全选/取消全选
const handleSelectAll = (checked: boolean) => {
if (checked) {
setSelectedFileIds(sortedFiles.map((file) => file.id))
} else {
setSelectedFileIds([])
}
}
// 单个文件选择
const handleFileSelect = (fileId: string, checked: boolean) => {
if (checked) {
setSelectedFileIds((prev) => [...prev, fileId])
} else {
setSelectedFileIds((prev) => prev.filter((id) => id !== fileId))
}
}
// 批量删除
const handleBatchDelete = async () => {
if (selectedFileIds.length === 0) return
try {
for (const fileId of selectedFileIds) {
await handleDelete(fileId)
}
setSelectedFileIds([])
} catch (error) {
Logger.error('Batch delete error:', error)
}
}
const dataSource = sortedFiles?.map((file) => { const dataSource = sortedFiles?.map((file) => {
return { return {
key: file.id, key: file.id,
@ -216,18 +181,12 @@ const FilesPage: FC = () => {
ext: file.ext, ext: file.ext,
created_at: dayjs(file.created_at).format('MM-DD HH:mm'), created_at: dayjs(file.created_at).format('MM-DD HH:mm'),
created_at_unix: dayjs(file.created_at).unix(), created_at_unix: dayjs(file.created_at).unix(),
checkbox: (
<Checkbox
checked={selectedFileIds.includes(file.id)}
onChange={(e) => handleFileSelect(file.id, e.target.checked)}
/>
),
actions: ( actions: (
<Flex align="center" gap={0} style={{ opacity: 0.7 }}> <Flex align="center" gap={0} style={{ opacity: 0.7 }}>
<Button type="text" icon={<EditOutlined />} onClick={() => handleRename(file.id)} /> <Button type="text" icon={<EditOutlined />} onClick={() => handleRename(file.id)} />
<Popconfirm <Popconfirm
title={t('files.delete.title')} title={t('files.delete.title')}
description={t('files.delete.content', { count: 1 })} description={t('files.delete.content')}
okText={t('common.confirm')} okText={t('common.confirm')}
cancelText={t('common.cancel')} cancelText={t('common.cancel')}
onConfirm={() => handleDelete(file.id)} onConfirm={() => handleDelete(file.id)}
@ -264,90 +223,26 @@ const FilesPage: FC = () => {
))} ))}
</SideNav> </SideNav>
<MainContent> <MainContent>
<TableHeader> <SortContainer>
<TableGrid style={{ gridTemplateColumns: GRID_TEMPLATE }}> {['created_at', 'size', 'name'].map((field) => (
<TableCell> <SortButton
<Checkbox key={field}
indeterminate={selectedFileIds.length > 0 && selectedFileIds.length < sortedFiles.length} active={sortField === field}
checked={selectedFileIds.length === sortedFiles.length && sortedFiles.length > 0} onClick={() => {
onChange={(e) => handleSelectAll(e.target.checked)} if (sortField === field) {
disabled={sortedFiles.length === 0} setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
/> } else {
</TableCell> setSortField(field as 'created_at' | 'size' | 'name')
<TableCell>{/* 图标列 */}</TableCell> setSortOrder('desc')
<TableCell> }
<SortButton }}>
active={sortField === 'name'} {t(`files.${field}`)}
onClick={() => { {sortField === field && (sortOrder === 'desc' ? <SortDescendingOutlined /> : <SortAscendingOutlined />)}
if (sortField === 'name') { </SortButton>
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc') ))}
} else { </SortContainer>
setSortField('name')
setSortOrder('desc')
}
}}>
{t('files.name')}
{sortField === 'name' &&
(sortOrder === 'desc' ? <SortDescendingOutlined /> : <SortAscendingOutlined />)}
</SortButton>
</TableCell>
<TableCell>
<SortButton
active={sortField === 'size'}
onClick={() => {
if (sortField === 'size') {
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
} else {
setSortField('size')
setSortOrder('desc')
}
}}>
{t('files.size')}
{sortField === 'size' &&
(sortOrder === 'desc' ? <SortDescendingOutlined /> : <SortAscendingOutlined />)}
</SortButton>
</TableCell>
<TableCell>
<SortButton
active={sortField === 'created_at'}
onClick={() => {
if (sortField === 'created_at') {
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
} else {
setSortField('created_at')
setSortOrder('desc')
}
}}>
{t('files.created_at')}
{sortField === 'created_at' &&
(sortOrder === 'desc' ? <SortDescendingOutlined /> : <SortAscendingOutlined />)}
</SortButton>
</TableCell>
<TableCell style={{ justifyContent: 'center' }}>
{selectedFileIds.length > 0 && (
<Popconfirm
title={t('files.delete.title')}
description={t('files.delete.content', { count: selectedFileIds.length })}
okText={t('common.confirm')}
cancelText={t('common.cancel')}
onConfirm={handleBatchDelete}
icon={<ExclamationCircleOutlined style={{ color: 'red' }} />}>
<Button type="text" danger icon={<DeleteOutlined />} />
</Popconfirm>
)}
</TableCell>
</TableGrid>
</TableHeader>
{dataSource && dataSource?.length > 0 ? ( {dataSource && dataSource?.length > 0 ? (
<FileList <FileList id={fileType} list={dataSource} files={sortedFiles} />
id={fileType}
list={dataSource}
files={sortedFiles}
selectedFileIds={selectedFileIds}
onFileSelect={handleFileSelect}
columnWidths={GRID_TEMPLATE}
/>
) : ( ) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)} )}
@ -370,6 +265,14 @@ const MainContent = styled.div`
flex-direction: column; flex-direction: column;
` `
const SortContainer = styled.div`
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border-bottom: 0.5px solid var(--color-border);
`
const ContentContainer = styled.div` const ContentContainer = styled.div`
display: flex; display: flex;
flex: 1; flex: 1;
@ -412,39 +315,20 @@ const SideNav = styled.div`
} }
` `
const SortButton = styled.div<{ active?: boolean }>` const SortButton = styled(Button)<{ active?: boolean }>`
position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: 4px;
padding: 4px 12px;
height: 30px; 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)')}; color: ${(props) => (props.active ? 'var(--color-text)' : 'var(--color-text-secondary)')};
font-weight: ${(props) => (props.active ? '500' : '400')};
cursor: pointer;
z-index: 1;
&::before { &:hover {
content: '';
position: absolute;
top: -4px;
left: -8px;
right: -8px;
bottom: -4px;
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')};
transition: all 0.2s ease;
z-index: -1;
}
&:hover::before {
background-color: var(--color-background-soft); background-color: var(--color-background-soft);
border-color: var(--color-border); color: var(--color-text);
}
> * {
position: relative;
z-index: 2;
} }
.anticon { .anticon {
@ -452,25 +336,4 @@ const SortButton = styled.div<{ active?: boolean }>`
} }
` `
const TableGrid = styled.div`
display: grid;
gap: 8px;
padding: 8px 8px 0px 16px;
align-items: center;
`
const TableCell = styled.div`
display: flex;
align-items: center;
justify-content: flex-start;
&:last-child {
justify-content: center;
}
`
const TableHeader = styled.div`
margin: 0 16px;
`
export default FilesPage export default FilesPage

View File

@ -21,8 +21,8 @@ import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import CustomCollapse from '../../components/CustomCollapse' import CustomCollapse from '../../components/CustomCollapse'
import FileItem from '../files/FileItem'
import { NavbarIcon } from '../home/ChatNavbar' import { NavbarIcon } from '../home/ChatNavbar'
import KnowledgeFileItem from './components/KnowledgeFileItem'
import KnowledgeSearchPopup from './components/KnowledgeSearchPopup' import KnowledgeSearchPopup from './components/KnowledgeSearchPopup'
import KnowledgeSettingsPopup from './components/KnowledgeSettingsPopup' import KnowledgeSettingsPopup from './components/KnowledgeSettingsPopup'
import StatusIcon from './components/StatusIcon' import StatusIcon from './components/StatusIcon'
@ -323,8 +323,8 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
{(item) => { {(item) => {
const file = item.content as FileType const file = item.content as FileType
return ( return (
<div style={{ height: '75px', marginTop: '12px' }}> <div style={{ height: '75px', paddingTop: '12px' }}>
<KnowledgeFileItem <FileItem
key={item.id} key={item.id}
fileInfo={{ fileInfo={{
name: ( name: (
@ -381,7 +381,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<FlexColumn> <FlexColumn>
{directoryItems.length === 0 && <EmptyView />} {directoryItems.length === 0 && <EmptyView />}
{directoryItems.reverse().map((item) => ( {directoryItems.reverse().map((item) => (
<KnowledgeFileItem <FileItem
key={item.id} key={item.id}
fileInfo={{ fileInfo={{
name: ( name: (
@ -433,7 +433,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<FlexColumn> <FlexColumn>
{urlItems.length === 0 && <EmptyView />} {urlItems.length === 0 && <EmptyView />}
{urlItems.reverse().map((item) => ( {urlItems.reverse().map((item) => (
<KnowledgeFileItem <FileItem
key={item.id} key={item.id}
fileInfo={{ fileInfo={{
name: ( name: (
@ -510,7 +510,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<FlexColumn> <FlexColumn>
{sitemapItems.length === 0 && <EmptyView />} {sitemapItems.length === 0 && <EmptyView />}
{sitemapItems.reverse().map((item) => ( {sitemapItems.reverse().map((item) => (
<KnowledgeFileItem <FileItem
key={item.id} key={item.id}
fileInfo={{ fileInfo={{
name: ( name: (
@ -565,7 +565,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<FlexColumn> <FlexColumn>
{noteItems.length === 0 && <EmptyView />} {noteItems.length === 0 && <EmptyView />}
{noteItems.reverse().map((note) => ( {noteItems.reverse().map((note) => (
<KnowledgeFileItem <FileItem
key={note.id} key={note.id}
fileInfo={{ fileInfo={{
name: <span onClick={() => handleEditNote(note)}>{(note.content as string).slice(0, 50)}...</span>, name: <span onClick={() => handleEditNote(note)}>{(note.content as string).slice(0, 50)}...</span>,

View File

@ -1,124 +0,0 @@
import { getFileIcon } from '@renderer/pages/home/Inputbar/AttachmentPreview'
import React, { memo } from 'react'
import styled from 'styled-components'
interface KnowledgeFileItemProps {
fileInfo: {
icon?: React.ReactNode
name: React.ReactNode | string
ext: string
extra?: string
actions: React.ReactNode
}
style?: React.CSSProperties
}
const KnowledgeFileItem: React.FC<KnowledgeFileItemProps> = ({ fileInfo, style }) => {
const { name, ext, extra, actions, icon } = fileInfo
return (
<FileItemCard style={style}>
<FileContainer>
<FileIconContainer>
<FileIcon>{icon || getFileIcon(ext)}</FileIcon>
</FileIconContainer>
<FileContent>
<FileNameContainer>
<FileName>{name}</FileName>
{extra && <FileExtra>{extra}</FileExtra>}
</FileNameContainer>
</FileContent>
<FileActions>{actions}</FileActions>
</FileContainer>
</FileItemCard>
)
}
const FileItemCard = styled.div`
border-radius: 8px;
overflow: hidden;
border: 0.5px solid var(--color-border);
flex-shrink: 0;
transition:
box-shadow 0.2s ease,
background-color 0.2s ease;
--shadow-color: rgba(0, 0, 0, 0.05);
&:hover {
box-shadow:
0 10px 15px -3px var(--shadow-color),
0 4px 6px -4px var(--shadow-color);
}
body[theme-mode='dark'] & {
--shadow-color: rgba(255, 255, 255, 0.02);
}
`
const FileContainer = styled.div`
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
min-height: 63px;
`
const FileIconContainer = styled.div`
flex-shrink: 0;
`
const FileIcon = styled.div`
width: 40px;
height: 40px;
color: var(--color-text-3);
font-size: 32px;
display: flex;
align-items: center;
justify-content: center;
`
const FileContent = styled.div`
flex: 1;
min-width: 0;
`
const FileNameContainer = styled.div`
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
`
const FileName = styled.div`
font-size: 15px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
transition: color 0.2s ease;
span {
font-size: 15px;
}
&:hover {
color: var(--color-primary);
}
`
const FileExtra = styled.div`
font-size: 13px;
color: var(--color-text-2);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`
const FileActions = styled.div`
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
`
export default memo(KnowledgeFileItem)