diff --git a/web/src/components/originui/timeline.tsx b/web/src/components/originui/timeline.tsx index 57694ee3b..9ed7da005 100644 --- a/web/src/components/originui/timeline.tsx +++ b/web/src/components/originui/timeline.tsx @@ -1,7 +1,7 @@ 'use client'; import { cn } from '@/lib/utils'; -import { parseColorToRGBA } from '@/utils/common-util'; +import { parseColorToRGB } from '@/utils/common-util'; import { Slot } from '@radix-ui/react-slot'; import * as React from 'react'; @@ -251,7 +251,7 @@ const CustomTimeline = ({ }: CustomTimelineProps) => { const [internalActiveStep, setInternalActiveStep] = React.useState(defaultValue); - const _lineColor = `rgb(${parseColorToRGBA(lineColor)})`; + const _lineColor = `rgb(${parseColorToRGB(lineColor)})`; console.log(lineColor, _lineColor); const currentActiveStep = activeStep ?? internalActiveStep; @@ -261,7 +261,7 @@ const CustomTimeline = ({ } onStepChange?.(step, id); }; - const [r, g, b] = parseColorToRGBA(indicatorColor); + const [r, g, b] = parseColorToRGB(indicatorColor); return ( diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 0e73bf246..c5ad7c3ae 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -1628,6 +1628,24 @@ This delimiter is used to split the input text into several text pieces echo of parseSummaryTip: 'Parser:deepdoc', rerunFromCurrentStep: 'Rerun From Current Step', rerunFromCurrentStepTip: 'Changes detected. Click to re-run.', + confirmRerun: 'Confirm Rerun Process', + confirmRerunModalContent: ` +

+ You are about to rerun the process starting from the {{step}} step. +

+

This will:

+ `, + changeStepModalTitle: 'Step Switch Warning', + changeStepModalContent: ` +

You are currently editing the results of this stage.

+

If you switch to a later stage, your changes will be lost.

+

To keep them, please click Rerun to re-run the current stage.

`, + changeStepModalConfirmText: 'Switch Anyway', + changeStepModalCancelText: 'Cancel', }, dataflow: { parser: 'Parser', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index de3b11d9c..7bcd21b84 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -1536,6 +1536,24 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 parseSummaryTip: '解析器: deepdoc', rerunFromCurrentStep: '从当前步骤重新运行', rerunFromCurrentStepTip: '已修改,点击重新运行。', + confirmRerun: '确认重新运行流程', + confirmRerunModalContent: ` +

+ 您即将从 {{step}} 步骤开始重新运行该过程 +

+

这将:

+ `, + changeStepModalTitle: '切换步骤警告', + changeStepModalContent: ` +

您目前正在编辑此阶段的结果。

+

如果您切换到后续阶段,您的更改将会丢失。

+

要保留这些更改,请点击“重新运行”以重新运行当前阶段。

`, + changeStepModalConfirmText: '继续切换', + changeStepModalCancelText: '取消', }, dataflow: { parser: '解析器', diff --git a/web/src/pages/dataflow-result/chunker.tsx b/web/src/pages/dataflow-result/chunker.tsx index ff869809d..70f606d2d 100644 --- a/web/src/pages/dataflow-result/chunker.tsx +++ b/web/src/pages/dataflow-result/chunker.tsx @@ -1,3 +1,4 @@ +import { TimelineNode } from '@/components/originui/timeline'; import message from '@/components/ui/message'; import { RAGFlowPagination, @@ -23,9 +24,16 @@ import { useUpdateChunk, } from './hooks'; import styles from './index.less'; -const ChunkerContainer = () => { + +interface IProps { + isChange: boolean; + setIsChange: (isChange: boolean) => void; + step?: TimelineNode; +} +const ChunkerContainer = (props: IProps) => { + const { isChange, setIsChange, step } = props; const [selectedChunkIds, setSelectedChunkIds] = useState([]); - const [isChange, setIsChange] = useState(false); + const { t } = useTranslation(); const { data: { documentInfo, data = [], total }, @@ -135,17 +143,21 @@ const ChunkerContainer = () => { setIsChange(true); onChunkUpdatingOk(e); }; + + const handleReRunFunc = () => { + setIsChange(false); + }; return ( - <> +
{isChange && (
- +
)}
@@ -176,7 +188,7 @@ const ChunkerContainer = () => { selectedChunkIds={selectedChunkIds} />
-
+
{ parserId={documentInfo.parser_id} /> )} - +
); }; diff --git a/web/src/pages/dataflow-result/components/rerun-button/index.tsx b/web/src/pages/dataflow-result/components/rerun-button/index.tsx index 72edc3795..7ff16953b 100644 --- a/web/src/pages/dataflow-result/components/rerun-button/index.tsx +++ b/web/src/pages/dataflow-result/components/rerun-button/index.tsx @@ -1,16 +1,45 @@ +import { TimelineNode } from '@/components/originui/timeline'; import SvgIcon from '@/components/svg-icon'; import { Button } from '@/components/ui/button'; +import { Modal } from '@/components/ui/modal/modal'; import { CircleAlert } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useRerunDataflow } from '../../hooks'; interface RerunButtonProps { className?: string; + step?: TimelineNode; + onRerun?: () => void; } const RerunButton = (props: RerunButtonProps) => { + const { className, step, onRerun } = props; const { t } = useTranslation(); const { loading } = useRerunDataflow(); const clickFunc = () => { console.log('click rerun button'); + Modal.show({ + visible: true, + className: '!w-[560px]', + title: t('dataflowParser.confirmRerun'), + children: ( +
+ ), + onVisibleChange: () => { + Modal.hide(); + }, + onOk: () => { + onRerun?.(); + Modal.hide(); + }, + onCancel: () => { + Modal.hide(); + }, + }); }; return (
diff --git a/web/src/pages/dataflow-result/components/time-line/index.tsx b/web/src/pages/dataflow-result/components/time-line/index.tsx index 75508a05f..cd1f260b7 100644 --- a/web/src/pages/dataflow-result/components/time-line/index.tsx +++ b/web/src/pages/dataflow-result/components/time-line/index.tsx @@ -7,40 +7,61 @@ import { PlayIcon, } from 'lucide-react'; import { useMemo } from 'react'; -export const TimelineNodeObj = { - begin: { +export enum TimelineNodeType { + begin = 'begin', + parser = 'parser', + chunk = 'chunk', + indexer = 'indexer', + complete = 'complete', + end = 'end', +} +export const TimelineNodeArr = [ + { id: 1, title: 'Begin', icon: , clickable: false, + type: TimelineNodeType.begin, }, - parser: { id: 2, title: 'Parser', icon: }, - chunker: { id: 3, title: 'Chunker', icon: }, - indexer: { + { + id: 2, + title: 'Parser', + icon: , + type: TimelineNodeType.parser, + }, + { + id: 3, + title: 'Chunker', + icon: , + type: TimelineNodeType.chunk, + }, + { id: 4, title: 'Indexer', icon: , clickable: false, + type: TimelineNodeType.indexer, }, - complete: { + { id: 5, title: 'Complete', icon: , clickable: false, + type: TimelineNodeType.complete, }, -}; +]; export interface TimelineDataFlowProps { activeId: number | string; - activeFunc: (id: number | string) => void; + activeFunc: (id: number | string, step: TimelineNode) => void; } const TimelineDataFlow = ({ activeFunc, activeId }: TimelineDataFlowProps) => { // const [activeStep, setActiveStep] = useState(2); const timelineNodes: TimelineNode[] = useMemo(() => { const nodes: TimelineNode[] = []; - Object.keys(TimelineNodeObj).forEach((key) => { + TimelineNodeArr.forEach((node) => { nodes.push({ - ...TimelineNodeObj[key as keyof typeof TimelineNodeObj], + ...node, className: 'w-32', completed: false, }); @@ -54,7 +75,10 @@ const TimelineDataFlow = ({ activeFunc, activeId }: TimelineDataFlowProps) => { }, [activeId, timelineNodes]); const handleStepChange = (step: number, id: string | number) => { // setActiveStep(step); - activeFunc?.(id); + activeFunc?.( + id, + timelineNodes.find((node) => node.id === activeStep) as TimelineNode, + ); console.log(step, id); }; diff --git a/web/src/pages/dataflow-result/hooks.ts b/web/src/pages/dataflow-result/hooks.ts index be2dc1c01..5dbd28619 100644 --- a/web/src/pages/dataflow-result/hooks.ts +++ b/web/src/pages/dataflow-result/hooks.ts @@ -140,8 +140,12 @@ export const useFetchParserList = () => { export const useRerunDataflow = () => { const [loading, setLoading] = useState(false); + const [isChange, setIsChange] = useState(false); return { loading, + setLoading, + isChange, + setIsChange, }; }; diff --git a/web/src/pages/dataflow-result/index.tsx b/web/src/pages/dataflow-result/index.tsx index 96e7d549b..86021bfbd 100644 --- a/web/src/pages/dataflow-result/index.tsx +++ b/web/src/pages/dataflow-result/index.tsx @@ -2,11 +2,17 @@ import { useFetchNextChunkList } from '@/hooks/use-chunk-request'; import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import DocumentPreview from './components/document-preview'; -import { useGetChunkHighlights, useHandleChunkCardClick } from './hooks'; +import { + useGetChunkHighlights, + useHandleChunkCardClick, + useRerunDataflow, +} from './hooks'; import DocumentHeader from './components/document-preview/document-header'; +import { TimelineNode } from '@/components/originui/timeline'; import { PageHeader } from '@/components/page-header'; +import Spotlight from '@/components/spotlight'; import { Breadcrumb, BreadcrumbItem, @@ -15,6 +21,8 @@ import { BreadcrumbPage, BreadcrumbSeparator, } from '@/components/ui/breadcrumb'; +import { Button } from '@/components/ui/button'; +import { Modal } from '@/components/ui/modal/modal'; import { QueryStringMap, useNavigatePage, @@ -23,7 +31,10 @@ import { useGetKnowledgeSearchParams } from '@/hooks/route-hook'; import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request'; import { ChunkerContainer } from './chunker'; import { useGetDocumentUrl } from './components/document-preview/hooks'; -import TimelineDataFlow, { TimelineNodeObj } from './components/time-line'; +import TimelineDataFlow, { + TimelineNodeArr, + TimelineNodeType, +} from './components/time-line'; import styles from './index.less'; import ParserContainer from './parser'; @@ -34,7 +45,7 @@ const Chunk = () => { const { selectedChunkId } = useHandleChunkCardClick(); const [activeStepId, setActiveStepId] = useState(0); const { data: dataset } = useFetchKnowledgeBaseConfiguration(); - + const { isChange, setIsChange } = useRerunDataflow(); const { t } = useTranslation(); const { navigateToDataset, getQueryString, navigateToDatasetList } = @@ -58,10 +69,57 @@ const Chunk = () => { return 'unknown'; }, [documentInfo]); - const handleStepChange = (id: number | string) => { - setActiveStepId(id); + const handleStepChange = (id: number | string, step: TimelineNode) => { + console.log(id, step); + if (isChange) { + Modal.show({ + visible: true, + className: '!w-[560px]', + title: t('dataflowParser.changeStepModalTitle'), + children: ( +
+ ), + onVisibleChange: () => { + Modal.hide(); + }, + footer: ( +
+ + +
+ ), + }); + } else { + setActiveStepId(id); + } }; + const { type } = useGetKnowledgeSearchParams(); + const currentTimeNode: TimelineNode = useMemo(() => { + return ( + TimelineNodeArr.find((node) => node.id === activeStepId) || + ({} as TimelineNode) + ); + }, [activeStepId]); return ( <> @@ -114,9 +172,23 @@ const Chunk = () => {
- {(activeStepId === TimelineNodeObj.chunker.id || - type === 'chunk') && } - {activeStepId === TimelineNodeObj.parser.id && } +
+ {currentTimeNode?.type === TimelineNodeType.chunk && ( + + )} + {currentTimeNode?.type === TimelineNodeType.parser && ( + + )} + +
diff --git a/web/src/pages/dataflow-result/parser.tsx b/web/src/pages/dataflow-result/parser.tsx index d6d278a95..3f72a843c 100644 --- a/web/src/pages/dataflow-result/parser.tsx +++ b/web/src/pages/dataflow-result/parser.tsx @@ -1,3 +1,4 @@ +import { TimelineNode } from '@/components/originui/timeline'; import Spotlight from '@/components/spotlight'; import { Spin } from '@/components/ui/spin'; import classNames from 'classnames'; @@ -6,13 +7,18 @@ import { useTranslation } from 'react-i18next'; import FormatPreserveEditor from './components/parse-editer'; import RerunButton from './components/rerun-button'; import { useFetchParserList, useFetchPaserText } from './hooks'; -const ParserContainer = () => { +interface IProps { + isChange: boolean; + setIsChange: (isChange: boolean) => void; + step?: TimelineNode; +} +const ParserContainer = (props: IProps) => { + const { isChange, setIsChange, step } = props; const { data: initialValue, rerun: onSave } = useFetchPaserText(); const { t } = useTranslation(); const { loading } = useFetchParserList(); const [initialText, setInitialText] = useState(initialValue); - const [isChange, setIsChange] = useState(false); const handleSave = (newContent: string) => { console.log('保存内容:', newContent); if (newContent !== initialText) { @@ -23,14 +29,17 @@ const ParserContainer = () => { } // Here, the API is called to send newContent to the backend }; + const handleReRunFunc = () => { + setIsChange(false); + }; return ( <> {isChange && (
- +
)} -
+
diff --git a/web/src/pages/dataset/dataset-overview/hook.ts b/web/src/pages/dataset/dataset-overview/hook.ts new file mode 100644 index 000000000..218ac970f --- /dev/null +++ b/web/src/pages/dataset/dataset-overview/hook.ts @@ -0,0 +1,27 @@ +import kbService from '@/services/knowledge-service'; +import { useQuery } from '@tanstack/react-query'; +import { useParams, useSearchParams } from 'umi'; + +export interface IOverviewTital { + cancelled: number; + failed: number; + finished: number; + processing: number; +} +const useFetchOverviewTital = () => { + const [searchParams] = useSearchParams(); + const { id } = useParams(); + const knowledgeBaseId = searchParams.get('id') || id; + const { data } = useQuery({ + queryKey: ['overviewTital'], + queryFn: async () => { + const { data: res = {} } = await kbService.getKnowledgeBasicInfo({ + kb_id: knowledgeBaseId, + }); + return res.data || []; + }, + }); + return { data }; +}; + +export { useFetchOverviewTital }; diff --git a/web/src/pages/dataset/dataset-overview/index.tsx b/web/src/pages/dataset/dataset-overview/index.tsx index d67b60667..13184061e 100644 --- a/web/src/pages/dataset/dataset-overview/index.tsx +++ b/web/src/pages/dataset/dataset-overview/index.tsx @@ -1,11 +1,12 @@ import SvgIcon from '@/components/svg-icon'; import { useIsDarkTheme } from '@/components/theme-provider'; -import { toFixed } from '@/utils/common-util'; +import { parseColorToRGBA } from '@/utils/common-util'; import { CircleQuestionMark } from 'lucide-react'; import { FC, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { LogTabs } from './dataset-common'; import { DatasetFilter } from './dataset-filter'; +import { useFetchOverviewTital } from './hook'; import FileLogsTable from './overview-table'; interface StatCardProps { @@ -34,46 +35,36 @@ const StatCard: FC = ({ title, value, children, icon }) => { }; interface CardFooterProcessProps { - total: number; success: number; failed: number; } const CardFooterProcess: FC = ({ - total, success = 0, failed = 0, }) => { const { t } = useTranslation(); - const successPrecentage = total ? (success / total) * 100 : 0; - const failedPrecentage = total ? (failed / total) * 100 : 0; - const completedPercentage = total ? ((success + failed) / total) * 100 : 0; return (
-
-
-
- {success || 0} - {t('knowledgeDetails.success')} -
-
- {failed || 0} - {t('knowledgeDetails.failed')} -
-
-
- {toFixed(completedPercentage) as string}% - {t('knowledgeDetails.completed')} -
-
-
+
-
+ className="flex items-center justify-between rounded-md w-1/2 p-2" + style={{ + backgroundColor: `${parseColorToRGBA('var(--state-success)', 0.05)}`, + }} + > +
+
+
{t('knowledgeDetails.success')}
+
+
{success || 0}
+
+
+
+
+
{t('knowledgeDetails.failed')}
+
+
{failed || 0}
+
); @@ -99,6 +90,10 @@ const FileLogsPage: FC = () => { failed: 2, }, }; + + const { data: topData } = useFetchOverviewTital(); + console.log('topData --> ', topData); + const mockData = useMemo(() => { if (active === LogTabs.FILE_LOGS) { return Array(30) @@ -133,7 +128,8 @@ const FileLogsPage: FC = () => { task: i === 0 ? 'chunck' : 'Parser', pipeline: i === 0 ? 'data demo for...' : i === 1 ? 'test' : 'kiki’s demo', - status: + status: i === 0 ? 3 : i === 1 ? 4 : i === 2 ? 1 : 0, + statusName: i === 0 ? 'Success' : i === 1 @@ -147,7 +143,7 @@ const FileLogsPage: FC = () => { const pagination = { current: 1, - pageSize: 30, + pageSize: 10, total: 100, }; @@ -194,7 +190,6 @@ const FileLogsPage: FC = () => { } > @@ -211,7 +206,6 @@ const FileLogsPage: FC = () => { } > diff --git a/web/src/pages/dataset/dataset-overview/overview-table.tsx b/web/src/pages/dataset/dataset-overview/overview-table.tsx index 6b9083c4c..04585c403 100644 --- a/web/src/pages/dataset/dataset-overview/overview-table.tsx +++ b/web/src/pages/dataset/dataset-overview/overview-table.tsx @@ -337,7 +337,7 @@ const FileLogsTable: FC = ({ : 0, }); return ( -
+
{table.getHeaderGroups().map((headerGroup) => ( diff --git a/web/src/services/knowledge-service.ts b/web/src/services/knowledge-service.ts index 8d0bb8d30..647af4da2 100644 --- a/web/src/services/knowledge-service.ts +++ b/web/src/services/knowledge-service.ts @@ -39,6 +39,7 @@ const { setMeta, getMeta, retrievalTestShare, + getKnowledgeBasicInfo, } = api; const methods = { @@ -169,6 +170,10 @@ const methods = { url: retrievalTestShare, method: 'post', }, + getKnowledgeBasicInfo: { + url: getKnowledgeBasicInfo, + method: 'get', + }, }; const kbService = registerServer(methods, request); diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts index c5e8fbefc..e6f0afeae 100644 --- a/web/src/utils/api.ts +++ b/web/src/utils/api.ts @@ -45,6 +45,7 @@ export default { getKnowledgeGraph: (knowledgeId: string) => `${api_host}/kb/${knowledgeId}/knowledge_graph`, getMeta: `${api_host}/kb/get_meta`, + getKnowledgeBasicInfo: `${api_host}/kb/basic_info`, // tags listTag: (knowledgeId: string) => `${api_host}/kb/${knowledgeId}/tags`, @@ -192,7 +193,6 @@ export default { retrievalTestShare: `${ExternalApi}${api_host}/searchbots/retrieval_test`, // data pipeline - fetchDataflow: (id: string) => `${api_host}/dataflow/get/${id}`, setDataflow: `${api_host}/dataflow/set`, removeDataflow: `${api_host}/dataflow/rm`, diff --git a/web/src/utils/common-util.ts b/web/src/utils/common-util.ts index 5e770eff0..d74ea5ae9 100644 --- a/web/src/utils/common-util.ts +++ b/web/src/utils/common-util.ts @@ -152,8 +152,11 @@ function getCSSVariableValue(variableName: string): string { return value; } -// Parse the color and convert to RGBA -export function parseColorToRGBA(color: string): [number, number, number] { +/**Parse the color and convert to RGB, + * #fff -> [255, 255, 255] + * var(--text-primary) -> [var(--text-primary-r), var(--text-primary-g), var(--text-primary-b)] + * */ +export function parseColorToRGB(color: string): [number, number, number] { // Handling CSS variables (e.g. var(--accent-primary)) let colorStr = color; if (colorStr.startsWith('var(')) { @@ -203,3 +206,14 @@ export function parseColorToRGBA(color: string): [number, number, number] { console.error(`Unsupported colorStr format: ${colorStr}`); return [0, 0, 0]; } + +/** + * + * @param color eg: #fff, or var(--color-text-primary) + * @param opcity 0~1 + * @return rgba(r,g,b,opcity) + */ +export function parseColorToRGBA(color: string, opcity = 1): string { + const [r, g, b] = parseColorToRGB(color); + return `rgba(${r},${g},${b},${opcity})`; +}