Feat: Display document parsing status #3221 (#7241)

### What problem does this PR solve?

Feat: Display document parsing status #3221
### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu 2025-04-24 11:45:37 +08:00 committed by GitHub
parent 216cd7474b
commit ff442c48b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 794 additions and 48 deletions

368
web/package-lock.json generated
View File

@ -23,6 +23,7 @@
"@radix-ui/react-collapsible": "^1.1.3",
"@radix-ui/react-dialog": "1.1.4",
"@radix-ui/react-dropdown-menu": "2.1.4",
"@radix-ui/react-hover-card": "^1.1.11",
"@radix-ui/react-icons": "^1.3.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.2.1",
@ -5484,6 +5485,340 @@
}
}
},
"node_modules/@radix-ui/react-hover-card": {
"version": "1.1.11",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-hover-card/-/react-hover-card-1.1.11.tgz",
"integrity": "sha512-q9h9grUpGZKR3MNhtVCLVnPGmx1YnzBgGR+O40mhSNGsUnkR+LChVH8c7FB0mkS+oudhd8KAkZGTJPJCjdAPIg==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.2",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-dismissable-layer": "1.1.7",
"@radix-ui/react-popper": "1.2.4",
"@radix-ui/react-portal": "1.1.6",
"@radix-ui/react-presence": "1.1.4",
"@radix-ui/react-primitive": "2.1.0",
"@radix-ui/react-use-controllable-state": "1.2.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@floating-ui/core": {
"version": "1.6.9",
"resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.9.tgz",
"integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.9"
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@floating-ui/dom": {
"version": "1.6.13",
"resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.6.13.tgz",
"integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.6.0",
"@floating-ui/utils": "^0.2.9"
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@floating-ui/react-dom": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz",
"integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==",
"license": "MIT",
"dependencies": {
"@floating-ui/dom": "^1.0.0"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/primitive": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.1.2.tgz",
"integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
"license": "MIT"
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-arrow": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-arrow/-/react-arrow-1.1.4.tgz",
"integrity": "sha512-qz+fxrqgNxG0dYew5l7qR3c7wdgRu1XVUHGnGYX7rg5HM4p9SWaRmJwfgR3J0SgyUKayLmzQIun+N6rWRgiRKw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-context": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.1.2.tgz",
"integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-popper": {
"version": "1.2.4",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-popper/-/react-popper-1.2.4.tgz",
"integrity": "sha512-3p2Rgm/a1cK0r/UVkx5F/K9v/EplfjAeIFCGOPYPO4lZ0jtg4iSQXt/YGTSLWaf4x7NG6Z4+uKFcylcTZjeqDA==",
"license": "MIT",
"dependencies": {
"@floating-ui/react-dom": "^2.0.0",
"@radix-ui/react-arrow": "1.1.4",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-primitive": "2.1.0",
"@radix-ui/react-use-callback-ref": "1.1.1",
"@radix-ui/react-use-layout-effect": "1.1.1",
"@radix-ui/react-use-rect": "1.1.1",
"@radix-ui/react-use-size": "1.1.1",
"@radix-ui/rect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-portal": {
"version": "1.1.6",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.1.6.tgz",
"integrity": "sha512-XmsIl2z1n/TsYFLIdYam2rmFwf9OC/Sh2avkbmVMDuBZIe7hSpM0cYnWPAo7nHOVx8zTuwDZGByfcqLdnzp3Vw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.1.0",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-presence": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.1.4.tgz",
"integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-primitive": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.1.0.tgz",
"integrity": "sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.2.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-slot": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.2.0.tgz",
"integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
"integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-use-controllable-state": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
"integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-effect-event": "0.0.2",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-use-rect": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
"integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
"license": "MIT",
"dependencies": {
"@radix-ui/rect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/react-use-size": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
"integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card/node_modules/@radix-ui/rect": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/@radix-ui/rect/-/rect-1.1.1.tgz",
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
"license": "MIT"
},
"node_modules/@radix-ui/react-icons": {
"version": "1.3.1",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-icons/-/react-icons-1.3.1.tgz",
@ -7319,6 +7654,39 @@
}
}
},
"node_modules/@radix-ui/react-use-effect-event": {
"version": "0.0.2",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
"integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-effect-event/node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
"integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-escape-keydown": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",

View File

@ -34,6 +34,7 @@
"@radix-ui/react-collapsible": "^1.1.3",
"@radix-ui/react-dialog": "1.1.4",
"@radix-ui/react-dropdown-menu": "2.1.4",
"@radix-ui/react-hover-card": "^1.1.11",
"@radix-ui/react-icons": "^1.3.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.2.1",

View File

@ -23,6 +23,7 @@ export function ConfirmDeleteDialog({
children,
title,
onOk,
onCancel,
hidden = false,
}: IProps & PropsWithChildren) {
const { t } = useTranslation();
@ -48,7 +49,9 @@ export function ConfirmDeleteDialog({
</AlertDialogDescription> */}
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t('common.cancel')}</AlertDialogCancel>
<AlertDialogCancel onClick={onCancel}>
{t('common.cancel')}
</AlertDialogCancel>
<AlertDialogAction
className="bg-colors-background-functional-solid-danger text--colors-text-neutral-strong"
onClick={onOk}

View File

@ -0,0 +1,29 @@
'use client';
import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
import * as React from 'react';
import { cn } from '@/lib/utils';
const HoverCard = HoverCardPrimitive.Root;
const HoverCardTrigger = HoverCardPrimitive.Trigger;
const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
'z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-hover-card-content-transform-origin]',
className,
)}
{...props}
/>
));
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
export { HoverCard, HoverCardContent, HoverCardTrigger };

View File

@ -1,11 +1,23 @@
import { IDocumentInfo } from '@/interfaces/database/document';
import i18n from '@/locales/config';
import kbService from '@/services/knowledge-service';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useDebounce } from 'ahooks';
import { message } from 'antd';
import { get } from 'lodash';
import { useCallback } from 'react';
import { useParams } from 'umi';
import {
useGetPaginationWithRouter,
useHandleSearchChange,
} from './logic-hooks';
import { useGetKnowledgeSearchParams } from './route-hook';
export const enum DocumentApiAction {
UploadDocument = 'uploadDocument',
FetchDocumentList = 'fetchDocumentList',
UpdateDocumentStatus = 'updateDocumentStatus',
RunDocumentByIds = 'runDocumentByIds',
}
export const useUploadNextDocument = () => {
@ -47,3 +59,133 @@ export const useUploadNextDocument = () => {
return { uploadDocument: mutateAsync, loading, data };
};
export const useFetchDocumentList = () => {
const { knowledgeId } = useGetKnowledgeSearchParams();
const { searchString, handleInputChange } = useHandleSearchChange();
const { pagination, setPagination } = useGetPaginationWithRouter();
const { id } = useParams();
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
const { data, isFetching: loading } = useQuery<{
docs: IDocumentInfo[];
total: number;
}>({
queryKey: [
DocumentApiAction.FetchDocumentList,
debouncedSearchString,
pagination,
],
initialData: { docs: [], total: 0 },
refetchInterval: 15000,
enabled: !!knowledgeId || !!id,
queryFn: async () => {
const ret = await kbService.get_document_list({
kb_id: knowledgeId || id,
keywords: debouncedSearchString,
page_size: pagination.pageSize,
page: pagination.current,
});
if (ret.data.code === 0) {
return ret.data.data;
}
return {
docs: [],
total: 0,
};
},
});
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
(e) => {
setPagination({ page: 1 });
handleInputChange(e);
},
[handleInputChange, setPagination],
);
return {
loading,
searchString,
documents: data.docs,
pagination: { ...pagination, total: data?.total },
handleInputChange: onInputChange,
setPagination,
};
};
export const useSetDocumentStatus = () => {
const queryClient = useQueryClient();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [DocumentApiAction.UpdateDocumentStatus],
mutationFn: async ({
status,
documentId,
}: {
status: boolean;
documentId: string;
}) => {
const { data } = await kbService.document_change_status({
doc_id: documentId,
status: Number(status),
});
if (data.code === 0) {
message.success(i18n.t('message.modified'));
queryClient.invalidateQueries({
queryKey: [DocumentApiAction.FetchDocumentList],
});
}
return data;
},
});
return { setDocumentStatus: mutateAsync, data, loading };
};
export const useRunDocument = () => {
const queryClient = useQueryClient();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [DocumentApiAction.RunDocumentByIds],
mutationFn: async ({
documentIds,
run,
shouldDelete,
}: {
documentIds: string[];
run: number;
shouldDelete: boolean;
}) => {
queryClient.invalidateQueries({
queryKey: [DocumentApiAction.FetchDocumentList],
});
const ret = await kbService.document_run({
doc_ids: documentIds,
run,
delete: shouldDelete,
});
const code = get(ret, 'data.code');
if (code === 0) {
queryClient.invalidateQueries({
queryKey: [DocumentApiAction.FetchDocumentList],
});
message.success(i18n.t('message.operated'));
}
return code;
},
});
return { runDocumentByIds: mutateAsync, loading, data };
};

View File

@ -0,0 +1,17 @@
import { RunningStatus } from '@/constants/knowledge';
export const RunningStatusMap = {
[RunningStatus.UNSTART]: {
label: 'UNSTART',
color: 'cyan',
},
[RunningStatus.RUNNING]: {
label: 'Parsing',
color: 'blue',
},
[RunningStatus.CANCEL]: { label: 'CANCEL', color: 'orange' },
[RunningStatus.DONE]: { label: 'SUCCESS', color: 'blue' },
[RunningStatus.FAIL]: { label: 'FAIL', color: 'red' },
};
export * from '@/constants/knowledge';

View File

@ -23,8 +23,8 @@ import {
TableHeader,
TableRow,
} from '@/components/ui/table';
import { useFetchNextDocumentList } from '@/hooks/document-hooks';
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
import { useFetchDocumentList } from '@/hooks/use-document-request';
import { IDocumentInfo } from '@/interfaces/database/document';
import { getExtension } from '@/utils/document-util';
import { useMemo } from 'react';
@ -38,7 +38,7 @@ export function DatasetTable() {
pagination,
// handleInputChange,
setPagination,
} = useFetchNextDocumentList();
} = useFetchDocumentList();
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[],

View File

@ -2,7 +2,6 @@ import { useSetModalState } from '@/hooks/common-hooks';
import {
useCreateNextDocument,
useNextWebCrawl,
useRunNextDocument,
useSaveNextDocumentName,
useSetNextDocumentParser,
} from '@/hooks/document-hooks';
@ -159,35 +158,3 @@ export const useHandleWebCrawl = () => {
showWebCrawlUploadModal,
};
};
export const useHandleRunDocumentByIds = (id: string) => {
const { runDocumentByIds, loading } = useRunNextDocument();
const [currentId, setCurrentId] = useState<string>('');
const isLoading = loading && currentId !== '' && currentId === id;
const handleRunDocumentByIds = async (
documentId: string,
isRunning: boolean,
shouldDelete: boolean = false,
) => {
if (isLoading) {
return;
}
setCurrentId(documentId);
try {
await runDocumentByIds({
documentIds: [documentId],
run: isRunning ? 2 : 1,
shouldDelete,
});
setCurrentId('');
} catch (error) {
setCurrentId('');
}
};
return {
handleRunDocumentByIds,
loading: isLoading,
};
};

View File

@ -19,7 +19,7 @@ export default function Dataset() {
return (
<section className="p-8">
<ListFilterBar title="Files">
<ListFilterBar title="Dataset">
<Button
variant={'tertiary'}
size={'sm'}

View File

@ -0,0 +1,101 @@
import { Button } from '@/components/ui/button';
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from '@/components/ui/hover-card';
import { IDocumentInfo } from '@/interfaces/database/document';
import { useTranslation } from 'react-i18next';
import reactStringReplace from 'react-string-replace';
import { RunningStatus, RunningStatusMap } from './constant';
interface IProps {
record: IDocumentInfo;
}
function Dot({ run }: { run: RunningStatus }) {
const runningStatus = RunningStatusMap[run];
return (
<span
className={'size-2 inline-block rounded'}
style={{ backgroundColor: runningStatus.color }}
></span>
);
}
export const PopoverContent = ({ record }: IProps) => {
const { t } = useTranslation();
const label = t(`knowledgeDetails.runningStatus${record.run}`);
const replaceText = (text: string) => {
// Remove duplicate \n
const nextText = text.replace(/(\n)\1+/g, '$1');
const replacedText = reactStringReplace(
nextText,
/(\[ERROR\].+\s)/g,
(match, i) => {
return (
<span key={i} className={'text-red-600'}>
{match}
</span>
);
},
);
return replacedText;
};
const items = [
{
key: 'process_begin_at',
label: t('knowledgeDetails.processBeginAt'),
children: record.process_begin_at,
},
{
key: 'knowledgeDetails.process_duation',
label: t('processDuration'),
children: `${record.process_duation.toFixed(2)} s`,
},
{
key: 'progress_msg',
label: t('knowledgeDetails.progressMsg'),
children: replaceText(record.progress_msg.trim()),
},
];
return (
<section>
<div className="flex gap-2 items-center pb-2">
<Dot run={record.run}></Dot> {label}
</div>
<div className="flex flex-col max-h-[50vh] overflow-auto">
{items.map((x, idx) => {
return (
<div key={x.key} className={idx < 2 ? 'flex gap-2' : ''}>
<b>{x.label}:</b>
<div className={'w-full whitespace-pre-line text-wrap '}>
{x.children}
</div>
</div>
);
})}
</div>
</section>
);
};
export function ParsingCard({ record }: IProps) {
return (
<HoverCard>
<HoverCardTrigger asChild>
<Button variant={'ghost'} size={'sm'}>
<Dot run={record.run}></Dot>
</Button>
</HoverCardTrigger>
<HoverCardContent className="w-[40vw]">
<PopoverContent record={record}></PopoverContent>
</HoverCardContent>
</HoverCard>
);
}

View File

@ -0,0 +1,62 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import { Button } from '@/components/ui/button';
import { Progress } from '@/components/ui/progress';
import { Separator } from '@/components/ui/separator';
import { IDocumentInfo } from '@/interfaces/database/document';
import { CircleX, Play, RefreshCw } from 'lucide-react';
import { RunningStatus } from './constant';
import { ParsingCard } from './parsing-card';
import { useHandleRunDocumentByIds } from './use-run-document';
import { isParserRunning } from './utils';
const IconMap = {
[RunningStatus.UNSTART]: <Play />,
[RunningStatus.RUNNING]: <CircleX />,
[RunningStatus.CANCEL]: <RefreshCw />,
[RunningStatus.DONE]: <RefreshCw />,
[RunningStatus.FAIL]: <RefreshCw />,
};
export function ParsingStatusCell({ record }: { record: IDocumentInfo }) {
const { run, parser_id, progress, chunk_num, id } = record;
const operationIcon = IconMap[run];
const p = Number((progress * 100).toFixed(2));
const { handleRunDocumentByIds } = useHandleRunDocumentByIds(id);
const isRunning = isParserRunning(run);
const isZeroChunk = chunk_num === 0;
const handleOperationIconClick =
(shouldDelete: boolean = false) =>
() => {
handleRunDocumentByIds(record.id, isRunning, shouldDelete);
};
return (
<section className="flex gap-2 items-center ">
<div>
<Button variant={'ghost'} size={'sm'}>
{parser_id}
</Button>
<Separator orientation="vertical" />
</div>
<ConfirmDeleteDialog
hidden={isZeroChunk}
onOk={handleOperationIconClick(true)}
onCancel={handleOperationIconClick(false)}
>
<Button
variant={'ghost'}
size={'sm'}
onClick={isZeroChunk ? handleOperationIconClick(false) : () => {}}
>
{operationIcon}
</Button>
</ConfirmDeleteDialog>
{isParserRunning(run) ? (
<Progress value={p} className="h-1" />
) : (
<ParsingCard record={record}></ParsingCard>
)}
</section>
);
}

View File

@ -16,6 +16,7 @@ import {
TooltipTrigger,
} from '@/components/ui/tooltip';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { useSetDocumentStatus } from '@/hooks/use-document-request';
import { IDocumentInfo } from '@/interfaces/database/document';
import { cn } from '@/lib/utils';
import { formatDate } from '@/utils/date';
@ -25,6 +26,7 @@ import { ArrowUpDown, MoreHorizontal, Pencil, Wrench } from 'lucide-react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useChangeDocumentParser } from './hooks';
import { ParsingStatusCell } from './parsing-status-cell';
type UseDatasetTableColumnsType = Pick<
ReturnType<typeof useChangeDocumentParser>,
@ -57,6 +59,7 @@ export function useDatasetTableColumns({
// }, [setRecord, showSetMetaModal]);
const { navigateToChunkParsedResult } = useNavigatePage();
const { setDocumentStatus } = useSetDocumentStatus();
const columns: ColumnDef<IDocumentInfo>[] = [
{
@ -94,7 +97,7 @@ export function useDatasetTableColumns({
</Button>
);
},
meta: { cellClassName: 'max-w-[20vw]' },
// meta: { cellClassName: 'max-w-[20vw]' },
cell: ({ row }) => {
const name: string = row.getValue('name');
@ -142,20 +145,34 @@ export function useDatasetTableColumns({
),
},
{
accessorKey: 'parser_id',
header: t('chunkMethod'),
accessorKey: 'status',
header: t('enabled'),
cell: ({ row }) => {
const id = row.original.id;
return (
<Switch
checked={row.getValue('status') === '1'}
onCheckedChange={(e) => {
setDocumentStatus({ status: e, documentId: id });
}}
/>
);
},
},
{
accessorKey: 'chunk_num',
header: t('chunkNumber'),
cell: ({ row }) => (
<div className="capitalize">{row.getValue('parser_id')}</div>
<div className="capitalize">{row.getValue('chunk_num')}</div>
),
},
{
accessorKey: 'run',
header: t('parsingStatus'),
cell: ({ row }) => (
<Button variant="destructive" size={'sm'}>
{row.getValue('run')}
</Button>
),
// meta: { cellClassName: 'min-w-[20vw]' },
cell: ({ row }) => {
return <ParsingStatusCell record={row.original}></ParsingStatusCell>;
},
},
{
id: 'actions',
@ -166,7 +183,6 @@ export function useDatasetTableColumns({
return (
<section className="flex gap-4 items-center">
<Switch id="airplane-mode" />
<Button
variant="icon"
size={'icon'}

View File

@ -0,0 +1,34 @@
import { useRunDocument } from '@/hooks/use-document-request';
import { useState } from 'react';
export const useHandleRunDocumentByIds = (id: string) => {
const { runDocumentByIds, loading } = useRunDocument();
const [currentId, setCurrentId] = useState<string>('');
const isLoading = loading && currentId !== '' && currentId === id;
const handleRunDocumentByIds = async (
documentId: string,
isRunning: boolean,
shouldDelete: boolean = false,
) => {
if (isLoading) {
return;
}
setCurrentId(documentId);
try {
await runDocumentByIds({
documentIds: [documentId],
run: isRunning ? 2 : 1,
shouldDelete,
});
setCurrentId('');
} catch (error) {
setCurrentId('');
}
};
return {
handleRunDocumentByIds,
loading: isLoading,
};
};

View File

@ -0,0 +1,6 @@
import { RunningStatus } from './constant';
export const isParserRunning = (text: RunningStatus) => {
const isRunning = text === RunningStatus.RUNNING;
return isRunning;
};