diff --git a/src/renderer/src/assets/styles/animation.scss b/src/renderer/src/assets/styles/animation.scss
index 5d02acfc..eb6cb359 100644
--- a/src/renderer/src/assets/styles/animation.scss
+++ b/src/renderer/src/assets/styles/animation.scss
@@ -1,4 +1,4 @@
-@keyframes pulse {
+@keyframes animation-pulse {
0% {
box-shadow: 0 0 0 0 rgba(var(--pulse-color), 0.5);
}
@@ -14,5 +14,5 @@
.animation-pulse {
--pulse-color: 59, 130, 246;
--pulse-size: 8px;
- animation: pulse 1.5s infinite;
+ animation: animation-pulse 1.5s infinite;
}
diff --git a/src/renderer/src/components/CustomCollapse.tsx b/src/renderer/src/components/CustomCollapse.tsx
new file mode 100644
index 00000000..dc15babf
--- /dev/null
+++ b/src/renderer/src/components/CustomCollapse.tsx
@@ -0,0 +1,44 @@
+import { Collapse } from 'antd'
+import { FC, memo } from 'react'
+
+interface CustomCollapseProps {
+ label: string
+ extra: React.ReactNode
+ children: React.ReactNode
+}
+
+const CustomCollapse: FC = ({ label, extra, children }) => {
+ const CollapseStyle = {
+ background: 'transparent',
+ border: '0.5px solid var(--color-border)'
+ }
+ const CollapseItemStyles = {
+ header: {
+ padding: '8px 16px',
+ alignItems: 'center',
+ justifyContent: 'space-between'
+ },
+ body: {
+ borderTop: '0.5px solid var(--color-border)'
+ }
+ }
+ return (
+
+ )
+}
+
+export default memo(CustomCollapse)
diff --git a/src/renderer/src/pages/files/FileItem.tsx b/src/renderer/src/pages/files/FileItem.tsx
new file mode 100644
index 00000000..79baa857
--- /dev/null
+++ b/src/renderer/src/pages/files/FileItem.tsx
@@ -0,0 +1,145 @@
+import {
+ FileExcelFilled,
+ FileImageFilled,
+ FileMarkdownFilled,
+ FilePdfFilled,
+ FilePptFilled,
+ FileTextFilled,
+ FileUnknownFilled,
+ FileWordFilled,
+ FileZipFilled,
+ FolderOpenFilled,
+ GlobalOutlined,
+ LinkOutlined
+} from '@ant-design/icons'
+import { Flex } from 'antd'
+import React, { memo } from 'react'
+import styled from 'styled-components'
+
+interface FileItemProps {
+ fileInfo: {
+ name: React.ReactNode | string
+ ext: string
+ extra?: React.ReactNode | string
+ actions: React.ReactNode
+ }
+}
+
+const getFileIcon = (type?: string) => {
+ if (!type) return
+
+ const ext = type.toLowerCase()
+
+ if (['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'].includes(ext)) {
+ return
+ }
+
+ if (['.doc', '.docx'].includes(ext)) {
+ return
+ }
+ if (['.xls', '.xlsx'].includes(ext)) {
+ return
+ }
+ if (['.ppt', '.pptx'].includes(ext)) {
+ return
+ }
+ if (ext === '.pdf') {
+ return
+ }
+ if (['.md', '.markdown'].includes(ext)) {
+ return
+ }
+
+ if (['.zip', '.rar', '.7z', '.tar', '.gz'].includes(ext)) {
+ return
+ }
+
+ if (['.txt', '.json', '.log', '.yml', '.yaml', '.xml', '.csv'].includes(ext)) {
+ return
+ }
+
+ if (['.url'].includes(ext)) {
+ return
+ }
+
+ if (['.sitemap'].includes(ext)) {
+ return
+ }
+
+ if (['.folder'].includes(ext)) {
+ return
+ }
+
+ return
+}
+
+const FileItem: React.FC = ({ fileInfo }) => {
+ const { name, ext, extra, actions } = fileInfo
+
+ return (
+
+
+ {getFileIcon(ext)}
+
+ {name}
+ {extra && {extra}}
+
+ {actions}
+
+
+ )
+}
+
+const FileItemCard = styled.div`
+ background: rgba(255, 255, 255, 0.04);
+ border-radius: 8px;
+ overflow: hidden;
+ border: 0.5px solid var(--color-border);
+ flex-shrink: 0;
+ transition: box-shadow 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 CardContent = styled.div`
+ padding: 8px 16px;
+ display: flex;
+ align-items: center;
+ gap: 16px;
+`
+
+const FileIcon = styled.div`
+ color: var(--color-text-3);
+ font-size: 32px;
+`
+
+const FileName = styled.div`
+ font-size: 15px;
+ font-weight: bold;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ cursor: pointer;
+ transition: color 0.2s ease;
+ span {
+ font-size: 15px;
+ font-weight: bold;
+ }
+ &:hover {
+ color: var(--color-primary);
+ }
+`
+
+const FileInfo = styled.div`
+ font-size: 13px;
+ color: var(--color-text-2);
+`
+
+export default memo(FileItem)
diff --git a/src/renderer/src/pages/files/FileList.tsx b/src/renderer/src/pages/files/FileList.tsx
new file mode 100644
index 00000000..d81b5d50
--- /dev/null
+++ b/src/renderer/src/pages/files/FileList.tsx
@@ -0,0 +1,167 @@
+import FileManager from '@renderer/services/FileManager'
+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 React, { memo } from 'react'
+import styled from 'styled-components'
+
+import FileItem from './FileItem'
+import GeminiFiles from './GeminiFiles'
+
+interface FileItemProps {
+ id: FileTypes | 'all' | string
+ list: {
+ key: FileTypes | 'all' | string
+ file: React.ReactNode
+ files?: FileType[]
+ count?: number
+ size: string
+ ext: string
+ created_at: string
+ actions: React.ReactNode
+ }[]
+ files?: FileType[]
+}
+
+const FileList: React.FC = ({ id, list, files }) => {
+ if (id === FileTypes.IMAGE && files?.length && files?.length > 0) {
+ return (
+
+
+
+ {files?.map((file) => (
+
+
+
+
+
+ {
+ const img = e.target as HTMLImageElement
+ img.parentElement?.classList.add('loaded')
+ }}
+ />
+
+ {formatFileSize(file.size)}
+
+
+
+ ))}
+
+
+
+ )
+ }
+
+ if (id.startsWith('gemini_')) {
+ return
+ }
+
+ return (
+
+ {(item) => (
+
+
+
+ )}
+
+ )
+}
+
+const ImageWrapper = styled.div`
+ position: relative;
+ aspect-ratio: 1;
+ overflow: hidden;
+ border-radius: 8px;
+ background-color: var(--color-background-soft);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 0.5px solid var(--color-border);
+
+ .ant-image {
+ height: 100%;
+ width: 100%;
+ opacity: 0;
+ transition:
+ opacity 0.3s ease,
+ transform 0.3s ease;
+
+ &.loaded {
+ opacity: 1;
+ }
+ }
+
+ &:hover {
+ .ant-image.loaded {
+ transform: scale(1.05);
+ }
+
+ div:last-child {
+ opacity: 1;
+ }
+ }
+`
+
+const LoadingWrapper = styled.div`
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: var(--color-background-soft);
+`
+
+const ImageInfo = styled.div`
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: rgba(0, 0, 0, 0.6);
+ color: white;
+ padding: 5px 8px;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ font-size: 12px;
+
+ > div:first-child {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+`
+
+export default memo(FileList)
diff --git a/src/renderer/src/pages/files/FilesPage.tsx b/src/renderer/src/pages/files/FilesPage.tsx
index 4ad8f731..bbbb770a 100644
--- a/src/renderer/src/pages/files/FilesPage.tsx
+++ b/src/renderer/src/pages/files/FilesPage.tsx
@@ -1,14 +1,15 @@
import {
DeleteOutlined,
EditOutlined,
- EllipsisOutlined,
+ ExclamationCircleOutlined,
FileImageOutlined,
FilePdfOutlined,
- FileTextOutlined
+ FileTextOutlined,
+ SortAscendingOutlined,
+ SortDescendingOutlined
} 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 { useProviders } from '@renderer/hooks/useProvider'
import FileManager from '@renderer/services/FileManager'
@@ -16,18 +17,23 @@ import store from '@renderer/store'
import { FileType, FileTypes } from '@renderer/types'
import { formatFileSize } from '@renderer/utils'
import type { MenuProps } from 'antd'
-import { Button, Dropdown, Menu } from 'antd'
+import { Button, Empty, Flex, Menu, Popconfirm } from 'antd'
import dayjs from 'dayjs'
import { useLiveQuery } from 'dexie-react-hooks'
-import { FC, useMemo, useState } from 'react'
+import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
-import ContentView from './ContentView'
+import FileList from './FileList'
+
+type SortField = 'created_at' | 'size' | 'name'
+type SortOrder = 'asc' | 'desc'
const FilesPage: FC = () => {
const { t } = useTranslation()
const [fileType, setFileType] = useState('document')
+ const [sortField, setSortField] = useState('created_at')
+ const [sortOrder, setSortOrder] = useState('desc')
const { providers } = useProviders()
const geminiProviders = providers.filter((provider) => provider.type === 'gemini')
@@ -42,6 +48,24 @@ const FilesPage: FC = () => {
})
}
+ const sortFiles = (files: FileType[]) => {
+ return [...files].sort((a, b) => {
+ let comparison = 0
+ switch (sortField) {
+ case 'created_at':
+ comparison = dayjs(a.created_at).unix() - dayjs(b.created_at).unix()
+ break
+ case 'size':
+ comparison = a.size - b.size
+ break
+ case 'name':
+ comparison = a.origin_name.localeCompare(b.origin_name)
+ break
+ }
+ return sortOrder === 'asc' ? comparison : -comparison
+ })
+ }
+
const files = useLiveQuery(() => {
if (fileType === 'all') {
return db.files.orderBy('count').toArray().then(tempFilesSort)
@@ -49,6 +73,8 @@ const FilesPage: FC = () => {
return db.files.where('type').equals(fileType).sortBy('count').then(tempFilesSort)
}, [fileType])
+ const sortedFiles = files ? sortFiles(files) : []
+
const handleDelete = async (fileId: string) => {
const file = await FileManager.getFile(fileId)
@@ -89,95 +115,34 @@ const FilesPage: FC = () => {
}
}
- 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) => {
+ const dataSource = sortedFiles?.map((file) => {
return {
key: file.id,
- file: (
- window.api.file.openPath(file.path)}>
- {FileManager.formatFileName(file)}
-
- ),
+ file: window.api.file.openPath(file.path)}>{FileManager.formatFileName(file)},
size: formatFileSize(file.size),
size_bytes: file.size,
count: file.count,
+ path: file.path,
+ ext: file.ext,
created_at: dayjs(file.created_at).format('MM-DD HH:mm'),
created_at_unix: dayjs(file.created_at).unix(),
actions: (
-
- } />
-
+
+ } onClick={() => handleRename(file.id)} />
+ handleDelete(file.id)}
+ icon={}>
+ } />
+
+
)
}
})
- const columns = useMemo(
- () => [
- {
- title: t('files.name'),
- dataIndex: 'file',
- key: 'file',
- width: '300px'
- },
- {
- title: t('files.size'),
- dataIndex: 'size',
- key: 'size',
- 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',
- 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',
- 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'),
- dataIndex: 'actions',
- key: 'actions',
- width: '80px',
- align: 'center'
- }
- ],
- [t]
- )
-
const menuItems = [
{ key: FileTypes.DOCUMENT, label: t('files.document'), icon: },
{ key: FileTypes.IMAGE, label: t('files.image'), icon: },
@@ -199,9 +164,31 @@ const FilesPage: FC = () => {
-
-
-
+
+
+ {['created_at', 'size', 'name'].map((field) => (
+ {
+ if (sortField === field) {
+ setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
+ } else {
+ setSortField(field as 'created_at' | 'size' | 'name')
+ setSortOrder('desc')
+ }
+ }}>
+ {t(`files.${field}`)}
+ {sortField === field && (sortOrder === 'desc' ? : )}
+
+ ))}
+
+ {dataSource && dataSource?.length > 0 ? (
+
+ ) : (
+
+ )}
+
)
@@ -214,6 +201,20 @@ const Container = styled.div`
height: calc(100vh - var(--navbar-height));
`
+const MainContent = styled.div`
+ display: flex;
+ flex: 1;
+ 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`
display: flex;
flex: 1;
@@ -221,19 +222,6 @@ const ContentContainer = styled.div`
min-height: 100%;
`
-const TableContainer = styled(Scrollbar)`
- padding: 15px;
- display: flex;
- width: 100%;
- flex-direction: column;
-`
-
-const FileNameText = styled.div`
- font-size: 14px;
- color: var(--color-text);
- cursor: pointer;
-`
-
const SideNav = styled.div`
width: var(--assistants-width);
border-right: 0.5px solid var(--color-border);
@@ -266,4 +254,25 @@ 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
diff --git a/src/renderer/src/pages/files/GeminiFiles.tsx b/src/renderer/src/pages/files/GeminiFiles.tsx
index ff60c875..a664856b 100644
--- a/src/renderer/src/pages/files/GeminiFiles.tsx
+++ b/src/renderer/src/pages/files/GeminiFiles.tsx
@@ -2,12 +2,13 @@ import { DeleteOutlined } from '@ant-design/icons'
import type { FileMetadataResponse } from '@google/generative-ai/server'
import { useProvider } from '@renderer/hooks/useProvider'
import { runAsyncFunction } from '@renderer/utils'
-import { Table } from 'antd'
-import type { ColumnsType } from 'antd/es/table'
+import { Spin } from 'antd'
+import dayjs from 'dayjs'
import { FC, useCallback, useEffect, useState } from 'react'
-import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
+import FileItem from './FileItem'
+
interface GeminiFilesProps {
id: string
}
@@ -15,7 +16,6 @@ interface GeminiFilesProps {
const GeminiFiles: FC = ({ id }) => {
const { provider } = useProvider(id)
const [files, setFiles] = useState([])
- const { t } = useTranslation()
const [loading, setLoading] = useState(false)
const fetchFiles = useCallback(async () => {
@@ -23,51 +23,6 @@ const GeminiFiles: FC = ({ id }) => {
files && setFiles(files.filter((file) => file.state === 'ACTIVE'))
}, [provider])
- const columns: ColumnsType = [
- {
- title: t('files.name'),
- dataIndex: 'displayName',
- key: 'displayName'
- },
- {
- title: t('files.type'),
- dataIndex: 'mimeType',
- key: 'mimeType'
- },
- {
- title: t('files.size'),
- dataIndex: 'sizeBytes',
- key: 'sizeBytes',
- render: (size: string) => `${(parseInt(size) / 1024 / 1024).toFixed(2)} MB`
- },
- {
- title: t('files.created_at'),
- dataIndex: 'createTime',
- key: 'createTime',
- render: (time: string) => new Date(time).toLocaleString()
- },
- {
- title: t('files.actions'),
- dataIndex: 'actions',
- key: 'actions',
- align: 'center',
- render: (_, record) => {
- return (
- {
- setFiles(files.filter((file) => file.name !== record.name))
- window.api.gemini.deleteFile(provider.apiKey, record.name).catch((error) => {
- console.error('Failed to delete file:', error)
- setFiles((prev) => [...prev, record])
- })
- }}
- />
- )
- }
- }
- ]
-
useEffect(() => {
runAsyncFunction(async () => {
try {
@@ -86,13 +41,61 @@ const GeminiFiles: FC = ({ id }) => {
setFiles([])
}, [id])
+ if (loading) {
+ return (
+
+
+
+
+
+ )
+ }
+
return (
-
+
+ {files.map((file) => (
+ {
+ setFiles(files.filter((f) => f.name !== file.name))
+ window.api.gemini.deleteFile(provider.apiKey, file.name).catch((error) => {
+ console.error('Failed to delete file:', error)
+ setFiles((prev) => [...prev, file])
+ })
+ }}
+ />
+ )
+ }}
+ />
+ ))}
+
)
}
-const Container = styled.div``
+const Container = styled.div`
+ width: 100%;
+`
+
+const FileListContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+`
+
+const LoadingWrapper = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-height: 200px;
+`
export default GeminiFiles
diff --git a/src/renderer/src/pages/knowledge/KnowledgeContent.tsx b/src/renderer/src/pages/knowledge/KnowledgeContent.tsx
index ada10179..88f87e53 100644
--- a/src/renderer/src/pages/knowledge/KnowledgeContent.tsx
+++ b/src/renderer/src/pages/knowledge/KnowledgeContent.tsx
@@ -2,10 +2,6 @@ import {
CopyOutlined,
DeleteOutlined,
EditOutlined,
- FileTextOutlined,
- FolderOutlined,
- GlobalOutlined,
- LinkOutlined,
PlusOutlined,
RedoOutlined,
SearchOutlined,
@@ -19,24 +15,29 @@ import { useKnowledge } from '@renderer/hooks/useKnowledge'
import FileManager from '@renderer/services/FileManager'
import { getProviderName } from '@renderer/services/ProviderService'
import { FileType, FileTypes, KnowledgeBase, KnowledgeItem } from '@renderer/types'
+import { formatFileSize } from '@renderer/utils'
import { bookExts, documentExts, textExts, thirdPartyApplicationExts } from '@shared/config/constant'
-import { Alert, Button, Card, Divider, Dropdown, message, Tag, Tooltip, Typography, Upload } from 'antd'
+import { Alert, Button, Divider, Dropdown, Empty, message, Tag, Tooltip, Upload } from 'antd'
+import dayjs from 'dayjs'
+import VirtualList from 'rc-virtual-list'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
+import CustomCollapse from '../../components/CustomCollapse'
+import FileItem from '../files/FileItem'
import KnowledgeSearchPopup from './components/KnowledgeSearchPopup'
import KnowledgeSettingsPopup from './components/KnowledgeSettingsPopup'
import StatusIcon from './components/StatusIcon'
const { Dragger } = Upload
-const { Title } = Typography
interface KnowledgeContentProps {
selectedBase: KnowledgeBase
}
const fileTypes = [...bookExts, ...thirdPartyApplicationExts, ...documentExts, ...textExts]
+
const KnowledgeContent: FC = ({ selectedBase }) => {
const { t } = useTranslation()
@@ -234,13 +235,14 @@ const KnowledgeContent: FC = ({ selectedBase }) => {
{!providerName && (
)}
-
-
- {t('files.title')}
- } onClick={handleAddFile} disabled={disabled}>
+
+ } onClick={handleAddFile} disabled={disabled}>
{t('knowledge.add_file')}
-
+ }>
handleDrop([file as File])}
@@ -252,86 +254,123 @@ const KnowledgeContent: FC = ({ selectedBase }) => {
{t('knowledge.file_hint', { file_types: 'TXT, MD, HTML, PDF, DOCX, PPTX, XLSX, EPUB...' })}
-
-
- {fileItems.reverse().map((item) => {
- const file = item.content as FileType
- return (
-
-
-
-
- window.api.file.openPath(file.path)}>
-
- {file.origin_name}
-
-
-
-
- {item.uniqueId && } onClick={() => refreshItem(item)} />}
-
-
-
-
-
-
- )
- })}
-
+
+ {fileItems.length === 0 ? (
+
+ ) : (
+
+ {(item) => {
+ const file = item.content as FileType
+ return (
+
+ window.api.file.openPath(file.path)}>
+
+ {file.origin_name}
+
+
+ ),
+ ext: file.ext,
+ extra: `${dayjs(file.created_at).format('MM-DD HH:mm')} · ${formatFileSize(file.size)}`,
+ actions: (
+
+ {item.uniqueId && (
+ } onClick={() => refreshItem(item)} />
+ )}
+
+
+
+
+ )
+ }}
+ />
+
+ )
+ }}
+
+ )}
+
+
-
-
- {t('knowledge.directories')}
- } onClick={handleAddDirectory} disabled={disabled}>
+ } onClick={handleAddDirectory} disabled={disabled}>
{t('knowledge.add_directory')}
-
+ }>
+ {directoryItems.length === 0 && }
{directoryItems.reverse().map((item) => (
-
-
-
-
+ window.api.file.openPath(item.content as string)}>
{item.content as string}
-
-
- {item.uniqueId && } onClick={() => refreshItem(item)} />}
-
-
-
-
-
-
+ ),
+ ext: '.folder',
+ extra: `${dayjs(item.created_at).format('MM-DD HH:mm')}`,
+ actions: (
+
+ {item.uniqueId && } onClick={() => refreshItem(item)} />}
+
+
+
+
+ )
+ }}
+ />
))}
-
+
-
-
- {t('knowledge.urls')}
- } onClick={handleAddUrl} disabled={disabled}>
+ } onClick={handleAddUrl} disabled={disabled}>
{t('knowledge.add_url')}
-
+ }>
+ {urlItems.length === 0 && }
{urlItems.reverse().map((item) => (
-
-
-
-
+ = ({ selectedBase }) => {
-
-
- {item.uniqueId && } onClick={() => refreshItem(item)} />}
-
-
-
-
-
-
+ ),
+ ext: '.url',
+ extra: `${dayjs(item.created_at).format('MM-DD HH:mm')}`,
+ actions: (
+
+ {item.uniqueId && } onClick={() => refreshItem(item)} />}
+
+
+
+
+ )
+ }}
+ />
))}
-
+
-
-
- {t('knowledge.sitemaps')}
- } onClick={handleAddSitemap} disabled={disabled}>
+ } onClick={handleAddSitemap} disabled={disabled}>
{t('knowledge.add_sitemap')}
-
+ }>
+ {sitemapItems.length === 0 && }
{sitemapItems.reverse().map((item) => (
-
-
-
-
+
@@ -399,51 +443,64 @@ const KnowledgeContent: FC = ({ selectedBase }) => {
-
-
- {item.uniqueId && } onClick={() => refreshItem(item)} />}
-
-
-
-
-
-
+ ),
+ ext: '.sitemap',
+ extra: `${dayjs(item.created_at).format('MM-DD HH:mm')}`,
+ actions: (
+
+ {item.uniqueId && } onClick={() => refreshItem(item)} />}
+
+
+
+
+ )
+ }}
+ />
))}
-
+
-
-
- {t('knowledge.notes')}
- } onClick={handleAddNote} disabled={disabled}>
+ } onClick={handleAddNote} disabled={disabled}>
{t('knowledge.add_note')}
-
+ }>
+ {noteItems.length === 0 && }
{noteItems.reverse().map((note) => (
-
-
- handleEditNote(note)} style={{ cursor: 'pointer' }}>
- {(note.content as string).slice(0, 50)}...
-
-
-
-
-
+ handleEditNote(note)}>{(note.content as string).slice(0, 50)}...,
+ ext: '.txt',
+ extra: `${dayjs(note.created_at).format('MM-DD HH:mm')}`,
+ actions: (
+
+ handleEditNote(note)} icon={} />
+
+
+
+ removeItem(note)} icon={} />
+
+ )
+ }}
+ />
))}
-
+
@@ -498,69 +555,9 @@ const MainContent = styled(Scrollbar)`
padding-bottom: 50px;
padding: 15px;
position: relative;
-`
-
-const FileSection = styled.div`
- display: flex;
- flex-direction: column;
-`
-
-const ContentSection = styled.div`
- margin-top: 20px;
- display: flex;
- flex-direction: column;
- gap: 10px;
-
- .ant-input-textarea {
- background: var(--color-background-soft);
- border-radius: 8px;
- }
-`
-
-const TitleWrapper = styled.div`
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 5px;
- background-color: var(--color-background-soft);
- padding: 5px 20px;
- min-height: 45px;
- border-radius: 6px;
- .ant-typography {
- margin-bottom: 0;
- }
-`
-
-const FileListSection = styled.div`
- margin-top: 20px;
- width: 100%;
- display: flex;
- flex-direction: column;
- gap: 8px;
-`
-
-const ItemCard = styled(Card)`
- background-color: transparent;
- border: none;
- .ant-card-body {
- padding: 0 20px;
- }
-`
-
-const ItemContent = styled.div`
- display: flex;
- align-items: center;
- justify-content: space-between;
gap: 16px;
`
-const ItemInfo = styled.div`
- display: flex;
- align-items: center;
- gap: 8px;
- flex: 1;
-`
-
const IndexSection = styled.div`
margin-top: 20px;
display: flex;
@@ -602,10 +599,12 @@ const ModelInfo = styled.div`
color: var(--color-text-2);
}
`
+
const FlexColumn = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
+ margin-top: 16px;
`
const FlexAlignCenter = styled.div`
@@ -620,10 +619,6 @@ const ClickableSpan = styled.span`
width: 0;
`
-const FileIcon = styled(FileTextOutlined)`
- font-size: 16px;
-`
-
const BottomSpacer = styled.div`
min-height: 20px;
`