Compare commits

...

3 Commits

Author SHA1 Message Date
32dbed36e3 Fix: Unified terminology to "Pipeline" and optimized related component logic. #9869 (#10394)
### What problem does this PR solve?

Fix: Unified terminology to "Pipeline" and optimized related component
logic. #9869

- Added logic to clear pipeline_id when parseType changes in the chunk
method dialog.
- Fixed an issue in the Tooltip form component that prevented clicks
from triggering saves.
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-09-30 19:53:15 +08:00
7f62ab8eb3 Feat: View data flow test results #9869 (#10392)
### What problem does this PR solve?

Feat: View data flow test results #9869

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-09-30 18:55:55 +08:00
e87987785c fix(web): add data stream selection component (#10387)
### What problem does this PR solve?

fix(web): add data stream selection component

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-09-30 17:35:06 +08:00
27 changed files with 252 additions and 112 deletions

View File

@ -240,6 +240,11 @@ export function ChunkMethodDialog({
name: 'parseType',
defaultValue: pipelineId ? 2 : 1,
});
useEffect(() => {
if (parseType === 1) {
form.setValue('pipeline_id', '');
}
}, [parseType, form]);
return (
<Dialog open onOpenChange={hideModal}>
<DialogContent className="max-w-[50vw]">

View File

@ -75,7 +75,7 @@ export function DataFlowSelect(props: IProps) {
tooltip={t('dataFlowTip')}
className="text-sm text-text-primary whitespace-wrap "
>
{t('dataFlow')}
{t('dataPipeline')}
</FormLabel>
{toDataPipeline && (
<div

View File

@ -33,7 +33,12 @@ export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
export const FormTooltip = ({ tooltip }: { tooltip: React.ReactNode }) => {
return (
<Tooltip>
<TooltipTrigger tabIndex={-1}>
<TooltipTrigger
tabIndex={-1}
onClick={(e) => {
e.preventDefault(); // Prevent clicking the tooltip from triggering form save
}}
>
<Info className="size-3 ml-2" />
</TooltipTrigger>
<TooltipContent>
@ -107,7 +112,7 @@ export const AntToolTip: React.FC<AntToolTipProps> = ({
{visible && title && (
<div
className={cn(
'absolute z-50 px-2.5 py-2 text-xs text-text-primary bg-muted rounded-sm shadow-sm whitespace-wrap',
'absolute z-50 px-2.5 py-2 text-xs text-text-primary bg-muted rounded-sm shadow-sm whitespace-wrap w-max',
getPlacementClasses(),
className,
)}

View File

@ -13,7 +13,9 @@ import {
} from './logic-hooks';
import { useGetKnowledgeSearchParams } from './route-hook';
export const useFetchNextChunkList = (): ResponseGetType<{
export const useFetchNextChunkList = (
enabled = true,
): ResponseGetType<{
data: IChunk[];
total: number;
documentInfo: IKnowledgeFile;
@ -37,6 +39,7 @@ export const useFetchNextChunkList = (): ResponseGetType<{
placeholderData: (previousData: any) =>
previousData ?? { data: [], total: 0, documentInfo: {} }, // https://github.com/TanStack/query/issues/8183
gcTime: 0,
enabled,
queryFn: async () => {
const { data } = await kbService.chunk_list({
doc_id: documentId,

View File

@ -290,9 +290,9 @@ export default {
linkDataPipeline: 'Link Data Pipeline',
enableAutoGenerate: 'Enable Auto Generate',
teamPlaceholder: 'Please select a team.',
dataFlowPlaceholder: 'Please select a data flow.',
dataFlowPlaceholder: 'Please select a pipeline.',
buildItFromScratch: 'Build it from scratch',
dataFlow: 'Data Flow',
dataFlow: 'Pipeline',
parseType: 'Parse Type',
manualSetup: 'Manual Setup',
builtIn: 'Built-in',
@ -420,7 +420,7 @@ export default {
<p>In a Tag column, <b>comma</b> is used to separate tags.</p>
<i>Lines of texts that fail to follow the above rules will be ignored.</i>
`,
useRaptor: 'Use RAPTOR to enhance retrieval',
useRaptor: 'RAPTOR',
useRaptorTip:
'Enable RAPTOR for multi-hop question-answering tasks. See https://ragflow.io/docs/dev/enable_raptor for details.',
prompt: 'Prompt',
@ -466,7 +466,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
topnTags: 'Top-N Tags',
tags: 'Tags',
addTag: 'Add tag',
useGraphRag: 'Extract knowledge graph',
useGraphRag: 'Knowledge graph',
useGraphRagTip:
'Construct a knowledge graph over file chunks of the current knowledge base to enhance multi-hop question-answering involving nested logic. See https://ragflow.io/docs/dev/construct_knowledge_graph for details.',
graphRagMethod: 'Method',
@ -474,7 +474,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
General: Use prompts provided by github.com/microsoft/graphrag to extract entities and relationships`,
resolution: 'Entity resolution',
resolutionTip: `An entity deduplication switch. When enabled, the LLM will combine similar entities - e.g., '2025' and 'the year of 2025', or 'IT' and 'Information Technology' - to construct a more accurate graph`,
community: 'Community reports generation',
community: 'Community reports',
communityTip:
'In a knowledge graph, a community is a cluster of entities linked by relationships. You can have the LLM generate an abstract for each community, known as a community report. See here for more information: https://www.microsoft.com/en-us/research/blog/graphrag-improving-global-search-via-dynamic-community-selection/',
theDocumentBeingParsedCannotBeDeleted:
@ -1065,7 +1065,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
{input}
The above is the content you need to summarize.`,
createGraph: 'Create agent',
createFromTemplates: 'Create from templates',
createFromTemplates: 'Create from template',
retrieval: 'Retrieval',
generate: 'Generate',
answer: 'Interact',
@ -1586,9 +1586,12 @@ This delimiter is used to split the input text into several text pieces echo of
'Write your SQL query here. You can use variables, raw SQL, or mix both using variable syntax.',
frameworkPrompts: 'Framework',
release: 'Publish',
createFromBlank: 'Create from Blank',
createFromTemplate: 'Create from Template',
importJsonFile: 'Import json file',
createFromBlank: 'Create from blank',
createFromTemplate: 'Create from template',
importJsonFile: 'Import JSON file',
ceateAgent: 'Agent flow',
createPipeline: 'Data pipeline',
chooseAgentType: 'Choose Agent Type',
},
llmTools: {
bad_calculator: {
@ -1788,5 +1791,12 @@ Important structured information may include: names, dates, locations, events, k
summary: 'Augmented Context',
},
},
datasetOverview: {
downloadTip: 'Files being downloaded from data sources. ',
processingTip: 'Files being processed by data flows.',
totalFiles: 'Total Files',
downloading: 'Downloading',
processing: 'Processing',
},
},
};

View File

@ -1500,6 +1500,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
createFromBlank: '从空白创建',
createFromTemplate: '从模板创建',
importJsonFile: '导入 JSON 文件',
chooseAgentType: '选择智能体类型',
},
footer: {
profile: 'All rights reserved @ React',
@ -1695,5 +1696,12 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
filenameEmbeddingWeight: '文件名嵌入权重',
switchPromptMessage: '提示词将发生变化,请确认是否放弃已有提示词?',
},
datasetOverview: {
downloadTip: '正在从数据源下载文件。',
processingTip: '正在由数据流处理文件。',
totalFiles: '文件总数',
downloading: '正在下载',
processing: '正在处理',
},
},
};

View File

@ -22,7 +22,7 @@ export function CreateAgentDialog({
return (
<Dialog open onOpenChange={hideModal}>
<DialogContent className="sm:max-w-[425px]">
<DialogContent>
<DialogHeader>
<DialogTitle>{t('flow.createGraph')}</DialogTitle>
</DialogHeader>

View File

@ -25,6 +25,7 @@ type FlowTypeCardProps = {
onChange?: (value: FlowType) => void;
};
function FlowTypeCards({ value, onChange }: FlowTypeCardProps) {
const { t } = useTranslation();
const handleChange = useCallback(
(value: FlowType) => () => {
onChange?.(value);
@ -59,7 +60,11 @@ function FlowTypeCards({ value, onChange }: FlowTypeCardProps) {
) : (
<Route className="size-6" />
)}
<p>{val}</p>
<p>
{t(
`flow.${val === FlowType.Agent ? 'createAgent' : 'createPipeline'}`,
)}
</p>
</div>
{isActive && <Check />}
</CardContent>
@ -106,7 +111,11 @@ export function CreateAgentForm({
id={TagRenameId}
>
{shouldChooseAgent && (
<RAGFlowFormItem required name="type" label={t('common.type')}>
<RAGFlowFormItem
required
name="type"
label={t('flow.chooseAgentType')}
>
<FlowTypeCards></FlowTypeCards>
</RAGFlowFormItem>
)}

View File

@ -16,12 +16,7 @@ export const NameFormSchema = {
export function NameFormField() {
const { t } = useTranslation();
return (
<RAGFlowFormItem
name="name"
required
label={t('common.name')}
tooltip={t('flow.sqlStatementTip')}
>
<RAGFlowFormItem name="name" required label={t('common.name')}>
<Input placeholder={t('common.namePlaceholder')} autoComplete="off" />
</RAGFlowFormItem>
);

View File

@ -52,6 +52,7 @@ export const HandleContext = createContext<HandleContextType>(
export type LogContextType = {
messageId: string;
setMessageId: (messageId: string) => void;
setUploadedFileData: (data: Record<string, any>) => void;
};
export const LogContext = createContext<LogContextType>({} as LogContextType);

View File

@ -13,7 +13,7 @@ export function useRunDataflow(
) {
const { send } = useSendMessageBySSE(api.runCanvas);
const { id } = useParams();
const { setMessageId } = useContext(LogContext);
const { setMessageId, setUploadedFileData } = useContext(LogContext);
const { handleRun: saveGraph, loading } =
useSaveGraphBeforeOpeningDebugDrawer(showLogSheet!);
@ -32,7 +32,7 @@ export function useRunDataflow(
if (res && res?.response.status === 200 && get(res, 'data.code') === 0) {
// fetch canvas
hideRunOrChatDrawer();
setUploadedFileData(fileResponseData.file);
const msgId = get(res, 'data.data.message_id');
if (msgId) {
setMessageId(msgId);
@ -43,7 +43,14 @@ export function useRunDataflow(
message.error(get(res, 'data.message', ''));
}
},
[hideRunOrChatDrawer, id, saveGraph, send, setMessageId],
[
hideRunOrChatDrawer,
id,
saveGraph,
send,
setMessageId,
setUploadedFileData,
],
);
return { run, loading: loading };

View File

@ -26,7 +26,7 @@ import {
Settings,
Upload,
} from 'lucide-react';
import { ComponentPropsWithoutRef, useCallback } from 'react';
import { ComponentPropsWithoutRef, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import DataFlowCanvas from './canvas';
import { DropdownProvider } from './canvas/context';
@ -99,6 +99,9 @@ export default function DataFlow() {
isLogEmpty,
} = useFetchLog(logSheetVisible);
const [uploadedFileData, setUploadedFileData] =
useState<Record<string, any>>();
const handleRunAgent = useCallback(() => {
if (isParsing) {
// show log sheet
@ -184,7 +187,9 @@ export default function DataFlow() {
</DropdownMenu>
</div>
</PageHeader>
<LogContext.Provider value={{ messageId, setMessageId }}>
<LogContext.Provider
value={{ messageId, setMessageId, setUploadedFileData }}
>
<ReactFlowProvider>
<DropdownProvider>
<DataFlowCanvas
@ -211,6 +216,8 @@ export default function DataFlow() {
isLogEmpty={isLogEmpty}
logs={logs}
handleCancel={handleCancel}
messageId={messageId}
uploadedFileData={uploadedFileData}
></LogSheet>
)}
</section>

View File

@ -7,6 +7,7 @@ import {
SheetTitle,
} from '@/components/ui/sheet';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { useFetchAgent } from '@/hooks/use-agent-request';
import { IModalProps } from '@/interfaces/common';
import { cn } from '@/lib/utils';
import { PipelineResultSearchParams } from '@/pages/dataflow-result/constant';
@ -18,6 +19,7 @@ import {
} from 'lucide-react';
import { useTranslation } from 'react-i18next';
import 'react18-json-view/src/style.css';
import { useParams } from 'umi';
import {
isEndOutputEmpty,
useDownloadOutput,
@ -27,9 +29,10 @@ import { DataflowTimeline } from './dataflow-timeline';
type LogSheetProps = IModalProps<any> & {
handleCancel(): void;
uploadedFileData?: Record<string, any>;
} & Pick<
UseFetchLogReturnType,
'isCompleted' | 'isLogEmpty' | 'isParsing' | 'logs'
'isCompleted' | 'isLogEmpty' | 'isParsing' | 'logs' | 'messageId'
>;
export function LogSheet({
@ -39,11 +42,16 @@ export function LogSheet({
handleCancel,
isCompleted,
isLogEmpty,
messageId,
uploadedFileData,
}: LogSheetProps) {
const { t } = useTranslation();
const { id } = useParams();
const { data: agent } = useFetchAgent();
const { handleDownloadJson } = useDownloadOutput(logs);
const { navigateToDataflowResult } = useNavigatePage();
return (
<Sheet open onOpenChange={hideModal} modal={false}>
<SheetContent
@ -57,14 +65,16 @@ export function LogSheet({
variant={'ghost'}
disabled={!isCompleted}
onClick={navigateToDataflowResult({
id: 'cfc28d6c9c4911f088bf047c16ec874f', // 'log_id',
[PipelineResultSearchParams.AgentId]:
'cfc28d6c9c4911f088bf047c16ec874f', // 'agent_id',
[PipelineResultSearchParams.DocumentId]:
'05b0e19a9d9d11f0b674047c16ec874f', //'doc_id',
[PipelineResultSearchParams.AgentTitle]: 'full', //'title',
id: messageId, // 'log_id',
[PipelineResultSearchParams.AgentId]: id, // 'agent_id',
[PipelineResultSearchParams.DocumentId]: uploadedFileData?.id, //'doc_id',
[PipelineResultSearchParams.AgentTitle]: agent.title, //'title',
[PipelineResultSearchParams.IsReadOnly]: 'true',
[PipelineResultSearchParams.Type]: 'dataflow',
[PipelineResultSearchParams.CreatedBy]:
uploadedFileData?.created_by,
[PipelineResultSearchParams.DocumentExtension]:
uploadedFileData?.extension,
})}
>
{t('dataflow.viewResult')} <ArrowUpRight />

View File

@ -1,8 +1,9 @@
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
import { api_host } from '@/utils/api';
import api, { api_host } from '@/utils/api';
import { useSize } from 'ahooks';
import { CustomTextRenderer } from 'node_modules/react-pdf/dist/esm/shared/types';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useGetPipelineResultSearchParams } from '../../hooks';
export const useDocumentResizeObserver = () => {
const [containerWidth, setContainerWidth] = useState<number>();
@ -44,12 +45,16 @@ export const useHighlightText = (searchText: string = '') => {
return textRenderer;
};
export const useGetDocumentUrl = () => {
export const useGetDocumentUrl = (isAgent: boolean) => {
const { documentId } = useGetKnowledgeSearchParams();
const { createdBy, documentId: id } = useGetPipelineResultSearchParams();
const url = useMemo(() => {
if (isAgent) {
return api.downloadFile + `?id=${id}&created_by=${createdBy}`;
}
return `${api_host}/document/get/${documentId}`;
}, [documentId]);
}, [createdBy, documentId, id, isAgent]);
return url;
};

View File

@ -5,8 +5,8 @@ export const useParserInit = ({
initialValue,
}: {
initialValue:
| Pick<IJsonContainerProps, 'initialValue'>
| Pick<IObjContainerProps, 'initialValue'>;
| IJsonContainerProps['initialValue']
| IObjContainerProps['initialValue'];
}) => {
const [content, setContent] = useState(initialValue);

View File

@ -8,7 +8,7 @@ import { IJsonContainerProps } from './interface';
export const parserKeyMap = {
json: 'text',
chunks: 'text',
};
} as const;
export const ArrayContainer = (props: IJsonContainerProps) => {
const {
@ -33,24 +33,15 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
editDivRef,
} = useParserInit({ initialValue });
const parserKey = parserKeyMap[content.key as keyof typeof parserKeyMap];
const handleEdit = useCallback(
(e?: any, index?: number) => {
setContent((pre) => ({
...pre,
value: pre.value.map((item, i) => {
if (i === index) {
return {
...item,
[parserKeyMap[content.key]]: unescapeNewlines(e.target.innerText),
};
}
return item;
}),
}));
setActiveEditIndex(index);
},
[setContent, setActiveEditIndex],
);
const handleSave = useCallback(
(e: any) => {
const saveData = {
@ -59,7 +50,7 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
if (index === activeEditIndex) {
return {
...item,
[parserKeyMap[content.key]]: e.target.innerText,
[parserKey]: e.target.textContent || '',
};
} else {
return item;
@ -75,26 +66,28 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
useEffect(() => {
if (activeEditIndex !== undefined && editDivRef.current) {
editDivRef.current.focus();
editDivRef.current.textContent = escapeNewlines(
content.value[activeEditIndex][parserKeyMap[content.key]],
);
editDivRef.current.textContent =
content.value[activeEditIndex][parserKey];
}
}, [activeEditIndex, content]);
}, [editDivRef, activeEditIndex, content, parserKey]);
return (
<>
{content.value?.map((item, index) => {
if (item[parserKeyMap[content.key]] === '') {
if (
item[parserKeyMap[content.key as keyof typeof parserKeyMap]] === ''
) {
return null;
}
return (
<section
key={index}
className={
className={cn(
isChunck
? 'bg-bg-card my-2 p-2 rounded-lg flex gap-1 items-start'
: ''
}
: '',
activeEditIndex === index && isChunck ? 'bg-bg-title' : '',
)}
>
{isChunck && !isReadonly && (
<Checkbox
@ -113,6 +106,7 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
onBlur={handleSave}
className={cn(
'w-full bg-transparent text-text-secondary border-none focus-visible:border-none focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none p-0',
className,
)}
></div>
@ -120,7 +114,7 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
{activeEditIndex !== index && (
<div
className={cn(
'text-text-secondary overflow-auto scrollbar-auto whitespace-pre-wrap w-full',
'text-text-secondary overflow-auto scrollbar-auto w-full',
{
[styles.contentEllipsis]:
textMode === ChunkTextMode.Ellipse,
@ -134,7 +128,7 @@ export const ArrayContainer = (props: IJsonContainerProps) => {
}
}}
>
{escapeNewlines(item[parserKeyMap[content.key]])}
{item[parserKeyMap[content.key]]}
</div>
)}
</section>

View File

@ -25,22 +25,19 @@ export const ObjectContainer = (props: IObjContainerProps) => {
editDivRef,
} = useParserInit({ initialValue });
const handleEdit = useCallback(
(e?: any) => {
setContent((pre) => ({
...pre,
value: escapeNewlines(e.target.innerText),
}));
setActiveEditIndex(1);
},
[setContent, setActiveEditIndex],
);
const handleEdit = useCallback(() => {
// setContent((pre) => ({
// ...pre,
// value: escapeNewlines(e.target.innerText),
// }));
setActiveEditIndex(1);
}, [setContent, setActiveEditIndex]);
const handleSave = useCallback(
(e: any) => {
const saveData = {
...content,
value: e.target.innerText,
value: e.target.textContent,
};
onSave(saveData);
setActiveEditIndex(undefined);
@ -51,9 +48,9 @@ export const ObjectContainer = (props: IObjContainerProps) => {
useEffect(() => {
if (activeEditIndex !== undefined && editDivRef.current) {
editDivRef.current.focus();
editDivRef.current.textContent = escapeNewlines(content.value);
editDivRef.current.textContent = content.value;
}
}, [activeEditIndex, content, escapeNewlines]);
}, [activeEditIndex, content]);
return (
<>
@ -90,7 +87,7 @@ export const ObjectContainer = (props: IObjContainerProps) => {
}
}}
>
{escapeNewlines(content.value)}
{content.value}
</div>
)}
</section>

View File

@ -20,4 +20,6 @@ export enum PipelineResultSearchParams {
IsReadOnly = 'is_read_only',
AgentId = 'agent_id',
AgentTitle = 'agent_title',
CreatedBy = 'created_by', // Who uploaded the file
DocumentExtension = 'extension',
}

View File

@ -3,6 +3,7 @@ import message from '@/components/ui/message';
import { useCreateChunk, useDeleteChunk } from '@/hooks/chunk-hooks';
import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks';
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
import { useFetchMessageTrace } from '@/hooks/use-agent-request';
import { IChunk } from '@/interfaces/database/knowledge';
import kbService from '@/services/knowledge-service';
import { formatSecondsToHumanReadable } from '@/utils/date';
@ -10,7 +11,7 @@ import { buildChunkHighlights } from '@/utils/document-util';
import { useMutation, useQuery } from '@tanstack/react-query';
import { t } from 'i18next';
import { camelCase, upperFirst } from 'lodash';
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { IHighlight } from 'react-pdf-highlighter';
import { useParams, useSearchParams } from 'umi';
import { ITimelineNodeObj, TimelineNodeObj } from './components/time-line';
@ -21,11 +22,15 @@ import {
} from './constant';
import { IDslComponent, IPipelineFileLogDetail } from './interface';
export const useFetchPipelineFileLogDetail = (props?: {
export const useFetchPipelineFileLogDetail = ({
isAgent = false,
isEdit = true,
refreshCount,
}: {
isEdit?: boolean;
refreshCount?: number;
isAgent: boolean;
}) => {
const { isEdit = true, refreshCount } = props || { isEdit: true };
const { id } = useParams();
const [searchParams] = useSearchParams();
const logId = searchParams.get('id') || id;
@ -39,6 +44,7 @@ export const useFetchPipelineFileLogDetail = (props?: {
queryKey,
initialData: {} as IPipelineFileLogDetail,
gcTime: 0,
enabled: !isAgent,
queryFn: async () => {
if (isEdit) {
const { data } = await kbService.get_pipeline_detail({
@ -287,5 +293,39 @@ export const useGetPipelineResultSearchParams = () => {
currentQueryParameters.get(PipelineResultSearchParams.AgentId) || '',
agentTitle:
currentQueryParameters.get(PipelineResultSearchParams.AgentTitle) || '',
documentExtension:
currentQueryParameters.get(
PipelineResultSearchParams.DocumentExtension,
) || '',
createdBy:
currentQueryParameters.get(PipelineResultSearchParams.CreatedBy) || '',
};
};
export function useFetchPipelineResult({
agentId,
}: Pick<ReturnType<typeof useGetPipelineResultSearchParams>, 'agentId'>) {
const [searchParams] = useSearchParams();
const messageId = searchParams.get('id');
const { data, setMessageId, setISStopFetchTrace } =
useFetchMessageTrace(agentId);
useEffect(() => {
if (messageId) {
setMessageId(messageId);
setISStopFetchTrace(true);
}
}, [agentId, messageId, setISStopFetchTrace, setMessageId]);
const pipelineResult = useMemo(() => {
if (Array.isArray(data)) {
const latest = data?.at(-1);
if (latest?.component_id === 'END' && Array.isArray(latest.trace)) {
return latest.trace.at(0);
}
}
}, [data]);
return { pipelineResult };
}

View File

@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import DocumentPreview from './components/document-preview';
import {
useFetchPipelineFileLogDetail,
useFetchPipelineResult,
useGetChunkHighlights,
useGetPipelineResultSearchParams,
useHandleChunkCardClick,
@ -26,28 +27,38 @@ import {
} from '@/components/ui/breadcrumb';
import { Button } from '@/components/ui/button';
import { Modal } from '@/components/ui/modal/modal';
import { Images } from '@/constants/common';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
import { useGetDocumentUrl } from './components/document-preview/hooks';
import TimelineDataFlow from './components/time-line';
import { TimelineNodeType } from './constant';
import styles from './index.less';
import { IDslComponent } from './interface';
import { IDslComponent, IPipelineFileLogDetail } from './interface';
import ParserContainer from './parser';
const Chunk = () => {
const { isReadOnly, knowledgeId, agentId, agentTitle } =
const { isReadOnly, knowledgeId, agentId, agentTitle, documentExtension } =
useGetPipelineResultSearchParams();
const isAgent = !!agentId;
const { pipelineResult } = useFetchPipelineResult({ agentId });
const {
data: { documentInfo },
} = useFetchNextChunkList();
} = useFetchNextChunkList(!isAgent);
const { selectedChunk, handleChunkCardClick } = useHandleChunkCardClick();
const [activeStepId, setActiveStepId] = useState<number | string>(2);
const { data: dataset } = useFetchPipelineFileLogDetail();
const { data: dataset } = useFetchPipelineFileLogDetail({
isAgent,
});
const { t } = useTranslation();
const { timelineNodes } = useTimelineDataFlow(dataset);
const { timelineNodes } = useTimelineDataFlow(
agentId ? (pipelineResult as IPipelineFileLogDetail) : dataset,
);
const {
navigateToDataset,
@ -55,12 +66,17 @@ const Chunk = () => {
navigateToAgents,
navigateToDataflow,
} = useNavigatePage();
const fileUrl = useGetDocumentUrl();
let fileUrl = useGetDocumentUrl(isAgent);
const { highlights, setWidthAndHeight } =
useGetChunkHighlights(selectedChunk);
const fileType = useMemo(() => {
if (isAgent) {
return Images.some((x) => x === documentExtension)
? 'visual'
: documentExtension;
}
switch (documentInfo?.type) {
case 'doc':
return documentInfo?.name.split('.').pop() || 'doc';
@ -72,7 +88,7 @@ const Chunk = () => {
return documentInfo?.type;
}
return 'unknown';
}, [documentInfo]);
}, [documentExtension, documentInfo?.name, documentInfo?.type, isAgent]);
const {
handleReRunFunc,

View File

@ -77,4 +77,6 @@ export interface NavigateToDataflowResultProps {
[PipelineResultSearchParams.AgentTitle]?: string;
[PipelineResultSearchParams.IsReadOnly]?: string;
[PipelineResultSearchParams.Type]: string;
[PipelineResultSearchParams.CreatedBy]: string;
[PipelineResultSearchParams.DocumentExtension]: string;
}

View File

@ -1,6 +1,7 @@
import { FilterCollection } from '@/components/list-filter-bar/interface';
import SvgIcon from '@/components/svg-icon';
import { useIsDarkTheme } from '@/components/theme-provider';
import { AntToolTip } from '@/components/ui/tooltip';
import { useFetchDocumentList } from '@/hooks/use-document-request';
import { t } from 'i18next';
import { CircleQuestionMark } from 'lucide-react';
@ -17,19 +18,30 @@ interface StatCardProps {
value: number;
icon: JSX.Element;
children?: JSX.Element;
tooltip?: string;
}
interface CardFooterProcessProps {
success: number;
failed: number;
}
const StatCard: FC<StatCardProps> = ({ title, value, children, icon }) => {
const StatCard: FC<StatCardProps> = ({
title,
value,
children,
icon,
tooltip,
}) => {
return (
<div className="bg-bg-card p-4 rounded-lg border border-border flex flex-col gap-2">
<div className="flex items-center justify-between">
<h3 className="flex items-center gap-1 text-sm font-medium text-text-secondary">
{title}
<CircleQuestionMark size={12} />
{tooltip && (
<AntToolTip title={tooltip} trigger="hover">
<CircleQuestionMark size={12} />
</AntToolTip>
)}
</h3>
{icon}
</div>
@ -51,15 +63,19 @@ const CardFooterProcess: FC<CardFooterProcessProps> = ({
<div className="w-full flex justify-between gap-4 rounded-lg text-sm font-bold text-text-primary">
<div className="flex items-center justify-between rounded-md w-1/2 p-2 bg-state-success-5">
<div className="flex items-center rounded-lg gap-1">
<div className="w-2 h-2 rounded-full bg-state-success"></div>
<div>{t('knowledgeDetails.success')}</div>
<div className="w-2 h-2 rounded-full bg-state-success "></div>
<div className="font-normal text-text-secondary text-xs">
{t('knowledgeDetails.success')}
</div>
</div>
<div>{success || 0}</div>
</div>
<div className="flex items-center justify-between rounded-md w-1/2 bg-state-error-5 p-2">
<div className="flex items-center rounded-lg gap-1">
<div className="w-2 h-2 rounded-full bg-state-error"></div>
<div>{t('knowledgeDetails.failed')}</div>
<div className="font-normal text-text-secondary text-xs">
{t('knowledgeDetails.failed')}
</div>
</div>
<div>{failed || 0}</div>
</div>
@ -189,7 +205,7 @@ const FileLogsPage: FC = () => {
{/* Stats Cards */}
<div className="grid grid-cols-3 md:grid-cols-3 gap-4 mb-6">
<StatCard
title="Total Files"
title={t('datasetOverview.totalFiles')}
value={topAllData.totalFiles.value}
icon={
isDark ? (
@ -204,11 +220,13 @@ const FileLogsPage: FC = () => {
{topAllData.totalFiles.precent > 0 ? '+' : ''}
{topAllData.totalFiles.precent}%{' '}
</span>
from last week
<span className="font-normal text-text-secondary text-xs">
from last week
</span>
</div>
</StatCard>
<StatCard
title="Downloading"
title={t('datasetOverview.downloading')}
value={topAllData.downloads.value}
icon={
isDark ? (
@ -217,6 +235,7 @@ const FileLogsPage: FC = () => {
<SvgIcon name="data-flow/data-icon-bri" width={40} />
)
}
tooltip={t('datasetOverview.downloadTip')}
>
<CardFooterProcess
success={topAllData.downloads.success}
@ -224,7 +243,7 @@ const FileLogsPage: FC = () => {
/>
</StatCard>
<StatCard
title="Processing"
title={t('datasetOverview.processing')}
value={topAllData.processing.value}
icon={
isDark ? (
@ -233,6 +252,7 @@ const FileLogsPage: FC = () => {
<SvgIcon name="data-flow/processing-icon-bri" width={40} />
)
}
tooltip={t('datasetOverview.processingTip')}
>
<CardFooterProcess
success={topAllData.processing.success}

View File

@ -42,7 +42,7 @@ export function ChunkMethodItem(props: IProps) {
'w-1/4 whitespace-pre-wrap': line === 1,
})}
>
{t('chunkMethod')}
{t('dataPipeline')}
</FormLabel>
<div className={line === 1 ? 'w-3/4 ' : 'w-full'}>
<FormControl>

View File

@ -99,7 +99,7 @@ export function ParsingStatusCell({
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={handleShowChangeParserModal}>
{t('knowledgeDetails.chunkMethod')}
{t('knowledgeDetails.dataPipeline')}
</DropdownMenuItem>
<DropdownMenuItem onClick={handleShowSetMetaModal}>
{t('knowledgeDetails.setMetaData')}

View File

@ -1,3 +1,4 @@
import { IconFontFill } from '@/components/icon-font';
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { Button } from '@/components/ui/button';
import { useSecondPathName } from '@/hooks/route-hook';
@ -9,13 +10,7 @@ import { cn, formatBytes } from '@/lib/utils';
import { Routes } from '@/routes';
import { formatPureDate } from '@/utils/date';
import { isEmpty } from 'lodash';
import {
Banknote,
DatabaseZap,
FileSearch2,
FolderOpen,
GitGraph,
} from 'lucide-react';
import { Banknote, DatabaseZap, FileSearch2, FolderOpen } from 'lucide-react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useHandleMenuClick } from './hooks';
@ -35,29 +30,29 @@ export function SideBar({ refreshCount }: PropType) {
const items = useMemo(() => {
const list = [
{
icon: DatabaseZap,
icon: <DatabaseZap className="size-4" />,
label: t(`knowledgeDetails.overview`),
key: Routes.DataSetOverview,
},
{
icon: FolderOpen,
icon: <FolderOpen className="size-4" />,
label: t(`knowledgeDetails.subbarFiles`),
key: Routes.DatasetBase,
},
{
icon: FileSearch2,
icon: <FileSearch2 className="size-4" />,
label: t(`knowledgeDetails.testing`),
key: Routes.DatasetTesting,
},
{
icon: Banknote,
icon: <Banknote className="size-4" />,
label: t(`knowledgeDetails.configuration`),
key: Routes.DataSetSetting,
},
];
if (!isEmpty(routerData?.graph)) {
list.push({
icon: GitGraph,
icon: <IconFontFill name="knowledgegraph" className="size-4" />,
label: t(`knowledgeDetails.knowledgeGraph`),
key: Routes.KnowledgeGraph,
});
@ -105,7 +100,7 @@ export function SideBar({ refreshCount }: PropType) {
)}
onClick={handleMenuClick(item.key)}
>
<item.icon className="size-4" />
{item.icon}
<span>{item.label}</span>
</Button>
);

View File

@ -1,3 +1,4 @@
import { DataFlowSelect } from '@/components/data-pipeline-select';
import { ButtonLoading } from '@/components/ui/button';
import {
Dialog,
@ -18,10 +19,10 @@ import { Input } from '@/components/ui/input';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { IModalProps } from '@/interfaces/common';
import { zodResolver } from '@hookform/resolvers/zod';
import { useEffect } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import {
ChunkMethodItem,
EmbeddingModelItem,
@ -94,6 +95,13 @@ export function InputForm({ onOk }: IModalProps<any>) {
control: form.control,
name: 'parseType',
});
useEffect(() => {
console.log('parseType', parseType);
if (parseType === 1) {
form.setValue('pipeline_id', '');
}
}, [parseType, form]);
const { navigateToAgents } = useNavigatePage();
return (

View File

@ -177,6 +177,7 @@ export default {
`${ExternalApi}${api_host}/agentbots/${canvasId}/inputs`,
prompt: `${api_host}/canvas/prompts`,
cancelDataflow: (id: string) => `${api_host}/canvas/cancel/${id}`,
downloadFile: `${api_host}/canvas/download`,
// mcp server
listMcpServer: `${api_host}/mcp_server/list`,