2024-06-05 14:27:06 +08:00
|
|
|
|
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('')
|
|
|
|
|
|
|
2024-06-05 16:29:36 +08:00
|
|
|
|
const { page, pageSize, data: datalist, total, loading, setPage, search, reload, filterData, refreshData,loadData } = useTable({}, (param) =>
|
2024-06-05 14:27:06 +08:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|