1
This commit is contained in:
276
src/pages/FileLibPage/files.tsx
Executable file
276
src/pages/FileLibPage/files.tsx
Executable file
@@ -0,0 +1,276 @@
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCaption,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from "../../components/ui/table";
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "../../components/ui/tabs";
|
||||
|
||||
import { ArrowLeft, Filter, RotateCw, Search, X } from "lucide-react";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { bsconfirm } from "../../alerts/confirm";
|
||||
import PaginationComponent from "../../components/PaginationComponent";
|
||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||
import { Input } from "../../components/ui/input";
|
||||
import { Select, SelectContent, SelectGroup, SelectIconTrigger, SelectItem } from "../../components/ui/select1";
|
||||
import { locationContext } from "../../contexts/locationContext";
|
||||
import { deleteFile, readFileByLibDatabase, retryKnowledgeFileApi } from "../../controllers/API";
|
||||
import { captureAndAlertRequestErrorHoc } from "../../controllers/request";
|
||||
import UploadModal from "../../modals/UploadModal";
|
||||
import { useTable } from "../../util/hook";
|
||||
import del from "../../assets/npc/del.png"
|
||||
|
||||
export default function FilesPage() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { id } = useParams()
|
||||
// 上传 上传成功添加到列表
|
||||
const [open, setOpen] = useState(false)
|
||||
const [title, setTitle] = useState('')
|
||||
|
||||
const { page, pageSize, data: datalist, total, loading, setPage, search, reload, filterData, refreshData,loadData } = useTable((param) =>
|
||||
readFileByLibDatabase({ ...param, id, name: param.keyword }).then(res => {
|
||||
setHasPermission(res.writeable)
|
||||
return res
|
||||
})
|
||||
)
|
||||
// loadData();
|
||||
setTimeout(() => reload(), 5000);
|
||||
|
||||
const [hasPermission, setHasPermission] = useState(true)
|
||||
const { appConfig } = useContext(locationContext)
|
||||
|
||||
// filter
|
||||
const [filter, setFilter] = useState(999)
|
||||
useEffect(() => {
|
||||
filterData({ status: filter })
|
||||
}, [filter])
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
const libname = window.libname // 临时记忆
|
||||
if (libname) {
|
||||
localStorage.setItem('libname', window.libname)
|
||||
}
|
||||
setTitle(window.libname || localStorage.getItem('libname'))
|
||||
}, [])
|
||||
|
||||
const handleOpen = (e) => {
|
||||
setOpen(e)
|
||||
reload()
|
||||
}
|
||||
|
||||
// 删除
|
||||
const { delShow, idRef, close, delConfim } = useDelete()
|
||||
|
||||
const handleDelete = () => {
|
||||
captureAndAlertRequestErrorHoc(deleteFile(idRef.current).then(res => {
|
||||
reload()
|
||||
close()
|
||||
}))
|
||||
}
|
||||
|
||||
const [repeatFiles, setRepeatFiles] = useState([])
|
||||
// 上传结果展示
|
||||
const handleUploadResult = (fileCount, failFiles, res) => {
|
||||
const _repeatFiles = res.filter(e => e.status === 3)
|
||||
if (_repeatFiles.length) {
|
||||
setRepeatFiles(_repeatFiles)
|
||||
} else {
|
||||
failFiles.length && bsconfirm({
|
||||
desc: <div>
|
||||
<p>{t('lib.fileUploadResult', { total: fileCount, failed: failFiles.length })}</p>
|
||||
<div className="max-h-[160px] overflow-y-auto no-scrollbar">
|
||||
{failFiles.map(str => <p className=" text-red-400" key={str}>{str}</p>)}
|
||||
</div>
|
||||
</div>,
|
||||
onOk(next) {
|
||||
next()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 重试解析
|
||||
const [retryLoad, setRetryLoad] = useState(false)
|
||||
const handleRetry = (objs) => {
|
||||
setRetryLoad(true)
|
||||
captureAndAlertRequestErrorHoc(retryKnowledgeFileApi(objs).then(res => {
|
||||
// 乐观更新
|
||||
// refreshData(
|
||||
// (item) => ids.includes(item.id),
|
||||
// { status: 1 }
|
||||
// )
|
||||
reload()
|
||||
setRepeatFiles([])
|
||||
setRetryLoad(false)
|
||||
}))
|
||||
}
|
||||
|
||||
const selectChange = (id) => {
|
||||
setFilter(Number(id))
|
||||
}
|
||||
|
||||
return <div className="w-full h-screen p-6 relative overflow-y-auto">
|
||||
{loading && <div className="absolute w-full h-full top-0 left-0 flex justify-center items-center z-10 bg-[rgba(255,255,255,0.6)] dark:bg-blur-shared">
|
||||
<span className="loading loading-infinity loading-lg"></span>
|
||||
</div>}
|
||||
<ShadTooltip content="back" side="top">
|
||||
<button className="extra-side-bar-buttons w-[36px] absolute top-[26px]" onClick={() => { }} >
|
||||
<Link to='/filelib'><ArrowLeft className="side-bar-button-size" /></Link>
|
||||
</button>
|
||||
</ShadTooltip>
|
||||
<Tabs defaultValue="account" className="w-full">
|
||||
{/* <TabsList className="ml-12">
|
||||
<TabsTrigger value="account" className="roundedrounded-xl">{t('lib.fileList')}</TabsTrigger>
|
||||
<TabsTrigger disabled value="password">{t('lib.systemIntegration')}</TabsTrigger>
|
||||
</TabsList> */}
|
||||
<div className="flex justify-between">
|
||||
<p className="text-[16px] ml-[40px]" style={{color:"#FFF"}}>{t('lib.fileData')}</p>
|
||||
<div className="flex justify-center items-center w-[74px] h-[27px] cursor-pointer" style={{background: "#FFD025",borderRadius: "7px"}}>
|
||||
{/* <img src={jia1} className="w-[14px] mr-[5px]" alt=""/> */}
|
||||
<span className="text-[12px]" style={{color:"#333333"}} onClick={() => setOpen(true)}>上 传</span>
|
||||
</div>
|
||||
</div>
|
||||
<TabsContent value="account">
|
||||
{/* <div className="flex justify-between items-center">
|
||||
<span className=" text-gray-800">{title}</span>
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="w-[180px] relative">
|
||||
<Input placeholder={t('lib.fileName')} onChange={(e) => search(e.target.value)}></Input>
|
||||
<Search className="absolute right-4 top-2 text-gray-300 pointer-events-none"></Search>
|
||||
</div>
|
||||
{hasPermission && <Button className="h-8 rounded-full" onClick={() => setOpen(true)}>{t('lib.upload')}</Button>}
|
||||
</div>
|
||||
</div> */}
|
||||
<Table>
|
||||
<TableCaption>
|
||||
<div className="join grid grid-cols-2 w-[200px]">
|
||||
<PaginationComponent
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
total={total}
|
||||
onChange={(newPage) => setPage(newPage)}
|
||||
/>
|
||||
</div>
|
||||
</TableCaption>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[600px] dialogueLog-header">{t('lib.fileName')}</TableHead>
|
||||
{/* 状态 */}
|
||||
<TableHead className="flex items-center gap-4 dialogueLog-header">{t('lib.status')}
|
||||
{/* Select component */}
|
||||
<Select onValueChange={selectChange}>
|
||||
<SelectIconTrigger className="">
|
||||
<Filter size={16} className={`cursor-pointer ${filter === 999 ? '' : 'text-gray-950'}`} />
|
||||
</SelectIconTrigger>
|
||||
<SelectContent className="">
|
||||
<SelectGroup>
|
||||
<SelectItem value={'999'}>{t('all')}</SelectItem>
|
||||
<SelectItem value={'1'}>{t('lib.parsing')}</SelectItem>
|
||||
<SelectItem value={'2'}>{t('lib.completed')}</SelectItem>
|
||||
<SelectItem value={'3'}>{t('lib.parseFailed')}</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</TableHead>
|
||||
<TableHead className=" dialogueLog-header">{t('lib.uploadTime')}</TableHead>
|
||||
<TableHead className=" dialogueLog-header">{t('operations')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{datalist.map(el => (
|
||||
<TableRow key={el.id}>
|
||||
<TableCell className="dialogueLog-body">{el.file_name}</TableCell>
|
||||
<TableCell className="dialogueLog-body">
|
||||
{el.status === 3 ? <div className="flex items-center">
|
||||
<div className="tooltip" data-tip={el.remark}>
|
||||
<span className='text-red-500'>{t('lib.parseFailed')}</span>
|
||||
</div>
|
||||
<Button variant="link"><RotateCw size={16} onClick={() => handleRetry([el])} /></Button>
|
||||
</div> :
|
||||
<span className={el.status === 3 && 'text-red-500'}>{[t('lib.parseFailed'), t('lib.parsing'), t('lib.completed'), t('lib.parseFailed')][el.status]}</span>
|
||||
}
|
||||
</TableCell>
|
||||
<TableCell className="dialogueLog-body">{el.update_time.replace('T', ' ')}</TableCell>
|
||||
<TableCell className="text-right dialogueLog-body">
|
||||
{hasPermission ?
|
||||
// <a href="javascript:;" onClick={() => delConfim(el.id)} className="underline ml-4">{t('delete')}</a>
|
||||
<img src={del} onClick={() => delConfim(el.id)} className="w-[20px] cursor-pointer" alt=""/>
|
||||
:
|
||||
// <a href="javascript:;" className="underline ml-4 text-gray-400">{t('delete')}</a>
|
||||
<img src={del} className="w-[20px] cursor-pointer" alt=""/>
|
||||
}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{/* Pagination */}
|
||||
</TabsContent>
|
||||
<TabsContent value="password"></TabsContent>
|
||||
</Tabs>
|
||||
{/* upload modal */}
|
||||
<UploadModal id={id} accept={appConfig.libAccepts} open={open} setOpen={handleOpen} onResult={handleUploadResult}></UploadModal>
|
||||
{/* 重复文件提醒 */}
|
||||
<dialog className={`modal ${repeatFiles.length && 'modal-open'}`}>
|
||||
<div className="modal-box w-[560px] bg-[#fff] shadow-lg dark:bg-background">
|
||||
<h3 className="font-bold text-lg relative">文件重复提示
|
||||
<X className="absolute right-0 top-0 text-gray-400 cursor-pointer" size={20} onClick={() => setRepeatFiles([])}></X>
|
||||
</h3>
|
||||
<p className="py-4">以下文件在知识库中已存在,继续上传将会覆盖原有文件以及处理策略,是否覆盖?</p>
|
||||
<ul className="overflow-y-auto max-h-[400px]">
|
||||
{repeatFiles.map(el => (
|
||||
<li key={el.id} className="py-2 text-red-500">{el.remark}</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="modal-action">
|
||||
<Button className="h-8 rounded-full" variant="outline" onClick={() => setRepeatFiles([])}>不覆盖,保留原文件</Button>
|
||||
<Button className="h-8 rounded-full" disabled={retryLoad} onClick={() => handleRetry(repeatFiles)}>
|
||||
{retryLoad && <span className="loading loading-spinner loading-xs"></span>}覆盖
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
{/* Delete confirmation */}
|
||||
<dialog className={`modal ${delShow && 'modal-open'}`}>
|
||||
<form method="dialog" className="modal-box w-[400px] bg-[#262626] shadow-lg">
|
||||
<h3 className="text-[16px] font-bold text-center" style={{color:"#FFFFFF"}}>{t('prompt')}</h3>
|
||||
<p className="text-[12px] text-center mt-[18px]" style={{color:"#FFFFFF"}}>{t('lib.confirmDeleteFile')}</p>
|
||||
<div className="flex justify-center mt-[27px]">
|
||||
<Button className="baogao-btn" variant="outline" onClick={close}>{t('cancel')}</Button>
|
||||
<Button className="baogao-btn ml-[27px]" variant="destructive" onClick={handleDelete}>{t('delete')}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
};
|
||||
|
||||
|
||||
const useDelete = () => {
|
||||
const [delShow, setDelShow] = useState(false)
|
||||
const idRef = useRef<any>(null)
|
||||
|
||||
return {
|
||||
delShow,
|
||||
idRef,
|
||||
close: () => {
|
||||
setDelShow(false)
|
||||
},
|
||||
delConfim: (id) => {
|
||||
idRef.current = id
|
||||
setDelShow(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user