mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-07 02:55:08 +08:00
Compare commits
4 Commits
5715ca6b74
...
a6039cf563
| Author | SHA1 | Date | |
|---|---|---|---|
| a6039cf563 | |||
| 8be7380b79 | |||
| afb8a84f7b | |||
| 6bf0cda16f |
@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { TimelineNodeType } from '@/pages/dataflow-result/constant';
|
||||||
import { parseColorToRGB } from '@/utils/common-util';
|
import { parseColorToRGB } from '@/utils/common-util';
|
||||||
import { Slot } from '@radix-ui/react-slot';
|
import { Slot } from '@radix-ui/react-slot';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
@ -220,6 +221,8 @@ interface TimelineNode
|
|||||||
completed?: boolean;
|
completed?: boolean;
|
||||||
clickable?: boolean;
|
clickable?: boolean;
|
||||||
activeStyle?: TimelineIndicatorNodeProps;
|
activeStyle?: TimelineIndicatorNodeProps;
|
||||||
|
detail?: any;
|
||||||
|
type?: TimelineNodeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CustomTimelineProps extends React.HTMLAttributes<HTMLDivElement> {
|
interface CustomTimelineProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
@ -252,7 +255,6 @@ const CustomTimeline = ({
|
|||||||
const [internalActiveStep, setInternalActiveStep] =
|
const [internalActiveStep, setInternalActiveStep] =
|
||||||
React.useState(defaultValue);
|
React.useState(defaultValue);
|
||||||
const _lineColor = `rgb(${parseColorToRGB(lineColor)})`;
|
const _lineColor = `rgb(${parseColorToRGB(lineColor)})`;
|
||||||
console.log(lineColor, _lineColor);
|
|
||||||
const currentActiveStep = activeStep ?? internalActiveStep;
|
const currentActiveStep = activeStep ?? internalActiveStep;
|
||||||
|
|
||||||
const handleStepChange = (step: number, id: string | number) => {
|
const handleStepChange = (step: number, id: string | number) => {
|
||||||
@ -284,8 +286,6 @@ const CustomTimeline = ({
|
|||||||
typeof _nodeSizeTemp === 'number'
|
typeof _nodeSizeTemp === 'number'
|
||||||
? `${_nodeSizeTemp}px`
|
? `${_nodeSizeTemp}px`
|
||||||
: _nodeSizeTemp;
|
: _nodeSizeTemp;
|
||||||
console.log('icon-size', nodeSize, node.nodeSize, _nodeSize);
|
|
||||||
// const activeStyle = _activeStyle || {};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TimelineItem
|
<TimelineItem
|
||||||
@ -372,11 +372,10 @@ const CustomTimeline = ({
|
|||||||
)}
|
)}
|
||||||
</TimelineIndicator>
|
</TimelineIndicator>
|
||||||
|
|
||||||
<TimelineHeader>
|
<TimelineHeader className="transform -translate-x-[40%] text-center">
|
||||||
{node.date && <TimelineDate>{node.date}</TimelineDate>}
|
|
||||||
<TimelineTitle
|
<TimelineTitle
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-sm font-medium',
|
'text-sm font-medium -ml-1',
|
||||||
isActive && _activeStyle.textColor
|
isActive && _activeStyle.textColor
|
||||||
? `text-${_activeStyle.textColor}`
|
? `text-${_activeStyle.textColor}`
|
||||||
: '',
|
: '',
|
||||||
@ -387,6 +386,7 @@ const CustomTimeline = ({
|
|||||||
>
|
>
|
||||||
{node.title}
|
{node.title}
|
||||||
</TimelineTitle>
|
</TimelineTitle>
|
||||||
|
{node.date && <TimelineDate>{node.date}</TimelineDate>}
|
||||||
</TimelineHeader>
|
</TimelineHeader>
|
||||||
{node.content && <TimelineContent>{node.content}</TimelineContent>}
|
{node.content && <TimelineContent>{node.content}</TimelineContent>}
|
||||||
</TimelineItem>
|
</TimelineItem>
|
||||||
|
|||||||
@ -28,7 +28,7 @@ const DualRangeSlider = React.forwardRef<
|
|||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
|
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-border-button">
|
||||||
<SliderPrimitive.Range className="absolute h-full bg-accent-primary" />
|
<SliderPrimitive.Range className="absolute h-full bg-accent-primary" />
|
||||||
</SliderPrimitive.Track>
|
</SliderPrimitive.Track>
|
||||||
{initialValue.map((value, index) => (
|
{initialValue.map((value, index) => (
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export interface ModalProps {
|
|||||||
export interface ModalType extends FC<ModalProps> {
|
export interface ModalType extends FC<ModalProps> {
|
||||||
show: typeof modalIns.show;
|
show: typeof modalIns.show;
|
||||||
hide: typeof modalIns.hide;
|
hide: typeof modalIns.hide;
|
||||||
|
destroy: typeof modalIns.destroy;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Modal: ModalType = ({
|
const Modal: ModalType = ({
|
||||||
@ -75,13 +76,13 @@ const Modal: ModalType = ({
|
|||||||
|
|
||||||
const handleCancel = useCallback(() => {
|
const handleCancel = useCallback(() => {
|
||||||
onOpenChange?.(false);
|
onOpenChange?.(false);
|
||||||
// onCancel?.();
|
onCancel?.();
|
||||||
}, [onOpenChange]);
|
}, [onCancel, onOpenChange]);
|
||||||
|
|
||||||
const handleOk = useCallback(() => {
|
const handleOk = useCallback(() => {
|
||||||
onOpenChange?.(true);
|
onOpenChange?.(true);
|
||||||
// onOk?.();
|
onOk?.();
|
||||||
}, [onOpenChange]);
|
}, [onOk, onOpenChange]);
|
||||||
const handleChange = (open: boolean) => {
|
const handleChange = (open: boolean) => {
|
||||||
onOpenChange?.(open);
|
onOpenChange?.(open);
|
||||||
console.log('open', open, onOpenChange);
|
console.log('open', open, onOpenChange);
|
||||||
@ -208,5 +209,6 @@ Modal.show = modalIns
|
|||||||
return modalIns.show;
|
return modalIns.show;
|
||||||
};
|
};
|
||||||
Modal.hide = modalIns.hide;
|
Modal.hide = modalIns.hide;
|
||||||
|
Modal.destroy = modalIns.destroy;
|
||||||
|
|
||||||
export { Modal };
|
export { Modal };
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
import { setInitialChatVariableEnabledFieldValue } from '@/utils/chat';
|
||||||
|
import { ChatVariableEnabledField, variableEnabledFieldMap } from './chat';
|
||||||
|
|
||||||
export enum ProgrammingLanguage {
|
export enum ProgrammingLanguage {
|
||||||
Python = 'python',
|
Python = 'python',
|
||||||
Javascript = 'javascript',
|
Javascript = 'javascript',
|
||||||
@ -26,3 +29,21 @@ export enum AgentGlobals {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AgentGlobalsSysQueryWithBrace = `{${AgentGlobals.SysQuery}}`;
|
export const AgentGlobalsSysQueryWithBrace = `{${AgentGlobals.SysQuery}}`;
|
||||||
|
|
||||||
|
export const variableCheckBoxFieldMap = Object.keys(
|
||||||
|
variableEnabledFieldMap,
|
||||||
|
).reduce<Record<string, boolean>>((pre, cur) => {
|
||||||
|
pre[cur] = setInitialChatVariableEnabledFieldValue(
|
||||||
|
cur as ChatVariableEnabledField,
|
||||||
|
);
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
export const initialLlmBaseValues = {
|
||||||
|
...variableCheckBoxFieldMap,
|
||||||
|
temperature: 0.1,
|
||||||
|
top_p: 0.3,
|
||||||
|
frequency_penalty: 0.7,
|
||||||
|
presence_penalty: 0.4,
|
||||||
|
max_tokens: 256,
|
||||||
|
};
|
||||||
|
|||||||
@ -133,10 +133,10 @@ export const useNavigatePage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const navigateToDataflowResult = useCallback(
|
const navigateToDataflowResult = useCallback(
|
||||||
(id: string, knowledgeId?: string) => () => {
|
(id: string, knowledgeId: string, doc_id?: string) => () => {
|
||||||
navigate(
|
navigate(
|
||||||
// `${Routes.ParsedResult}/${id}?${QueryStringMap.KnowledgeId}=${knowledgeId}`,
|
// `${Routes.ParsedResult}/${id}?${QueryStringMap.KnowledgeId}=${knowledgeId}`,
|
||||||
`${Routes.DataflowResult}?id=${id}&type=dataflow`,
|
`${Routes.DataflowResult}?id=${id}&doc_id=${doc_id}&${QueryStringMap.KnowledgeId}=${knowledgeId}&type=dataflow`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[navigate],
|
[navigate],
|
||||||
|
|||||||
@ -52,6 +52,7 @@ export const enum AgentApiAction {
|
|||||||
FetchExternalAgentInputs = 'fetchExternalAgentInputs',
|
FetchExternalAgentInputs = 'fetchExternalAgentInputs',
|
||||||
SetAgentSetting = 'setAgentSetting',
|
SetAgentSetting = 'setAgentSetting',
|
||||||
FetchPrompt = 'fetchPrompt',
|
FetchPrompt = 'fetchPrompt',
|
||||||
|
CancelDataflow = 'cancelDataflow',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EmptyDsl = {
|
export const EmptyDsl = {
|
||||||
@ -387,7 +388,7 @@ export const useUploadCanvasFileWithProgress = (
|
|||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
onError(file, error as Error);
|
onError(file, error as Error);
|
||||||
});
|
});
|
||||||
message.error(error?.message);
|
message.error((error as Error)?.message || 'Upload failed');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -425,7 +426,7 @@ export const useFetchMessageTrace = (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return { data, loading, refetch, setMessageId };
|
return { data, loading, refetch, setMessageId, messageId };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTestDbConnect = () => {
|
export const useTestDbConnect = () => {
|
||||||
@ -571,7 +572,6 @@ export const useFetchAgentLog = (searchParams: IAgentLogsRequest) => {
|
|||||||
initialData: {} as IAgentLogsResponse,
|
initialData: {} as IAgentLogsResponse,
|
||||||
gcTime: 0,
|
gcTime: 0,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
console.log('useFetchAgentLog', searchParams);
|
|
||||||
const { data } = await fetchAgentLogsByCanvasId(id as string, {
|
const { data } = await fetchAgentLogsByCanvasId(id as string, {
|
||||||
...searchParams,
|
...searchParams,
|
||||||
});
|
});
|
||||||
@ -678,3 +678,24 @@ export const useFetchAgentList = ({
|
|||||||
|
|
||||||
return { data, loading };
|
return { data, loading };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useCancelDataflow = () => {
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isPending: loading,
|
||||||
|
mutateAsync,
|
||||||
|
} = useMutation({
|
||||||
|
mutationKey: [AgentApiAction.CancelDataflow],
|
||||||
|
mutationFn: async (taskId: string) => {
|
||||||
|
const ret = await agentService.cancelDataflow(taskId);
|
||||||
|
if (ret?.data?.code === 0) {
|
||||||
|
message.success('success');
|
||||||
|
} else {
|
||||||
|
message.error(ret?.data?.data);
|
||||||
|
}
|
||||||
|
return ret?.data?.code;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { data, loading, cancelDataflow: mutateAsync };
|
||||||
|
};
|
||||||
|
|||||||
@ -1579,6 +1579,7 @@ This delimiter is used to split the input text into several text pieces echo of
|
|||||||
sqlStatementTip:
|
sqlStatementTip:
|
||||||
'Write your SQL query here. You can use variables, raw SQL, or mix both using variable syntax.',
|
'Write your SQL query here. You can use variables, raw SQL, or mix both using variable syntax.',
|
||||||
frameworkPrompts: 'Framework',
|
frameworkPrompts: 'Framework',
|
||||||
|
release: 'Publish',
|
||||||
},
|
},
|
||||||
llmTools: {
|
llmTools: {
|
||||||
bad_calculator: {
|
bad_calculator: {
|
||||||
@ -1702,6 +1703,15 @@ This delimiter is used to split the input text into several text pieces echo of
|
|||||||
begin: 'File',
|
begin: 'File',
|
||||||
parserMethod: 'Parser method',
|
parserMethod: 'Parser method',
|
||||||
exportJson: 'Export JSON',
|
exportJson: 'Export JSON',
|
||||||
|
viewResult: 'View Result',
|
||||||
|
running: 'Running',
|
||||||
|
context: 'Context Generator',
|
||||||
|
contextDescription: 'Context Generator',
|
||||||
|
summary: 'Summary',
|
||||||
|
keywords: 'Keywords',
|
||||||
|
questions: 'Questions',
|
||||||
|
metadata: 'Metadata',
|
||||||
|
fieldName: 'Result Destination',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1490,6 +1490,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
sqlStatementTip:
|
sqlStatementTip:
|
||||||
'在此处编写您的 SQL 查询。您可以使用变量、原始 SQL,或使用变量语法混合使用两者。',
|
'在此处编写您的 SQL 查询。您可以使用变量、原始 SQL,或使用变量语法混合使用两者。',
|
||||||
frameworkPrompts: '框架',
|
frameworkPrompts: '框架',
|
||||||
|
release: '发布',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
profile: 'All rights reserved @ React',
|
profile: 'All rights reserved @ React',
|
||||||
@ -1620,6 +1621,15 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
begin: '文件',
|
begin: '文件',
|
||||||
parserMethod: '解析方法',
|
parserMethod: '解析方法',
|
||||||
exportJson: '导出 JSON',
|
exportJson: '导出 JSON',
|
||||||
|
viewResult: '查看结果',
|
||||||
|
running: '运行中',
|
||||||
|
context: '上下文生成器',
|
||||||
|
contextDescription: '上下文生成器',
|
||||||
|
summary: '摘要',
|
||||||
|
keywords: '关键词',
|
||||||
|
questions: '问题',
|
||||||
|
metadata: '元数据',
|
||||||
|
fieldName: '结果目的地',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
AgentGlobalsSysQueryWithBrace,
|
AgentGlobalsSysQueryWithBrace,
|
||||||
CodeTemplateStrMap,
|
CodeTemplateStrMap,
|
||||||
ProgrammingLanguage,
|
ProgrammingLanguage,
|
||||||
|
initialLlmBaseValues,
|
||||||
} from '@/constants/agent';
|
} from '@/constants/agent';
|
||||||
|
|
||||||
export enum AgentDialogueMode {
|
export enum AgentDialogueMode {
|
||||||
@ -14,13 +15,8 @@ export enum AgentDialogueMode {
|
|||||||
Task = 'task',
|
Task = 'task',
|
||||||
}
|
}
|
||||||
|
|
||||||
import {
|
|
||||||
ChatVariableEnabledField,
|
|
||||||
variableEnabledFieldMap,
|
|
||||||
} from '@/constants/chat';
|
|
||||||
import { ModelVariableType } from '@/constants/knowledge';
|
import { ModelVariableType } from '@/constants/knowledge';
|
||||||
import i18n from '@/locales/config';
|
import i18n from '@/locales/config';
|
||||||
import { setInitialChatVariableEnabledFieldValue } from '@/utils/chat';
|
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
|
||||||
// DuckDuckGo's channel options
|
// DuckDuckGo's channel options
|
||||||
@ -271,24 +267,6 @@ export const initialBeginValues = {
|
|||||||
prologue: `Hi! I'm your assistant. What can I do for you?`,
|
prologue: `Hi! I'm your assistant. What can I do for you?`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const variableCheckBoxFieldMap = Object.keys(
|
|
||||||
variableEnabledFieldMap,
|
|
||||||
).reduce<Record<string, boolean>>((pre, cur) => {
|
|
||||||
pre[cur] = setInitialChatVariableEnabledFieldValue(
|
|
||||||
cur as ChatVariableEnabledField,
|
|
||||||
);
|
|
||||||
return pre;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
const initialLlmBaseValues = {
|
|
||||||
...variableCheckBoxFieldMap,
|
|
||||||
temperature: 0.1,
|
|
||||||
top_p: 0.3,
|
|
||||||
frequency_penalty: 0.7,
|
|
||||||
presence_penalty: 0.4,
|
|
||||||
max_tokens: 256,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initialGenerateValues = {
|
export const initialGenerateValues = {
|
||||||
...initialLlmBaseValues,
|
...initialLlmBaseValues,
|
||||||
prompt: i18n.t('flow.promptText'),
|
prompt: i18n.t('flow.promptText'),
|
||||||
|
|||||||
@ -35,12 +35,12 @@ import {
|
|||||||
useHideFormSheetOnNodeDeletion,
|
useHideFormSheetOnNodeDeletion,
|
||||||
useShowDrawer,
|
useShowDrawer,
|
||||||
} from '../hooks/use-show-drawer';
|
} from '../hooks/use-show-drawer';
|
||||||
import { LogSheet } from '../log-sheet';
|
|
||||||
import RunSheet from '../run-sheet';
|
import RunSheet from '../run-sheet';
|
||||||
import { ButtonEdge } from './edge';
|
import { ButtonEdge } from './edge';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
import { RagNode } from './node';
|
import { RagNode } from './node';
|
||||||
import { BeginNode } from './node/begin-node';
|
import { BeginNode } from './node/begin-node';
|
||||||
|
import { ContextNode } from './node/context-node';
|
||||||
import { InnerNextStepDropdown } from './node/dropdown/next-step-dropdown';
|
import { InnerNextStepDropdown } from './node/dropdown/next-step-dropdown';
|
||||||
import { HierarchicalMergerNode } from './node/hierarchical-merger-node';
|
import { HierarchicalMergerNode } from './node/hierarchical-merger-node';
|
||||||
import NoteNode from './node/note-node';
|
import NoteNode from './node/note-node';
|
||||||
@ -56,6 +56,7 @@ export const nodeTypes: NodeTypes = {
|
|||||||
tokenizerNode: TokenizerNode,
|
tokenizerNode: TokenizerNode,
|
||||||
splitterNode: SplitterNode,
|
splitterNode: SplitterNode,
|
||||||
hierarchicalMergerNode: HierarchicalMergerNode,
|
hierarchicalMergerNode: HierarchicalMergerNode,
|
||||||
|
contextNode: ContextNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
const edgeTypes = {
|
const edgeTypes = {
|
||||||
@ -65,9 +66,10 @@ const edgeTypes = {
|
|||||||
interface IProps {
|
interface IProps {
|
||||||
drawerVisible: boolean;
|
drawerVisible: boolean;
|
||||||
hideDrawer(): void;
|
hideDrawer(): void;
|
||||||
|
showLogSheet(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DataFlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
function DataFlowCanvas({ drawerVisible, hideDrawer, showLogSheet }: IProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
nodes,
|
nodes,
|
||||||
@ -147,17 +149,10 @@ function DataFlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
clearActiveDropdown,
|
clearActiveDropdown,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const {
|
const { run, loading: running } = useRunDataflow(
|
||||||
visible: logSheetVisible,
|
showLogSheet!,
|
||||||
showModal: showLogSheet,
|
hideRunOrChatDrawer,
|
||||||
hideModal: hideLogSheet,
|
);
|
||||||
} = useSetModalState();
|
|
||||||
|
|
||||||
const {
|
|
||||||
run,
|
|
||||||
loading: running,
|
|
||||||
messageId,
|
|
||||||
} = useRunDataflow(showLogSheet!, hideRunOrChatDrawer);
|
|
||||||
|
|
||||||
const onConnect = (connection: Connection) => {
|
const onConnect = (connection: Connection) => {
|
||||||
originalOnConnect(connection);
|
originalOnConnect(connection);
|
||||||
@ -311,9 +306,7 @@ function DataFlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
loading={running}
|
loading={running}
|
||||||
></RunSheet>
|
></RunSheet>
|
||||||
)}
|
)}
|
||||||
{logSheetVisible && (
|
{/* {logSheetVisible && <LogSheet hideModal={hideLogSheet}></LogSheet>} */}
|
||||||
<LogSheet hideModal={hideLogSheet} messageId={messageId}></LogSheet>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
1
web/src/pages/data-flow/canvas/node/context-node.tsx
Normal file
1
web/src/pages/data-flow/canvas/node/context-node.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { RagNode as ContextNode } from './index';
|
||||||
@ -124,6 +124,7 @@ function AccordionOperators({
|
|||||||
Operator.Tokenizer,
|
Operator.Tokenizer,
|
||||||
Operator.Splitter,
|
Operator.Splitter,
|
||||||
Operator.HierarchicalMerger,
|
Operator.HierarchicalMerger,
|
||||||
|
Operator.Context,
|
||||||
]}
|
]}
|
||||||
isCustomDropdown={isCustomDropdown}
|
isCustomDropdown={isCustomDropdown}
|
||||||
mousePosition={mousePosition}
|
mousePosition={mousePosition}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { IRagNode } from '@/interfaces/database/flow';
|
|||||||
import { NodeProps, Position } from '@xyflow/react';
|
import { NodeProps, Position } from '@xyflow/react';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { NodeHandleId } from '../../constant';
|
import { NodeHandleId } from '../../constant';
|
||||||
import { needsSingleStepDebugging } from '../../utils';
|
|
||||||
import { CommonHandle } from './handle';
|
import { CommonHandle } from './handle';
|
||||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||||
import NodeHeader from './node-header';
|
import NodeHeader from './node-header';
|
||||||
@ -16,12 +15,7 @@ function InnerRagNode({
|
|||||||
selected,
|
selected,
|
||||||
}: NodeProps<IRagNode>) {
|
}: NodeProps<IRagNode>) {
|
||||||
return (
|
return (
|
||||||
<ToolBar
|
<ToolBar selected={selected} id={id} label={data.label}>
|
||||||
selected={selected}
|
|
||||||
id={id}
|
|
||||||
label={data.label}
|
|
||||||
showRun={needsSingleStepDebugging(data.label)}
|
|
||||||
>
|
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected}>
|
||||||
<CommonHandle
|
<CommonHandle
|
||||||
id={NodeHandleId.End}
|
id={NodeHandleId.End}
|
||||||
|
|||||||
@ -34,7 +34,7 @@ export function ToolBar({
|
|||||||
children,
|
children,
|
||||||
label,
|
label,
|
||||||
id,
|
id,
|
||||||
showRun = true,
|
showRun = false,
|
||||||
}: ToolBarProps) {
|
}: ToolBarProps) {
|
||||||
const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
|
const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { ParseDocumentType } from '@/components/layout-recognize-form-field';
|
||||||
|
import { initialLlmBaseValues } from '@/constants/agent';
|
||||||
import {
|
import {
|
||||||
ChatVariableEnabledField,
|
ChatVariableEnabledField,
|
||||||
variableEnabledFieldMap,
|
variableEnabledFieldMap,
|
||||||
@ -15,6 +17,89 @@ import {
|
|||||||
WrapText,
|
WrapText,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
export enum FileType {
|
||||||
|
PDF = 'pdf',
|
||||||
|
Spreadsheet = 'spreadsheet',
|
||||||
|
Image = 'image',
|
||||||
|
Email = 'email',
|
||||||
|
TextMarkdown = 'text&markdown',
|
||||||
|
Docx = 'word',
|
||||||
|
PowerPoint = 'slides',
|
||||||
|
Video = 'video',
|
||||||
|
Audio = 'audio',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PdfOutputFormat {
|
||||||
|
Json = 'json',
|
||||||
|
Markdown = 'markdown',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SpreadsheetOutputFormat {
|
||||||
|
Json = 'json',
|
||||||
|
Html = 'html',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ImageOutputFormat {
|
||||||
|
Text = 'text',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EmailOutputFormat {
|
||||||
|
Json = 'json',
|
||||||
|
Text = 'text',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TextMarkdownOutputFormat {
|
||||||
|
Text = 'text',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DocxOutputFormat {
|
||||||
|
Markdown = 'markdown',
|
||||||
|
Json = 'json',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PptOutputFormat {
|
||||||
|
Json = 'json',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum VideoOutputFormat {
|
||||||
|
Json = 'json',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AudioOutputFormat {
|
||||||
|
Text = 'text',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OutputFormatMap = {
|
||||||
|
[FileType.PDF]: PdfOutputFormat,
|
||||||
|
[FileType.Spreadsheet]: SpreadsheetOutputFormat,
|
||||||
|
[FileType.Image]: ImageOutputFormat,
|
||||||
|
[FileType.Email]: EmailOutputFormat,
|
||||||
|
[FileType.TextMarkdown]: TextMarkdownOutputFormat,
|
||||||
|
[FileType.Docx]: DocxOutputFormat,
|
||||||
|
[FileType.PowerPoint]: PptOutputFormat,
|
||||||
|
[FileType.Video]: VideoOutputFormat,
|
||||||
|
[FileType.Audio]: AudioOutputFormat,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InitialOutputFormatMap = {
|
||||||
|
[FileType.PDF]: PdfOutputFormat.Json,
|
||||||
|
[FileType.Spreadsheet]: SpreadsheetOutputFormat.Html,
|
||||||
|
[FileType.Image]: ImageOutputFormat.Text,
|
||||||
|
[FileType.Email]: EmailOutputFormat.Text,
|
||||||
|
[FileType.TextMarkdown]: TextMarkdownOutputFormat.Text,
|
||||||
|
[FileType.Docx]: DocxOutputFormat.Json,
|
||||||
|
[FileType.PowerPoint]: PptOutputFormat.Json,
|
||||||
|
[FileType.Video]: VideoOutputFormat.Json,
|
||||||
|
[FileType.Audio]: AudioOutputFormat.Text,
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum ContextGeneratorFieldName {
|
||||||
|
Summary = 'summary',
|
||||||
|
Keywords = 'keywords',
|
||||||
|
Questions = 'questions',
|
||||||
|
Metadata = 'metadata',
|
||||||
|
}
|
||||||
|
|
||||||
export enum PromptRole {
|
export enum PromptRole {
|
||||||
User = 'user',
|
User = 'user',
|
||||||
Assistant = 'assistant',
|
Assistant = 'assistant',
|
||||||
@ -34,6 +119,7 @@ export enum Operator {
|
|||||||
Tokenizer = 'Tokenizer',
|
Tokenizer = 'Tokenizer',
|
||||||
Splitter = 'Splitter',
|
Splitter = 'Splitter',
|
||||||
HierarchicalMerger = 'HierarchicalMerger',
|
HierarchicalMerger = 'HierarchicalMerger',
|
||||||
|
Context = 'Context',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SwitchLogicOperatorOptions = ['and', 'or'];
|
export const SwitchLogicOperatorOptions = ['and', 'or'];
|
||||||
@ -76,9 +162,34 @@ export enum ImageParseMethod {
|
|||||||
OCR = 'ocr',
|
OCR = 'ocr',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum TokenizerFields {
|
||||||
|
Text = 'text',
|
||||||
|
Questions = 'questions',
|
||||||
|
Summary = 'summary',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ParserFields {
|
||||||
|
From = 'from',
|
||||||
|
To = 'to',
|
||||||
|
Cc = 'cc',
|
||||||
|
Bcc = 'bcc',
|
||||||
|
Date = 'date',
|
||||||
|
Subject = 'subject',
|
||||||
|
Body = 'body',
|
||||||
|
Attachments = 'attachments',
|
||||||
|
}
|
||||||
|
|
||||||
export const initialBeginValues = {
|
export const initialBeginValues = {
|
||||||
mode: AgentDialogueMode.Conversational,
|
outputs: {
|
||||||
prologue: `Hi! I'm your assistant. What can I do for you?`,
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
file: {
|
||||||
|
type: 'Object',
|
||||||
|
value: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const variableCheckBoxFieldMap = Object.keys(
|
export const variableCheckBoxFieldMap = Object.keys(
|
||||||
@ -100,7 +211,7 @@ export const initialTokenizerValues = {
|
|||||||
TokenizerSearchMethod.FullText,
|
TokenizerSearchMethod.FullText,
|
||||||
],
|
],
|
||||||
filename_embd_weight: 0.1,
|
filename_embd_weight: 0.1,
|
||||||
fields: ['text'],
|
fields: TokenizerFields.Text,
|
||||||
outputs: {},
|
outputs: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -118,10 +229,40 @@ export enum StringTransformDelimiter {
|
|||||||
Space = ' ',
|
Space = ' ',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialParserValues = { outputs: {}, setups: [] };
|
export const initialParserValues = {
|
||||||
|
outputs: {
|
||||||
|
markdown: { type: 'string', value: '' },
|
||||||
|
text: { type: 'string', value: '' },
|
||||||
|
html: { type: 'string', value: '' },
|
||||||
|
json: { type: 'Array<object>', value: [] },
|
||||||
|
},
|
||||||
|
setups: [
|
||||||
|
{
|
||||||
|
fileFormat: FileType.PDF,
|
||||||
|
output_format: PdfOutputFormat.Json,
|
||||||
|
parse_method: ParseDocumentType.DeepDOC,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fileFormat: FileType.Spreadsheet,
|
||||||
|
output_format: SpreadsheetOutputFormat.Html,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fileFormat: FileType.Image,
|
||||||
|
output_format: ImageOutputFormat.Text,
|
||||||
|
parse_method: ImageParseMethod.OCR,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fileFormat: FileType.Email,
|
||||||
|
fields: Object.values(ParserFields),
|
||||||
|
output_format: EmailOutputFormat.Text,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export const initialSplitterValues = {
|
export const initialSplitterValues = {
|
||||||
outputs: {},
|
outputs: {
|
||||||
|
chunks: { type: 'Array<Object>', value: [] },
|
||||||
|
},
|
||||||
chunk_token_size: 512,
|
chunk_token_size: 512,
|
||||||
overlapped_percent: 0,
|
overlapped_percent: 0,
|
||||||
delimiters: [{ value: '\n' }],
|
delimiters: [{ value: '\n' }],
|
||||||
@ -136,7 +277,9 @@ export enum Hierarchy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const initialHierarchicalMergerValues = {
|
export const initialHierarchicalMergerValues = {
|
||||||
outputs: {},
|
outputs: {
|
||||||
|
chunks: { type: 'Array<Object>', value: [] },
|
||||||
|
},
|
||||||
hierarchy: Hierarchy.H3,
|
hierarchy: Hierarchy.H3,
|
||||||
levels: [
|
levels: [
|
||||||
{ expressions: [{ expression: '^#[^#]' }] },
|
{ expressions: [{ expression: '^#[^#]' }] },
|
||||||
@ -146,6 +289,12 @@ export const initialHierarchicalMergerValues = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const initialContextValues = {
|
||||||
|
...initialLlmBaseValues,
|
||||||
|
field_name: [ContextGeneratorFieldName.Summary],
|
||||||
|
outputs: {},
|
||||||
|
};
|
||||||
|
|
||||||
export const CategorizeAnchorPointPositions = [
|
export const CategorizeAnchorPointPositions = [
|
||||||
{ top: 1, right: 34 },
|
{ top: 1, right: 34 },
|
||||||
{ top: 8, right: 18 },
|
{ top: 8, right: 18 },
|
||||||
@ -178,6 +327,7 @@ export const NodeMap = {
|
|||||||
[Operator.Tokenizer]: 'tokenizerNode',
|
[Operator.Tokenizer]: 'tokenizerNode',
|
||||||
[Operator.Splitter]: 'splitterNode',
|
[Operator.Splitter]: 'splitterNode',
|
||||||
[Operator.HierarchicalMerger]: 'hierarchicalMergerNode',
|
[Operator.HierarchicalMerger]: 'hierarchicalMergerNode',
|
||||||
|
[Operator.Context]: 'contextNode',
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum BeginQueryType {
|
export enum BeginQueryType {
|
||||||
@ -220,18 +370,6 @@ export enum AgentExceptionMethod {
|
|||||||
Goto = 'goto',
|
Goto = 'goto',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum FileType {
|
|
||||||
PDF = 'pdf',
|
|
||||||
Spreadsheet = 'spreadsheet',
|
|
||||||
Image = 'image',
|
|
||||||
Email = 'email',
|
|
||||||
TextMarkdown = 'text&markdown',
|
|
||||||
Docx = 'word',
|
|
||||||
PowerPoint = 'slides',
|
|
||||||
Video = 'video',
|
|
||||||
Audio = 'audio',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FileTypeSuffixMap = {
|
export const FileTypeSuffixMap = {
|
||||||
[FileType.PDF]: ['pdf'],
|
[FileType.PDF]: ['pdf'],
|
||||||
[FileType.Spreadsheet]: ['xls', 'xlsx', 'csv'],
|
[FileType.Spreadsheet]: ['xls', 'xlsx', 'csv'],
|
||||||
|
|||||||
@ -48,3 +48,10 @@ export type HandleContextType = {
|
|||||||
export const HandleContext = createContext<HandleContextType>(
|
export const HandleContext = createContext<HandleContextType>(
|
||||||
{} as HandleContextType,
|
{} as HandleContextType,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type LogContextType = {
|
||||||
|
messageId: string;
|
||||||
|
setMessageId: (messageId: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LogContext = createContext<LogContextType>({} as LogContextType);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Operator } from '../constant';
|
import { Operator } from '../constant';
|
||||||
|
import ContextForm from '../form/context-form';
|
||||||
import HierarchicalMergerForm from '../form/hierarchical-merger-form';
|
import HierarchicalMergerForm from '../form/hierarchical-merger-form';
|
||||||
import ParserForm from '../form/parser-form';
|
import ParserForm from '../form/parser-form';
|
||||||
import SplitterForm from '../form/splitter-form';
|
import SplitterForm from '../form/splitter-form';
|
||||||
@ -23,4 +24,7 @@ export const FormConfigMap = {
|
|||||||
[Operator.HierarchicalMerger]: {
|
[Operator.HierarchicalMerger]: {
|
||||||
component: HierarchicalMergerForm,
|
component: HierarchicalMergerForm,
|
||||||
},
|
},
|
||||||
|
[Operator.Context]: {
|
||||||
|
component: ContextForm,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
86
web/src/pages/data-flow/form/context-form/index.tsx
Normal file
86
web/src/pages/data-flow/form/context-form/index.tsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { LargeModelFormField } from '@/components/large-model-form-field';
|
||||||
|
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
|
||||||
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
|
import { Form } from '@/components/ui/form';
|
||||||
|
import { MultiSelect } from '@/components/ui/multi-select';
|
||||||
|
import { useBuildPromptExtraPromptOptions } from '@/pages/agent/form/agent-form/use-build-prompt-options';
|
||||||
|
import { PromptEditor } from '@/pages/agent/form/components/prompt-editor';
|
||||||
|
import { buildOptions } from '@/utils/form';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import {
|
||||||
|
ContextGeneratorFieldName,
|
||||||
|
initialContextValues,
|
||||||
|
} from '../../constant';
|
||||||
|
import { useFormValues } from '../../hooks/use-form-values';
|
||||||
|
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
||||||
|
import { INextOperatorForm } from '../../interface';
|
||||||
|
import useGraphStore from '../../store';
|
||||||
|
import { buildOutputList } from '../../utils/build-output-list';
|
||||||
|
import { FormWrapper } from '../components/form-wrapper';
|
||||||
|
import { Output } from '../components/output';
|
||||||
|
|
||||||
|
const outputList = buildOutputList(initialContextValues.outputs);
|
||||||
|
|
||||||
|
export const FormSchema = z.object({
|
||||||
|
sys_prompt: z.string(),
|
||||||
|
prompts: z.string().optional(),
|
||||||
|
...LlmSettingSchema,
|
||||||
|
field_name: z.array(z.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ContextFormSchemaType = z.infer<typeof FormSchema>;
|
||||||
|
|
||||||
|
const ContextForm = ({ node }: INextOperatorForm) => {
|
||||||
|
const defaultValues = useFormValues(initialContextValues, node);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const form = useForm<ContextFormSchemaType>({
|
||||||
|
defaultValues,
|
||||||
|
resolver: zodResolver(FormSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { edges } = useGraphStore((state) => state);
|
||||||
|
|
||||||
|
const { extraOptions } = useBuildPromptExtraPromptOptions(edges, node?.id);
|
||||||
|
|
||||||
|
const options = buildOptions(ContextGeneratorFieldName, t, 'dataflow');
|
||||||
|
|
||||||
|
useWatchFormChange(node?.id, form);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form {...form}>
|
||||||
|
<FormWrapper>
|
||||||
|
<LargeModelFormField></LargeModelFormField>
|
||||||
|
<RAGFlowFormItem label={t('flow.systemPrompt')} name="sys_prompt">
|
||||||
|
<PromptEditor
|
||||||
|
placeholder={t('flow.messagePlaceholder')}
|
||||||
|
showToolbar={true}
|
||||||
|
extraOptions={extraOptions}
|
||||||
|
></PromptEditor>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<RAGFlowFormItem label={t('flow.userPrompt')} name="prompts">
|
||||||
|
<PromptEditor showToolbar={true}></PromptEditor>
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
<RAGFlowFormItem label={t('dataflow.fieldName')} name="field_name">
|
||||||
|
{(field) => (
|
||||||
|
<MultiSelect
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
placeholder={t('dataFlowPlaceholder')}
|
||||||
|
defaultValue={field.value}
|
||||||
|
options={options}
|
||||||
|
></MultiSelect>
|
||||||
|
)}
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
</FormWrapper>
|
||||||
|
<div className="p-5">
|
||||||
|
<Output list={outputList}></Output>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ContextForm);
|
||||||
@ -8,8 +8,7 @@ import {
|
|||||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
import { buildOptions } from '@/utils/form';
|
import { buildOptions } from '@/utils/form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FileType } from '../../constant';
|
import { FileType, OutputFormatMap } from '../../constant';
|
||||||
import { OutputFormatMap } from './constant';
|
|
||||||
import { CommonProps } from './interface';
|
import { CommonProps } from './interface';
|
||||||
import { buildFieldNameWithPrefix } from './utils';
|
import { buildFieldNameWithPrefix } from './utils';
|
||||||
|
|
||||||
|
|||||||
@ -1,65 +0,0 @@
|
|||||||
import { FileType } from '../../constant';
|
|
||||||
|
|
||||||
export enum PdfOutputFormat {
|
|
||||||
Json = 'json',
|
|
||||||
Markdown = 'markdown',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum SpreadsheetOutputFormat {
|
|
||||||
Json = 'json',
|
|
||||||
Html = 'html',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ImageOutputFormat {
|
|
||||||
Text = 'text',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum EmailOutputFormat {
|
|
||||||
Json = 'json',
|
|
||||||
Text = 'text',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum TextMarkdownOutputFormat {
|
|
||||||
Text = 'text',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum DocxOutputFormat {
|
|
||||||
Markdown = 'markdown',
|
|
||||||
Json = 'json',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PptOutputFormat {
|
|
||||||
Json = 'json',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum VideoOutputFormat {
|
|
||||||
Json = 'json',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum AudioOutputFormat {
|
|
||||||
Text = 'text',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const OutputFormatMap = {
|
|
||||||
[FileType.PDF]: PdfOutputFormat,
|
|
||||||
[FileType.Spreadsheet]: SpreadsheetOutputFormat,
|
|
||||||
[FileType.Image]: ImageOutputFormat,
|
|
||||||
[FileType.Email]: EmailOutputFormat,
|
|
||||||
[FileType.TextMarkdown]: TextMarkdownOutputFormat,
|
|
||||||
[FileType.Docx]: DocxOutputFormat,
|
|
||||||
[FileType.PowerPoint]: PptOutputFormat,
|
|
||||||
[FileType.Video]: VideoOutputFormat,
|
|
||||||
[FileType.Audio]: AudioOutputFormat,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const InitialOutputFormatMap = {
|
|
||||||
[FileType.PDF]: PdfOutputFormat.Json,
|
|
||||||
[FileType.Spreadsheet]: SpreadsheetOutputFormat.Html,
|
|
||||||
[FileType.Image]: ImageOutputFormat.Text,
|
|
||||||
[FileType.Email]: EmailOutputFormat.Text,
|
|
||||||
[FileType.TextMarkdown]: TextMarkdownOutputFormat.Text,
|
|
||||||
[FileType.Docx]: DocxOutputFormat.Json,
|
|
||||||
[FileType.PowerPoint]: PptOutputFormat.Json,
|
|
||||||
[FileType.Video]: VideoOutputFormat.Json,
|
|
||||||
[FileType.Audio]: AudioOutputFormat.Text,
|
|
||||||
};
|
|
||||||
@ -1,20 +1,12 @@
|
|||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
|
||||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
|
import { MultiSelect } from '@/components/ui/multi-select';
|
||||||
import { buildOptions } from '@/utils/form';
|
import { buildOptions } from '@/utils/form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ParserFields } from '../../constant';
|
||||||
import { CommonProps } from './interface';
|
import { CommonProps } from './interface';
|
||||||
import { buildFieldNameWithPrefix } from './utils';
|
import { buildFieldNameWithPrefix } from './utils';
|
||||||
|
|
||||||
const options = buildOptions([
|
const options = buildOptions(ParserFields);
|
||||||
'from',
|
|
||||||
'to',
|
|
||||||
'cc',
|
|
||||||
'bcc',
|
|
||||||
'date',
|
|
||||||
'subject',
|
|
||||||
'body',
|
|
||||||
'attachments',
|
|
||||||
]);
|
|
||||||
|
|
||||||
export function EmailFormFields({ prefix }: CommonProps) {
|
export function EmailFormFields({ prefix }: CommonProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -24,7 +16,14 @@ export function EmailFormFields({ prefix }: CommonProps) {
|
|||||||
name={buildFieldNameWithPrefix(`fields`, prefix)}
|
name={buildFieldNameWithPrefix(`fields`, prefix)}
|
||||||
label={t('dataflow.fields')}
|
label={t('dataflow.fields')}
|
||||||
>
|
>
|
||||||
<SelectWithSearch options={options}></SelectWithSearch>
|
{(field) => (
|
||||||
|
<MultiSelect
|
||||||
|
options={options}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
defaultValue={field.value}
|
||||||
|
variant="inverted"
|
||||||
|
></MultiSelect>
|
||||||
|
)}
|
||||||
</RAGFlowFormItem>
|
</RAGFlowFormItem>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -17,14 +17,17 @@ import {
|
|||||||
} from 'react-hook-form';
|
} from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { FileType, initialParserValues } from '../../constant';
|
import {
|
||||||
|
FileType,
|
||||||
|
InitialOutputFormatMap,
|
||||||
|
initialParserValues,
|
||||||
|
} from '../../constant';
|
||||||
import { useFormValues } from '../../hooks/use-form-values';
|
import { useFormValues } from '../../hooks/use-form-values';
|
||||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
||||||
import { INextOperatorForm } from '../../interface';
|
import { INextOperatorForm } from '../../interface';
|
||||||
import { buildOutputList } from '../../utils/build-output-list';
|
import { buildOutputList } from '../../utils/build-output-list';
|
||||||
import { Output } from '../components/output';
|
import { Output } from '../components/output';
|
||||||
import { OutputFormatFormField } from './common-form-fields';
|
import { OutputFormatFormField } from './common-form-fields';
|
||||||
import { InitialOutputFormatMap } from './constant';
|
|
||||||
import { EmailFormFields } from './email-form-fields';
|
import { EmailFormFields } from './email-form-fields';
|
||||||
import { ImageFormFields } from './image-form-fields';
|
import { ImageFormFields } from './image-form-fields';
|
||||||
import { PdfFormFields } from './pdf-form-fields';
|
import { PdfFormFields } from './pdf-form-fields';
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
import { SliderInputFormField } from '@/components/slider-input-form-field';
|
import { SliderInputFormField } from '@/components/slider-input-form-field';
|
||||||
import { Form } from '@/components/ui/form';
|
import { Form } from '@/components/ui/form';
|
||||||
@ -8,7 +9,11 @@ import { memo } from 'react';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { initialTokenizerValues, TokenizerSearchMethod } from '../../constant';
|
import {
|
||||||
|
initialTokenizerValues,
|
||||||
|
TokenizerFields,
|
||||||
|
TokenizerSearchMethod,
|
||||||
|
} from '../../constant';
|
||||||
import { useFormValues } from '../../hooks/use-form-values';
|
import { useFormValues } from '../../hooks/use-form-values';
|
||||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
||||||
import { INextOperatorForm } from '../../interface';
|
import { INextOperatorForm } from '../../interface';
|
||||||
@ -21,11 +26,12 @@ const outputList = buildOutputList(initialTokenizerValues.outputs);
|
|||||||
export const FormSchema = z.object({
|
export const FormSchema = z.object({
|
||||||
search_method: z.array(z.string()).min(1),
|
search_method: z.array(z.string()).min(1),
|
||||||
filename_embd_weight: z.number(),
|
filename_embd_weight: z.number(),
|
||||||
|
fields: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const SearchMethodOptions = buildOptions(TokenizerSearchMethod);
|
const SearchMethodOptions = buildOptions(TokenizerSearchMethod);
|
||||||
|
|
||||||
const FieldsOptions = [{ label: 'text', value: 'text' }];
|
const FieldsOptions = buildOptions(TokenizerFields);
|
||||||
|
|
||||||
const TokenizerForm = ({ node }: INextOperatorForm) => {
|
const TokenizerForm = ({ node }: INextOperatorForm) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -62,14 +68,7 @@ const TokenizerForm = ({ node }: INextOperatorForm) => {
|
|||||||
step={0.01}
|
step={0.01}
|
||||||
></SliderInputFormField>
|
></SliderInputFormField>
|
||||||
<RAGFlowFormItem name="fields" label={t('dataflow.fields')}>
|
<RAGFlowFormItem name="fields" label={t('dataflow.fields')}>
|
||||||
{(field) => (
|
{(field) => <SelectWithSearch options={FieldsOptions} {...field} />}
|
||||||
<MultiSelect
|
|
||||||
options={FieldsOptions}
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
defaultValue={field.value}
|
|
||||||
variant="inverted"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</RAGFlowFormItem>
|
</RAGFlowFormItem>
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
<div className="p-5">
|
<div className="p-5">
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useFetchModelId } from '@/hooks/logic-hooks';
|
||||||
import { Connection, Node, Position, ReactFlowInstance } from '@xyflow/react';
|
import { Connection, Node, Position, ReactFlowInstance } from '@xyflow/react';
|
||||||
import humanId from 'human-id';
|
import humanId from 'human-id';
|
||||||
import { lowerFirst } from 'lodash';
|
import { lowerFirst } from 'lodash';
|
||||||
@ -8,6 +9,7 @@ import {
|
|||||||
NodeMap,
|
NodeMap,
|
||||||
Operator,
|
Operator,
|
||||||
initialBeginValues,
|
initialBeginValues,
|
||||||
|
initialContextValues,
|
||||||
initialHierarchicalMergerValues,
|
initialHierarchicalMergerValues,
|
||||||
initialNoteValues,
|
initialNoteValues,
|
||||||
initialParserValues,
|
initialParserValues,
|
||||||
@ -21,6 +23,8 @@ import {
|
|||||||
} from '../utils';
|
} from '../utils';
|
||||||
|
|
||||||
export const useInitializeOperatorParams = () => {
|
export const useInitializeOperatorParams = () => {
|
||||||
|
const llmId = useFetchModelId();
|
||||||
|
|
||||||
const initialFormValuesMap = useMemo(() => {
|
const initialFormValuesMap = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
[Operator.Begin]: initialBeginValues,
|
[Operator.Begin]: initialBeginValues,
|
||||||
@ -29,8 +33,9 @@ export const useInitializeOperatorParams = () => {
|
|||||||
[Operator.Tokenizer]: initialTokenizerValues,
|
[Operator.Tokenizer]: initialTokenizerValues,
|
||||||
[Operator.Splitter]: initialSplitterValues,
|
[Operator.Splitter]: initialSplitterValues,
|
||||||
[Operator.HierarchicalMerger]: initialHierarchicalMergerValues,
|
[Operator.HierarchicalMerger]: initialHierarchicalMergerValues,
|
||||||
|
[Operator.Context]: { ...initialContextValues, llm_id: llmId },
|
||||||
};
|
};
|
||||||
}, []);
|
}, [llmId]);
|
||||||
|
|
||||||
const initializeOperatorParams = useCallback(
|
const initializeOperatorParams = useCallback(
|
||||||
(operatorName: Operator) => {
|
(operatorName: Operator) => {
|
||||||
|
|||||||
24
web/src/pages/data-flow/hooks/use-cancel-dataflow.ts
Normal file
24
web/src/pages/data-flow/hooks/use-cancel-dataflow.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { useCancelDataflow } from '@/hooks/use-agent-request';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
export function useCancelCurrentDataflow({
|
||||||
|
messageId,
|
||||||
|
setMessageId,
|
||||||
|
hideLogSheet,
|
||||||
|
}: {
|
||||||
|
messageId: string;
|
||||||
|
setMessageId: (messageId: string) => void;
|
||||||
|
hideLogSheet(): void;
|
||||||
|
}) {
|
||||||
|
const { cancelDataflow } = useCancelDataflow();
|
||||||
|
|
||||||
|
const handleCancel = useCallback(async () => {
|
||||||
|
const code = await cancelDataflow(messageId);
|
||||||
|
if (code === 0) {
|
||||||
|
setMessageId('');
|
||||||
|
hideLogSheet();
|
||||||
|
}
|
||||||
|
}, [cancelDataflow, hideLogSheet, messageId, setMessageId]);
|
||||||
|
|
||||||
|
return { handleCancel };
|
||||||
|
}
|
||||||
30
web/src/pages/data-flow/hooks/use-fetch-log.ts
Normal file
30
web/src/pages/data-flow/hooks/use-fetch-log.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { useFetchMessageTrace } from '@/hooks/use-agent-request';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export function useFetchLog() {
|
||||||
|
const { setMessageId, data, loading, messageId } =
|
||||||
|
useFetchMessageTrace(false);
|
||||||
|
|
||||||
|
const isCompleted = useMemo(() => {
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
const latest = data?.at(-1);
|
||||||
|
return (
|
||||||
|
latest?.component_id === 'END' && !isEmpty(latest?.trace[0].message)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const isLogEmpty = !data || !data.length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
isLogEmpty,
|
||||||
|
isCompleted,
|
||||||
|
loading,
|
||||||
|
isParsing: !isLogEmpty && !isCompleted,
|
||||||
|
messageId,
|
||||||
|
setMessageId,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,8 +1,9 @@
|
|||||||
import { useSendMessageBySSE } from '@/hooks/use-send-message';
|
import { useSendMessageBySSE } from '@/hooks/use-send-message';
|
||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useContext } from 'react';
|
||||||
import { useParams } from 'umi';
|
import { useParams } from 'umi';
|
||||||
|
import { LogContext } from '../context';
|
||||||
import { useSaveGraphBeforeOpeningDebugDrawer } from './use-save-graph';
|
import { useSaveGraphBeforeOpeningDebugDrawer } from './use-save-graph';
|
||||||
|
|
||||||
export function useRunDataflow(
|
export function useRunDataflow(
|
||||||
@ -11,7 +12,7 @@ export function useRunDataflow(
|
|||||||
) {
|
) {
|
||||||
const { send } = useSendMessageBySSE(api.runCanvas);
|
const { send } = useSendMessageBySSE(api.runCanvas);
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const [messageId, setMessageId] = useState();
|
const { setMessageId } = useContext(LogContext);
|
||||||
|
|
||||||
const { handleRun: saveGraph, loading } =
|
const { handleRun: saveGraph, loading } =
|
||||||
useSaveGraphBeforeOpeningDebugDrawer(showLogSheet!);
|
useSaveGraphBeforeOpeningDebugDrawer(showLogSheet!);
|
||||||
@ -39,10 +40,10 @@ export function useRunDataflow(
|
|||||||
return msgId;
|
return msgId;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[hideRunOrChatDrawer, id, saveGraph, send],
|
[hideRunOrChatDrawer, id, saveGraph, send, setMessageId],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { run, loading: loading, messageId };
|
return { run, loading: loading };
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RunDataflowType = ReturnType<typeof useRunDataflow>;
|
export type RunDataflowType = ReturnType<typeof useRunDataflow>;
|
||||||
|
|||||||
@ -30,13 +30,17 @@ import { ComponentPropsWithoutRef, useCallback } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import DataFlowCanvas from './canvas';
|
import DataFlowCanvas from './canvas';
|
||||||
import { DropdownProvider } from './canvas/context';
|
import { DropdownProvider } from './canvas/context';
|
||||||
|
import { LogContext } from './context';
|
||||||
|
import { useCancelCurrentDataflow } from './hooks/use-cancel-dataflow';
|
||||||
import { useHandleExportOrImportJsonFile } from './hooks/use-export-json';
|
import { useHandleExportOrImportJsonFile } from './hooks/use-export-json';
|
||||||
import { useFetchDataOnMount } from './hooks/use-fetch-data';
|
import { useFetchDataOnMount } from './hooks/use-fetch-data';
|
||||||
|
import { useFetchLog } from './hooks/use-fetch-log';
|
||||||
import {
|
import {
|
||||||
useSaveGraph,
|
useSaveGraph,
|
||||||
useSaveGraphBeforeOpeningDebugDrawer,
|
useSaveGraphBeforeOpeningDebugDrawer,
|
||||||
useWatchAgentChange,
|
useWatchAgentChange,
|
||||||
} from './hooks/use-save-graph';
|
} from './hooks/use-save-graph';
|
||||||
|
import { LogSheet } from './log-sheet';
|
||||||
import { SettingDialog } from './setting-dialog';
|
import { SettingDialog } from './setting-dialog';
|
||||||
import { useAgentHistoryManager } from './use-agent-history-manager';
|
import { useAgentHistoryManager } from './use-agent-history-manager';
|
||||||
import { VersionDialog } from './version-dialog';
|
import { VersionDialog } from './version-dialog';
|
||||||
@ -65,9 +69,7 @@ export default function DataFlow() {
|
|||||||
const { saveGraph, loading } = useSaveGraph();
|
const { saveGraph, loading } = useSaveGraph();
|
||||||
const { flowDetail: agentDetail } = useFetchDataOnMount();
|
const { flowDetail: agentDetail } = useFetchDataOnMount();
|
||||||
const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer);
|
const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer);
|
||||||
const handleRunAgent = useCallback(() => {
|
|
||||||
handleRun();
|
|
||||||
}, [handleRun]);
|
|
||||||
const {
|
const {
|
||||||
visible: versionDialogVisible,
|
visible: versionDialogVisible,
|
||||||
hideModal: hideVersionDialog,
|
hideModal: hideVersionDialog,
|
||||||
@ -80,6 +82,29 @@ export default function DataFlow() {
|
|||||||
showModal: showSettingDialog,
|
showModal: showSettingDialog,
|
||||||
} = useSetModalState();
|
} = useSetModalState();
|
||||||
|
|
||||||
|
const {
|
||||||
|
visible: logSheetVisible,
|
||||||
|
showModal: showLogSheet,
|
||||||
|
hideModal: hideLogSheet,
|
||||||
|
} = useSetModalState();
|
||||||
|
|
||||||
|
const { isParsing, data, messageId, setMessageId } = useFetchLog();
|
||||||
|
|
||||||
|
const handleRunAgent = useCallback(() => {
|
||||||
|
if (isParsing) {
|
||||||
|
// show log sheet
|
||||||
|
showLogSheet();
|
||||||
|
} else {
|
||||||
|
handleRun();
|
||||||
|
}
|
||||||
|
}, [handleRun, isParsing, showLogSheet]);
|
||||||
|
|
||||||
|
const { handleCancel } = useCancelCurrentDataflow({
|
||||||
|
messageId,
|
||||||
|
setMessageId,
|
||||||
|
hideLogSheet,
|
||||||
|
});
|
||||||
|
|
||||||
const time = useWatchAgentChange(chatDrawerVisible);
|
const time = useWatchAgentChange(chatDrawerVisible);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -111,15 +136,22 @@ export default function DataFlow() {
|
|||||||
>
|
>
|
||||||
<LaptopMinimalCheck /> {t('flow.save')}
|
<LaptopMinimalCheck /> {t('flow.save')}
|
||||||
</ButtonLoading>
|
</ButtonLoading>
|
||||||
<Button variant={'secondary'} onClick={handleRunAgent}>
|
<Button
|
||||||
<CirclePlay />
|
variant={'secondary'}
|
||||||
{t('flow.run')}
|
onClick={handleRunAgent}
|
||||||
|
disabled={isParsing}
|
||||||
|
>
|
||||||
|
<CirclePlay className={isParsing ? 'animate-spin' : ''} />
|
||||||
|
{isParsing ? t('dataflow.running') : t('flow.run')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant={'secondary'} onClick={showVersionDialog}>
|
<Button variant={'secondary'} onClick={showVersionDialog}>
|
||||||
<History />
|
<History />
|
||||||
{t('flow.historyversion')}
|
{t('flow.historyversion')}
|
||||||
</Button>
|
</Button>
|
||||||
|
{/* <Button variant={'secondary'}>
|
||||||
|
<Send />
|
||||||
|
{t('flow.release')}
|
||||||
|
</Button> */}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant={'secondary'}>
|
<Button variant={'secondary'}>
|
||||||
@ -140,15 +172,17 @@ export default function DataFlow() {
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<ReactFlowProvider>
|
<LogContext.Provider value={{ messageId, setMessageId }}>
|
||||||
<DropdownProvider>
|
<ReactFlowProvider>
|
||||||
<DataFlowCanvas
|
<DropdownProvider>
|
||||||
drawerVisible={chatDrawerVisible}
|
<DataFlowCanvas
|
||||||
hideDrawer={hideChatDrawer}
|
drawerVisible={chatDrawerVisible}
|
||||||
></DataFlowCanvas>
|
hideDrawer={hideChatDrawer}
|
||||||
</DropdownProvider>
|
showLogSheet={showLogSheet}
|
||||||
</ReactFlowProvider>
|
></DataFlowCanvas>
|
||||||
|
</DropdownProvider>
|
||||||
|
</ReactFlowProvider>
|
||||||
|
</LogContext.Provider>
|
||||||
{versionDialogVisible && (
|
{versionDialogVisible && (
|
||||||
<DropdownProvider>
|
<DropdownProvider>
|
||||||
<VersionDialog hideModal={hideVersionDialog}></VersionDialog>
|
<VersionDialog hideModal={hideVersionDialog}></VersionDialog>
|
||||||
@ -157,6 +191,14 @@ export default function DataFlow() {
|
|||||||
{settingDialogVisible && (
|
{settingDialogVisible && (
|
||||||
<SettingDialog hideModal={hideSettingDialog}></SettingDialog>
|
<SettingDialog hideModal={hideSettingDialog}></SettingDialog>
|
||||||
)}
|
)}
|
||||||
|
{logSheetVisible && (
|
||||||
|
<LogSheet
|
||||||
|
hideModal={hideLogSheet}
|
||||||
|
isParsing={isParsing}
|
||||||
|
logs={data}
|
||||||
|
handleCancel={handleCancel}
|
||||||
|
></LogSheet>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,10 @@ import {
|
|||||||
TimelineSeparator,
|
TimelineSeparator,
|
||||||
TimelineTitle,
|
TimelineTitle,
|
||||||
} from '@/components/originui/timeline';
|
} from '@/components/originui/timeline';
|
||||||
|
import { Progress } from '@/components/ui/progress';
|
||||||
import { ITraceData } from '@/interfaces/database/agent';
|
import { ITraceData } from '@/interfaces/database/agent';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { File } from 'lucide-react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { Operator } from '../constant';
|
import { Operator } from '../constant';
|
||||||
import OperatorIcon from '../operator-icon';
|
import OperatorIcon from '../operator-icon';
|
||||||
@ -17,6 +20,8 @@ export type DataflowTimelineProps = {
|
|||||||
traceList?: ITraceData[];
|
traceList?: ITraceData[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const END = 'END';
|
||||||
|
|
||||||
interface DataflowTrace {
|
interface DataflowTrace {
|
||||||
datetime: string;
|
datetime: string;
|
||||||
elapsed_time: number;
|
elapsed_time: number;
|
||||||
@ -48,43 +53,73 @@ export function DataflowTimeline({ traceList }: DataflowTimelineProps) {
|
|||||||
const traces = item.trace as DataflowTrace[];
|
const traces = item.trace as DataflowTrace[];
|
||||||
const nodeLabel = getNodeLabel(item.component_id);
|
const nodeLabel = getNodeLabel(item.component_id);
|
||||||
|
|
||||||
|
const latest = traces[traces.length - 1];
|
||||||
|
const progress = latest.progress * 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TimelineItem
|
<TimelineItem
|
||||||
key={item.component_id}
|
key={item.component_id}
|
||||||
step={index}
|
step={index}
|
||||||
className="group-data-[orientation=vertical]/timeline:ms-10 group-data-[orientation=vertical]/timeline:not-last:pb-8"
|
className="group-data-[orientation=vertical]/timeline:ms-10 group-data-[orientation=vertical]/timeline:not-last:pb-8 pb-6"
|
||||||
>
|
>
|
||||||
<TimelineHeader>
|
<TimelineHeader>
|
||||||
<TimelineSeparator className="group-data-[orientation=vertical]/timeline:-left-7 group-data-[orientation=vertical]/timeline:h-[calc(100%-1.5rem-0.25rem)] group-data-[orientation=vertical]/timeline:translate-y-7 bg-accent-primary" />
|
<TimelineSeparator className="group-data-[orientation=vertical]/timeline:-left-7 group-data-[orientation=vertical]/timeline:h-[calc(100%-1.5rem-0.25rem)] group-data-[orientation=vertical]/timeline:translate-y-7 bg-accent-primary" />
|
||||||
<TimelineTitle className="">
|
<TimelineTitle className="">
|
||||||
<TimelineContent className="text-foreground mt-2 rounded-lg border px-4 py-3">
|
<TimelineContent
|
||||||
<p className="mb-2">
|
className={cn(
|
||||||
{getNodeData(item.component_id)?.name || 'END'}
|
'text-foreground rounded-lg border px-4 py-3',
|
||||||
</p>
|
)}
|
||||||
|
>
|
||||||
|
<section className="flex items-center justify-between mb-2">
|
||||||
|
<span className="flex-1 truncate">
|
||||||
|
{getNodeData(item.component_id)?.name || END}
|
||||||
|
</span>
|
||||||
|
<div className="flex-1 flex items-center gap-5">
|
||||||
|
<Progress value={progress} className="h-1 flex-1" />
|
||||||
|
<span className="text-accent-primary text-xs">
|
||||||
|
{progress}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
<div className="divide-y space-y-1">
|
<div className="divide-y space-y-1">
|
||||||
{traces.map((x, idx) => (
|
{traces.map((x, idx) => (
|
||||||
<section
|
<section
|
||||||
key={idx}
|
key={idx}
|
||||||
className="text-text-secondary text-xs"
|
className="text-text-secondary text-xs space-x-2 py-2.5 !m-0"
|
||||||
>
|
>
|
||||||
<div className="space-x-2">
|
<span>{x.datetime}</span>
|
||||||
<span>{x.datetime}</span>
|
|
||||||
<span>{x.progress * 100}%</span>
|
|
||||||
<span>{x.elapsed_time.toString().slice(0, 6)}</span>
|
|
||||||
</div>
|
|
||||||
{item.component_id !== 'END' && (
|
{item.component_id !== 'END' && (
|
||||||
<div>{x.message}</div>
|
<span
|
||||||
|
className={cn({
|
||||||
|
'text-state-error':
|
||||||
|
x.message.startsWith('[ERROR]'),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{x.message}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
|
<span>{x.elapsed_time.toString().slice(0, 6)}s</span>
|
||||||
</section>
|
</section>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</TimelineContent>
|
</TimelineContent>
|
||||||
</TimelineTitle>
|
</TimelineTitle>
|
||||||
<TimelineIndicator className="border border-accent-primary group-data-completed/timeline-item:bg-primary group-data-completed/timeline-item:text-primary-foreground flex size-6 items-center justify-center group-data-[orientation=vertical]/timeline:-left-7">
|
<TimelineIndicator
|
||||||
{nodeLabel && (
|
className={cn(
|
||||||
|
'border border-accent-primary group-data-completed/timeline-item:bg-primary group-data-completed/timeline-item:text-primary-foreground flex size-5 items-center justify-center group-data-[orientation=vertical]/timeline:-left-7',
|
||||||
|
{
|
||||||
|
'rounded bg-accent-primary': nodeLabel === Operator.Begin,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.component_id === END ? (
|
||||||
|
<span className="rounded-full inline-block size-2 bg-accent-primary"></span>
|
||||||
|
) : nodeLabel === Operator.Begin ? (
|
||||||
|
<File className="size-3.5 text-bg-base"></File>
|
||||||
|
) : (
|
||||||
<OperatorIcon
|
<OperatorIcon
|
||||||
name={nodeLabel}
|
name={nodeLabel}
|
||||||
className="size-6 rounded-full"
|
className="size-3.5 rounded-full"
|
||||||
></OperatorIcon>
|
></OperatorIcon>
|
||||||
)}
|
)}
|
||||||
</TimelineIndicator>
|
</TimelineIndicator>
|
||||||
|
|||||||
@ -5,11 +5,15 @@ import {
|
|||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
} from '@/components/ui/sheet';
|
} from '@/components/ui/sheet';
|
||||||
import { useFetchMessageTrace } from '@/hooks/use-agent-request';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
import { IModalProps } from '@/interfaces/common';
|
||||||
|
import { ITraceData } from '@/interfaces/database/agent';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { NotebookText, SquareArrowOutUpRight } from 'lucide-react';
|
import {
|
||||||
import { useEffect } from 'react';
|
ArrowUpRight,
|
||||||
|
CirclePause,
|
||||||
|
Logs,
|
||||||
|
SquareArrowOutUpRight,
|
||||||
|
} from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import 'react18-json-view/src/style.css';
|
import 'react18-json-view/src/style.css';
|
||||||
import {
|
import {
|
||||||
@ -18,39 +22,56 @@ import {
|
|||||||
} from '../hooks/use-download-output';
|
} from '../hooks/use-download-output';
|
||||||
import { DataflowTimeline } from './dataflow-timeline';
|
import { DataflowTimeline } from './dataflow-timeline';
|
||||||
|
|
||||||
type LogSheetProps = IModalProps<any> & { messageId?: string };
|
type LogSheetProps = IModalProps<any> & {
|
||||||
|
isParsing: boolean;
|
||||||
|
handleCancel(): void;
|
||||||
|
logs?: ITraceData[];
|
||||||
|
};
|
||||||
|
|
||||||
export function LogSheet({ hideModal, messageId }: LogSheetProps) {
|
export function LogSheet({
|
||||||
|
hideModal,
|
||||||
|
isParsing,
|
||||||
|
logs,
|
||||||
|
handleCancel,
|
||||||
|
}: LogSheetProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setMessageId, data } = useFetchMessageTrace(false);
|
|
||||||
|
|
||||||
const { handleDownloadJson } = useDownloadOutput(data);
|
const { handleDownloadJson } = useDownloadOutput(logs);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (messageId) {
|
|
||||||
setMessageId(messageId);
|
|
||||||
}
|
|
||||||
}, [messageId, setMessageId]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open onOpenChange={hideModal} modal={false}>
|
<Sheet open onOpenChange={hideModal} modal={false}>
|
||||||
<SheetContent className={cn('top-20')}>
|
<SheetContent
|
||||||
|
className={cn('top-20')}
|
||||||
|
onInteractOutside={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle className="flex items-center gap-1">
|
<SheetTitle className="flex items-center gap-2.5">
|
||||||
<NotebookText className="size-4" /> {t('flow.log')}
|
<Logs className="size-4" /> {t('flow.log')}
|
||||||
|
<Button variant={'ghost'}>
|
||||||
|
{t('dataflow.viewResult')} <ArrowUpRight />
|
||||||
|
</Button>
|
||||||
</SheetTitle>
|
</SheetTitle>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<section className="max-h-[82vh] overflow-auto mt-6">
|
<section className="max-h-[82vh] overflow-auto mt-6">
|
||||||
<DataflowTimeline traceList={data}></DataflowTimeline>
|
<DataflowTimeline traceList={logs}></DataflowTimeline>
|
||||||
</section>
|
</section>
|
||||||
<Button
|
{isParsing ? (
|
||||||
onClick={handleDownloadJson}
|
<Button
|
||||||
disabled={isEndOutputEmpty(data)}
|
className="w-full mt-8 bg-state-error/10 text-state-error"
|
||||||
className="w-full mt-8"
|
onClick={handleCancel}
|
||||||
>
|
>
|
||||||
<SquareArrowOutUpRight />
|
<CirclePause /> Cancel
|
||||||
{t('dataflow.exportJson')}
|
</Button>
|
||||||
</Button>
|
) : (
|
||||||
|
<Button
|
||||||
|
onClick={handleDownloadJson}
|
||||||
|
disabled={isEndOutputEmpty(logs)}
|
||||||
|
className="w-full mt-8"
|
||||||
|
>
|
||||||
|
<SquareArrowOutUpRight />
|
||||||
|
{t('dataflow.exportJson')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,9 +2,10 @@ import { IconFont } from '@/components/icon-font';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import {
|
import {
|
||||||
Blocks,
|
Blocks,
|
||||||
|
File,
|
||||||
FileChartColumnIncreasing,
|
FileChartColumnIncreasing,
|
||||||
|
FileStack,
|
||||||
Heading,
|
Heading,
|
||||||
HousePlus,
|
|
||||||
ListMinus,
|
ListMinus,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Operator } from './constant';
|
import { Operator } from './constant';
|
||||||
@ -15,15 +16,16 @@ interface IProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const OperatorIconMap = {
|
export const OperatorIconMap = {
|
||||||
[Operator.Begin]: 'house-plus',
|
|
||||||
[Operator.Note]: 'notebook-pen',
|
[Operator.Note]: 'notebook-pen',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SVGIconMap = {
|
export const SVGIconMap = {
|
||||||
|
[Operator.Begin]: File,
|
||||||
[Operator.Parser]: FileChartColumnIncreasing,
|
[Operator.Parser]: FileChartColumnIncreasing,
|
||||||
[Operator.Tokenizer]: ListMinus,
|
[Operator.Tokenizer]: ListMinus,
|
||||||
[Operator.Splitter]: Blocks,
|
[Operator.Splitter]: Blocks,
|
||||||
[Operator.HierarchicalMerger]: Heading,
|
[Operator.HierarchicalMerger]: Heading,
|
||||||
|
[Operator.Context]: FileStack,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Empty = () => {
|
const Empty = () => {
|
||||||
@ -42,7 +44,7 @@ const OperatorIcon = ({ name, className }: IProps) => {
|
|||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<HousePlus className="rounded size-3" />
|
<File className="rounded size-3" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -50,7 +52,7 @@ const OperatorIcon = ({ name, className }: IProps) => {
|
|||||||
return typeof Icon === 'string' ? (
|
return typeof Icon === 'string' ? (
|
||||||
<IconFont name={Icon} className={cn('size-5 ', className)}></IconFont>
|
<IconFont name={Icon} className={cn('size-5 ', className)}></IconFont>
|
||||||
) : (
|
) : (
|
||||||
<SvgIcon className="size-5"></SvgIcon>
|
<SvgIcon className={cn('size-5', className)}></SvgIcon>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { IAgentForm } from '@/interfaces/database/agent';
|
import { IAgentForm } from '@/interfaces/database/agent';
|
||||||
import { DSLComponents, RAGFlowNodeType } from '@/interfaces/database/flow';
|
import { DSLComponents, RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
import { removeUselessFieldsFromValues } from '@/utils/form';
|
import { Edge, XYPosition } from '@xyflow/react';
|
||||||
import { Edge, Node, XYPosition } from '@xyflow/react';
|
|
||||||
import { FormInstance, FormListFieldData } from 'antd';
|
import { FormInstance, FormListFieldData } from 'antd';
|
||||||
import { humanId } from 'human-id';
|
import { humanId } from 'human-id';
|
||||||
import { curry, get, intersectionWith, isEmpty, isEqual, sample } from 'lodash';
|
import { curry, get, intersectionWith, isEmpty, isEqual, sample } from 'lodash';
|
||||||
@ -24,24 +23,13 @@ const buildComponentDownstreamOrUpstream = (
|
|||||||
edges: Edge[],
|
edges: Edge[],
|
||||||
nodeId: string,
|
nodeId: string,
|
||||||
isBuildDownstream = true,
|
isBuildDownstream = true,
|
||||||
nodes: Node[],
|
|
||||||
) => {
|
) => {
|
||||||
return edges
|
return edges
|
||||||
.filter((y) => {
|
.filter((y) => {
|
||||||
const node = nodes.find((x) => x.id === nodeId);
|
|
||||||
let isNotUpstreamTool = true;
|
let isNotUpstreamTool = true;
|
||||||
let isNotUpstreamAgent = true;
|
let isNotUpstreamAgent = true;
|
||||||
let isNotExceptionGoto = true;
|
let isNotExceptionGoto = true;
|
||||||
if (isBuildDownstream && node?.data.label === Operator.Agent) {
|
|
||||||
isNotExceptionGoto = y.sourceHandle !== NodeHandleId.AgentException;
|
|
||||||
// Exclude the tool operator downstream of the agent operator
|
|
||||||
isNotUpstreamTool = !y.target.startsWith(Operator.Tool);
|
|
||||||
// Exclude the agent operator downstream of the agent operator
|
|
||||||
isNotUpstreamAgent = !(
|
|
||||||
y.target.startsWith(Operator.Agent) &&
|
|
||||||
y.targetHandle === NodeHandleId.AgentTop
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
y[isBuildDownstream ? 'source' : 'target'] === nodeId &&
|
y[isBuildDownstream ? 'source' : 'target'] === nodeId &&
|
||||||
isNotUpstreamTool &&
|
isNotUpstreamTool &&
|
||||||
@ -54,9 +42,9 @@ const buildComponentDownstreamOrUpstream = (
|
|||||||
|
|
||||||
const removeUselessDataInTheOperator = curry(
|
const removeUselessDataInTheOperator = curry(
|
||||||
(operatorName: string, params: Record<string, unknown>) => {
|
(operatorName: string, params: Record<string, unknown>) => {
|
||||||
if (operatorName === Operator.Categorize) {
|
// if (operatorName === Operator.Categorize) {
|
||||||
return removeUselessFieldsFromValues(params, '');
|
// return removeUselessFieldsFromValues(params, '');
|
||||||
}
|
// }
|
||||||
return params;
|
return params;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -197,8 +185,8 @@ export const buildDslComponentsByGraph = (
|
|||||||
component_name: operatorName,
|
component_name: operatorName,
|
||||||
params: buildOperatorParams(operatorName)(params) ?? {},
|
params: buildOperatorParams(operatorName)(params) ?? {},
|
||||||
},
|
},
|
||||||
downstream: buildComponentDownstreamOrUpstream(edges, id, true, nodes),
|
downstream: buildComponentDownstreamOrUpstream(edges, id, true),
|
||||||
upstream: buildComponentDownstreamOrUpstream(edges, id, false, nodes),
|
upstream: buildComponentDownstreamOrUpstream(edges, id, false),
|
||||||
parent_id: x?.parentId,
|
parent_id: x?.parentId,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -352,25 +340,6 @@ export const generateNodeNamesWithIncreasingIndex = (
|
|||||||
export const duplicateNodeForm = (nodeData?: RAGFlowNodeType['data']) => {
|
export const duplicateNodeForm = (nodeData?: RAGFlowNodeType['data']) => {
|
||||||
const form: Record<string, any> = { ...(nodeData?.form ?? {}) };
|
const form: Record<string, any> = { ...(nodeData?.form ?? {}) };
|
||||||
|
|
||||||
// Delete the downstream node corresponding to the to field of the Categorize operator
|
|
||||||
if (nodeData?.label === Operator.Categorize) {
|
|
||||||
form.category_description = Object.keys(form.category_description).reduce<
|
|
||||||
Record<string, Record<string, any>>
|
|
||||||
>((pre, cur) => {
|
|
||||||
pre[cur] = {
|
|
||||||
...form.category_description[cur],
|
|
||||||
to: undefined,
|
|
||||||
};
|
|
||||||
return pre;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the downstream nodes corresponding to the yes and no fields of the Relevant operator
|
|
||||||
if (nodeData?.label === Operator.Relevant) {
|
|
||||||
form.yes = undefined;
|
|
||||||
form.no = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...(nodeData ?? { label: '' }),
|
...(nodeData ?? { label: '' }),
|
||||||
form,
|
form,
|
||||||
|
|||||||
@ -154,12 +154,7 @@ const ChunkerContainer = (props: IProps) => {
|
|||||||
<RerunButton step={step} onRerun={handleReRunFunc} />
|
<RerunButton step={step} onRerun={handleReRunFunc} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div
|
<div className={classNames('flex flex-col w-full')}>
|
||||||
className={classNames(
|
|
||||||
{ [styles.pagePdfWrapper]: isPdf },
|
|
||||||
'flex flex-col w-full',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Spin spinning={loading} className={styles.spin} size="large">
|
<Spin spinning={loading} className={styles.spin} size="large">
|
||||||
<div className="h-[50px] flex flex-row justify-between items-end pb-[5px]">
|
<div className="h-[50px] flex flex-row justify-between items-end pb-[5px]">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -1,24 +1,17 @@
|
|||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Ban, CircleCheck, Trash2 } from 'lucide-react';
|
import { Trash2 } from 'lucide-react';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type ICheckboxSetProps = {
|
type ICheckboxSetProps = {
|
||||||
selectAllChunk: (e: any) => void;
|
selectAllChunk: (e: any) => void;
|
||||||
removeChunk: (e?: any) => void;
|
removeChunk: (e?: any) => void;
|
||||||
switchChunk: (available: number) => void;
|
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
selectedChunkIds: string[];
|
selectedChunkIds: string[];
|
||||||
};
|
};
|
||||||
export default (props: ICheckboxSetProps) => {
|
export default (props: ICheckboxSetProps) => {
|
||||||
const {
|
const { selectAllChunk, removeChunk, checked, selectedChunkIds } = props;
|
||||||
selectAllChunk,
|
|
||||||
removeChunk,
|
|
||||||
switchChunk,
|
|
||||||
checked,
|
|
||||||
selectedChunkIds,
|
|
||||||
} = props;
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const handleSelectAllCheck = useCallback(
|
const handleSelectAllCheck = useCallback(
|
||||||
(e: any) => {
|
(e: any) => {
|
||||||
@ -32,14 +25,6 @@ export default (props: ICheckboxSetProps) => {
|
|||||||
removeChunk();
|
removeChunk();
|
||||||
}, [removeChunk]);
|
}, [removeChunk]);
|
||||||
|
|
||||||
const handleEnabledClick = useCallback(() => {
|
|
||||||
switchChunk(1);
|
|
||||||
}, [switchChunk]);
|
|
||||||
|
|
||||||
const handleDisabledClick = useCallback(() => {
|
|
||||||
switchChunk(0);
|
|
||||||
}, [switchChunk]);
|
|
||||||
|
|
||||||
const isSelected = useMemo(() => {
|
const isSelected = useMemo(() => {
|
||||||
return selectedChunkIds?.length > 0;
|
return selectedChunkIds?.length > 0;
|
||||||
}, [selectedChunkIds]);
|
}, [selectedChunkIds]);
|
||||||
@ -57,20 +42,6 @@ export default (props: ICheckboxSetProps) => {
|
|||||||
</div>
|
</div>
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<>
|
<>
|
||||||
<div
|
|
||||||
className="flex items-center cursor-pointer text-muted-foreground hover:text-text-primary"
|
|
||||||
onClick={handleEnabledClick}
|
|
||||||
>
|
|
||||||
<CircleCheck size={16} />
|
|
||||||
<span className="block ml-1">{t('chunk.enable')}</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="flex items-center cursor-pointer text-muted-foreground hover:text-text-primary"
|
|
||||||
onClick={handleDisabledClick}
|
|
||||||
>
|
|
||||||
<Ban size={16} />
|
|
||||||
<span className="block ml-1">{t('chunk.disable')}</span>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="flex items-center cursor-pointer text-red-400 hover:text-red-500"
|
className="flex items-center cursor-pointer text-red-400 hover:text-red-500"
|
||||||
onClick={handleDeleteClick}
|
onClick={handleDeleteClick}
|
||||||
|
|||||||
@ -1,46 +1,168 @@
|
|||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { CheckedState } from '@radix-ui/react-checkbox';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
interface FormatPreserveEditorProps {
|
interface FormatPreserveEditorProps {
|
||||||
initialValue: string;
|
initialValue: {
|
||||||
onSave: (value: string) => void;
|
key: string;
|
||||||
|
type: string;
|
||||||
|
value: Array<{ [key: string]: string }>;
|
||||||
|
};
|
||||||
|
onSave: (value: any) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
isSelect?: boolean;
|
||||||
|
isDelete?: boolean;
|
||||||
|
isChunck?: boolean;
|
||||||
|
handleCheckboxClick?: (id: string | number, checked: boolean) => void;
|
||||||
|
selectedChunkIds?: string[];
|
||||||
}
|
}
|
||||||
const FormatPreserveEditor = ({
|
const FormatPreserveEditor = ({
|
||||||
initialValue,
|
initialValue,
|
||||||
onSave,
|
onSave,
|
||||||
className,
|
className,
|
||||||
|
isChunck,
|
||||||
|
handleCheckboxClick,
|
||||||
|
selectedChunkIds,
|
||||||
}: FormatPreserveEditorProps) => {
|
}: FormatPreserveEditorProps) => {
|
||||||
const [content, setContent] = useState(initialValue);
|
const [content, setContent] = useState(initialValue);
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
// const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const [activeEditIndex, setActiveEditIndex] = useState<number | undefined>(
|
||||||
const handleEdit = () => setIsEditing(true);
|
undefined,
|
||||||
|
);
|
||||||
const handleSave = () => {
|
console.log('initialValue', initialValue);
|
||||||
onSave(content);
|
const handleEdit = (e?: any, index?: number) => {
|
||||||
setIsEditing(false);
|
console.log(e, index, content);
|
||||||
|
if (content.key === 'json') {
|
||||||
|
console.log(e, e.target.innerText);
|
||||||
|
setContent((pre) => ({
|
||||||
|
...pre,
|
||||||
|
value: pre.value.map((item, i) => {
|
||||||
|
if (i === index) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
[Object.keys(item)[0]]: e.target.innerText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
setActiveEditIndex(index);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleChange = (e: any) => {
|
||||||
|
if (content.key === 'json') {
|
||||||
|
setContent((pre) => ({
|
||||||
|
...pre,
|
||||||
|
value: pre.value.map((item, i) => {
|
||||||
|
if (i === activeEditIndex) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
[Object.keys(item)[0]]: e.target.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
setContent(e.target.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const escapeNewlines = (text: string) => {
|
||||||
|
return text.replace(/\n/g, '\\n');
|
||||||
|
};
|
||||||
|
const unescapeNewlines = (text: string) => {
|
||||||
|
return text.replace(/\\n/g, '\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
const saveData = {
|
||||||
|
...content,
|
||||||
|
value: content.value?.map((item) => {
|
||||||
|
return { ...item, text: unescapeNewlines(item.text) };
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
onSave(saveData);
|
||||||
|
setActiveEditIndex(undefined);
|
||||||
|
};
|
||||||
|
const handleCheck = (e: CheckedState, id: string | number) => {
|
||||||
|
handleCheckboxClick?.(id, e === 'indeterminate' ? false : e);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className="editor-container">
|
<div className="editor-container">
|
||||||
{isEditing ? (
|
{/* {isEditing && content.key === 'json' ? (
|
||||||
<Textarea
|
<Textarea
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full h-full bg-transparent text-text-secondary',
|
'w-full h-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 min-h-6 p-0',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
value={content}
|
value={content.value}
|
||||||
onChange={(e) => setContent(e.target.value)}
|
onChange={handleChange}
|
||||||
onBlur={handleSave}
|
onBlur={handleSave}
|
||||||
autoSize={{ maxRows: 100 }}
|
autoSize={{ maxRows: 100 }}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<pre className="text-text-secondary" onClick={handleEdit}>
|
<>
|
||||||
{content}
|
{content.key === 'json' && */}
|
||||||
</pre>
|
{content.value.map((item, index) => (
|
||||||
)}
|
<section
|
||||||
|
key={index}
|
||||||
|
className={
|
||||||
|
isChunck
|
||||||
|
? 'bg-bg-card my-2 p-2 rounded-lg flex gap-1 items-start'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{isChunck && (
|
||||||
|
<Checkbox
|
||||||
|
onCheckedChange={(e) => {
|
||||||
|
handleCheck(e, index);
|
||||||
|
}}
|
||||||
|
checked={selectedChunkIds?.some(
|
||||||
|
(id) => id.toString() === index.toString(),
|
||||||
|
)}
|
||||||
|
></Checkbox>
|
||||||
|
)}
|
||||||
|
{activeEditIndex === index && (
|
||||||
|
<Textarea
|
||||||
|
key={'t' + index}
|
||||||
|
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 !h-6 min-h-6 p-0',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
value={escapeNewlines(content.value[index].text)}
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={handleSave}
|
||||||
|
autoSize={{ maxRows: 100, minRows: 1 }}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{activeEditIndex !== index && (
|
||||||
|
<div
|
||||||
|
className="text-text-secondary overflow-auto scrollbar-auto whitespace-pre-wrap"
|
||||||
|
key={index}
|
||||||
|
onClick={(e) => {
|
||||||
|
handleEdit(e, index);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{escapeNewlines(item.text)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
))}
|
||||||
|
{/* {content.key !== 'json' && (
|
||||||
|
<pre
|
||||||
|
className="text-text-secondary overflow-auto scrollbar-auto"
|
||||||
|
onClick={handleEdit}
|
||||||
|
>
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}*/}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,16 +4,15 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Modal } from '@/components/ui/modal/modal';
|
import { Modal } from '@/components/ui/modal/modal';
|
||||||
import { CircleAlert } from 'lucide-react';
|
import { CircleAlert } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useRerunDataflow } from '../../hooks';
|
|
||||||
interface RerunButtonProps {
|
interface RerunButtonProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
step?: TimelineNode;
|
step?: TimelineNode;
|
||||||
onRerun?: () => void;
|
onRerun?: () => void;
|
||||||
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
const RerunButton = (props: RerunButtonProps) => {
|
const RerunButton = (props: RerunButtonProps) => {
|
||||||
const { className, step, onRerun } = props;
|
const { className, step, onRerun, loading } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { loading } = useRerunDataflow();
|
|
||||||
const clickFunc = () => {
|
const clickFunc = () => {
|
||||||
console.log('click rerun button');
|
console.log('click rerun button');
|
||||||
Modal.show({
|
Modal.show({
|
||||||
@ -29,15 +28,15 @@ const RerunButton = (props: RerunButtonProps) => {
|
|||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
),
|
),
|
||||||
onVisibleChange: () => {
|
okText: t('modal.okText'),
|
||||||
Modal.hide();
|
cancelText: t('modal.cancelText'),
|
||||||
},
|
onVisibleChange: (visible: boolean) => {
|
||||||
onOk: () => {
|
if (!visible) {
|
||||||
onRerun?.();
|
Modal.destroy();
|
||||||
Modal.hide();
|
} else {
|
||||||
},
|
onRerun?.();
|
||||||
onCancel: () => {
|
Modal.destroy();
|
||||||
Modal.hide();
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,85 +1,112 @@
|
|||||||
import { CustomTimeline, TimelineNode } from '@/components/originui/timeline';
|
import { CustomTimeline, TimelineNode } from '@/components/originui/timeline';
|
||||||
import {
|
import {
|
||||||
CheckLine,
|
Blocks,
|
||||||
FilePlayIcon,
|
File,
|
||||||
Grid3x2,
|
FilePlay,
|
||||||
|
FileStack,
|
||||||
|
Heading,
|
||||||
ListPlus,
|
ListPlus,
|
||||||
PlayIcon,
|
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
export enum TimelineNodeType {
|
import { TimelineNodeType } from '../../constant';
|
||||||
begin = 'begin',
|
import { IPipelineFileLogDetail } from '../../interface';
|
||||||
parser = 'parser',
|
|
||||||
chunk = 'chunk',
|
export type ITimelineNodeObj = {
|
||||||
indexer = 'indexer',
|
title: string;
|
||||||
complete = 'complete',
|
icon: JSX.Element;
|
||||||
end = 'end',
|
clickable?: boolean;
|
||||||
}
|
type: TimelineNodeType;
|
||||||
export const TimelineNodeArr = [
|
};
|
||||||
{
|
|
||||||
id: 1,
|
export const TimelineNodeObj = {
|
||||||
title: 'Begin',
|
[TimelineNodeType.begin]: {
|
||||||
icon: <PlayIcon size={13} />,
|
title: 'File',
|
||||||
|
icon: <File size={13} />,
|
||||||
clickable: false,
|
clickable: false,
|
||||||
type: TimelineNodeType.begin,
|
|
||||||
},
|
},
|
||||||
{
|
[TimelineNodeType.parser]: {
|
||||||
id: 2,
|
|
||||||
title: 'Parser',
|
title: 'Parser',
|
||||||
icon: <FilePlayIcon size={13} />,
|
icon: <FilePlay size={13} />,
|
||||||
type: TimelineNodeType.parser,
|
|
||||||
},
|
},
|
||||||
{
|
[TimelineNodeType.contextGenerator]: {
|
||||||
id: 3,
|
title: 'Context Generator',
|
||||||
title: 'Chunker',
|
icon: <FileStack size={13} />,
|
||||||
icon: <Grid3x2 size={13} />,
|
|
||||||
type: TimelineNodeType.chunk,
|
|
||||||
},
|
},
|
||||||
{
|
[TimelineNodeType.titleSplitter]: {
|
||||||
id: 4,
|
title: 'Title Splitter',
|
||||||
title: 'Indexer',
|
icon: <Heading size={13} />,
|
||||||
|
},
|
||||||
|
[TimelineNodeType.characterSplitter]: {
|
||||||
|
title: 'Title Splitter',
|
||||||
|
icon: <Heading size={13} />,
|
||||||
|
},
|
||||||
|
[TimelineNodeType.splitter]: {
|
||||||
|
title: 'Character Splitter',
|
||||||
|
icon: <Blocks size={13} />,
|
||||||
|
},
|
||||||
|
[TimelineNodeType.tokenizer]: {
|
||||||
|
title: 'Tokenizer',
|
||||||
icon: <ListPlus size={13} />,
|
icon: <ListPlus size={13} />,
|
||||||
clickable: false,
|
clickable: false,
|
||||||
type: TimelineNodeType.indexer,
|
|
||||||
},
|
},
|
||||||
{
|
};
|
||||||
id: 5,
|
// export const TimelineNodeArr = [
|
||||||
title: 'Complete',
|
// {
|
||||||
icon: <CheckLine size={13} />,
|
// id: 1,
|
||||||
clickable: false,
|
// title: 'File',
|
||||||
type: TimelineNodeType.complete,
|
// icon: <PlayIcon size={13} />,
|
||||||
},
|
// clickable: false,
|
||||||
];
|
// type: TimelineNodeType.begin,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// id: 2,
|
||||||
|
// title: 'Context Generator',
|
||||||
|
// icon: <PlayIcon size={13} />,
|
||||||
|
// type: TimelineNodeType.contextGenerator,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// id: 3,
|
||||||
|
// title: 'Title Splitter',
|
||||||
|
// icon: <PlayIcon size={13} />,
|
||||||
|
// type: TimelineNodeType.titleSplitter,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// id: 4,
|
||||||
|
// title: 'Character Splitter',
|
||||||
|
// icon: <PlayIcon size={13} />,
|
||||||
|
// type: TimelineNodeType.characterSplitter,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// id: 5,
|
||||||
|
// title: 'Tokenizer',
|
||||||
|
// icon: <CheckLine size={13} />,
|
||||||
|
// clickable: false,
|
||||||
|
// type: TimelineNodeType.tokenizer,
|
||||||
|
// },
|
||||||
|
// ]
|
||||||
export interface TimelineDataFlowProps {
|
export interface TimelineDataFlowProps {
|
||||||
activeId: number | string;
|
activeId: number | string;
|
||||||
activeFunc: (id: number | string, step: TimelineNode) => void;
|
activeFunc: (id: number | string, step: TimelineNode) => void;
|
||||||
|
data: IPipelineFileLogDetail;
|
||||||
|
timelineNodes: TimelineNode[];
|
||||||
}
|
}
|
||||||
const TimelineDataFlow = ({ activeFunc, activeId }: TimelineDataFlowProps) => {
|
const TimelineDataFlow = ({
|
||||||
// const [activeStep, setActiveStep] = useState(2);
|
activeFunc,
|
||||||
const timelineNodes: TimelineNode[] = useMemo(() => {
|
activeId,
|
||||||
const nodes: TimelineNode[] = [];
|
data,
|
||||||
TimelineNodeArr.forEach((node) => {
|
timelineNodes,
|
||||||
nodes.push({
|
}: TimelineDataFlowProps) => {
|
||||||
...node,
|
// const [timelineNodeArr,setTimelineNodeArr] = useState<ITimelineNodeObj & {id: number | string}>()
|
||||||
className: 'w-32',
|
|
||||||
completed: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return nodes;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const activeStep = useMemo(() => {
|
const activeStep = useMemo(() => {
|
||||||
const index = timelineNodes.findIndex((node) => node.id === activeId);
|
const index = timelineNodes.findIndex((node) => node.id === activeId);
|
||||||
return index > -1 ? index + 1 : 0;
|
return index > -1 ? index + 1 : 0;
|
||||||
}, [activeId, timelineNodes]);
|
}, [activeId, timelineNodes]);
|
||||||
const handleStepChange = (step: number, id: string | number) => {
|
const handleStepChange = (step: number, id: string | number) => {
|
||||||
// setActiveStep(step);
|
|
||||||
activeFunc?.(
|
activeFunc?.(
|
||||||
id,
|
id,
|
||||||
timelineNodes.find((node) => node.id === activeStep) as TimelineNode,
|
timelineNodes.find((node) => node.id === activeStep) as TimelineNode,
|
||||||
);
|
);
|
||||||
console.log(step, id);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -2,3 +2,14 @@ export enum ChunkTextMode {
|
|||||||
Full = 'full',
|
Full = 'full',
|
||||||
Ellipse = 'ellipse',
|
Ellipse = 'ellipse',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum TimelineNodeType {
|
||||||
|
begin = 'file',
|
||||||
|
parser = 'parser',
|
||||||
|
splitter = 'splitter',
|
||||||
|
contextGenerator = 'contextGenerator',
|
||||||
|
titleSplitter = 'titleSplitter',
|
||||||
|
characterSplitter = 'characterSplitter',
|
||||||
|
tokenizer = 'tokenizer',
|
||||||
|
end = 'end',
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import message from '@/components/ui/message';
|
import { TimelineNode } from '@/components/originui/timeline';
|
||||||
import {
|
import {
|
||||||
useCreateChunk,
|
useCreateChunk,
|
||||||
useDeleteChunk,
|
useDeleteChunk,
|
||||||
@ -8,40 +8,17 @@ import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks';
|
|||||||
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
|
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
|
||||||
import { IChunk } from '@/interfaces/database/knowledge';
|
import { IChunk } from '@/interfaces/database/knowledge';
|
||||||
import kbService from '@/services/knowledge-service';
|
import kbService from '@/services/knowledge-service';
|
||||||
|
import { formatSecondsToHumanReadable } from '@/utils/date';
|
||||||
import { buildChunkHighlights } from '@/utils/document-util';
|
import { buildChunkHighlights } from '@/utils/document-util';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { camelCase, upperFirst } from 'lodash';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { IHighlight } from 'react-pdf-highlighter';
|
import { IHighlight } from 'react-pdf-highlighter';
|
||||||
import { useParams, useSearchParams } from 'umi';
|
import { useParams, useSearchParams } from 'umi';
|
||||||
import { ChunkTextMode } from './constant';
|
import { ITimelineNodeObj, TimelineNodeObj } from './components/time-line';
|
||||||
|
import { ChunkTextMode, TimelineNodeType } from './constant';
|
||||||
|
import { IDslComponent, IPipelineFileLogDetail } from './interface';
|
||||||
|
|
||||||
export interface IPipelineFileLogDetail {
|
|
||||||
avatar: string;
|
|
||||||
create_date: string;
|
|
||||||
create_time: number;
|
|
||||||
document_id: string;
|
|
||||||
document_name: string;
|
|
||||||
document_suffix: string;
|
|
||||||
document_type: string;
|
|
||||||
dsl: string;
|
|
||||||
id: string;
|
|
||||||
kb_id: string;
|
|
||||||
operation_status: string;
|
|
||||||
parser_id: string;
|
|
||||||
pipeline_id: string;
|
|
||||||
pipeline_title: string;
|
|
||||||
process_begin_at: string;
|
|
||||||
process_duration: number;
|
|
||||||
progress: number;
|
|
||||||
progress_msg: string;
|
|
||||||
source_from: string;
|
|
||||||
status: string;
|
|
||||||
task_type: string;
|
|
||||||
tenant_id: string;
|
|
||||||
update_date: string;
|
|
||||||
update_time: number;
|
|
||||||
}
|
|
||||||
export const useFetchPipelineFileLogDetail = (props?: {
|
export const useFetchPipelineFileLogDetail = (props?: {
|
||||||
isEdit?: boolean;
|
isEdit?: boolean;
|
||||||
refreshCount?: number;
|
refreshCount?: number;
|
||||||
@ -199,52 +176,105 @@ export const useFetchParserList = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useRerunDataflow = () => {
|
export const useRerunDataflow = ({
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
data: IPipelineFileLogDetail;
|
||||||
|
}) => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [isChange, setIsChange] = useState(false);
|
const [isChange, setIsChange] = useState(false);
|
||||||
|
const handleReRunFunc = useCallback(
|
||||||
|
(newData: { value: IDslComponent; key: string }) => {
|
||||||
|
const newDsl = {
|
||||||
|
...data.dsl,
|
||||||
|
components: {
|
||||||
|
...data.dsl.components,
|
||||||
|
[newData.key]: newData.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// this Data provided to the interface
|
||||||
|
const params = {
|
||||||
|
id: data.id,
|
||||||
|
dsl: newDsl,
|
||||||
|
compenent_id: newData.key,
|
||||||
|
};
|
||||||
|
console.log('newDsl', newDsl, params);
|
||||||
|
},
|
||||||
|
[data],
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading,
|
loading,
|
||||||
setLoading,
|
setLoading,
|
||||||
isChange,
|
isChange,
|
||||||
setIsChange,
|
setIsChange,
|
||||||
|
handleReRunFunc,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useFetchPaserText = () => {
|
export const useTimelineDataFlow = (data: IPipelineFileLogDetail) => {
|
||||||
const initialText =
|
const timelineNodes: TimelineNode[] = useMemo(() => {
|
||||||
'第一行文本\n\t第二行缩进文本\n第三行 多个空格 第一行文本\n\t第二行缩进文本\n第三行 ' +
|
const nodes: Array<ITimelineNodeObj & { id: number | string }> = [];
|
||||||
'多个空格第一行文本\n\t第二行缩进文本\n第三行 多个空格第一行文本\n\t第二行缩进文本\n第三行 ' +
|
console.log('time-->', data);
|
||||||
'多个空格第一行文本\n\t第二行缩进文本\n第三行 多个空格第一行文本\n\t第二行缩进文本\n第三行 ' +
|
const times = data?.dsl?.components;
|
||||||
'多个空格第一行文本\n\t第二行缩进文本\n第三行 多个空格第一行文本\n\t第二行缩进文本\n第三行 ' +
|
if (times) {
|
||||||
'多个空格第一行文本\n\t第二行缩进文本\n第三行 多个空格第一行文本\n\t第二行缩进文本\n第三行 ' +
|
const getNode = (
|
||||||
'多个空格第一行文本\n\t第二行缩进文本\n第三行 多个空格第一行文本\n\t第二行缩进文本\n第三行 ' +
|
key: string,
|
||||||
'多个空格第一行文本\n\t第二行缩进文本\n第三行 多个空格第一行文本\n\t第二行缩进文本\n第三行 多个空格';
|
index: number,
|
||||||
const [loading, setLoading] = useState(false);
|
type:
|
||||||
const [data, setData] = useState<string>(initialText);
|
| TimelineNodeType.begin
|
||||||
const { t } = useTranslation();
|
| TimelineNodeType.parser
|
||||||
const queryClient = useQueryClient();
|
| TimelineNodeType.splitter
|
||||||
|
| TimelineNodeType.tokenizer
|
||||||
|
| TimelineNodeType.characterSplitter
|
||||||
|
| TimelineNodeType.titleSplitter,
|
||||||
|
) => {
|
||||||
|
const node = times[key].obj;
|
||||||
|
const name = camelCase(
|
||||||
|
node.component_name,
|
||||||
|
) as keyof typeof TimelineNodeObj;
|
||||||
|
|
||||||
const {
|
let tempType = type;
|
||||||
// data,
|
if (name === TimelineNodeType.parser) {
|
||||||
// isPending: loading,
|
tempType = TimelineNodeType.parser;
|
||||||
mutateAsync,
|
} else if (name === TimelineNodeType.tokenizer) {
|
||||||
} = useMutation({
|
tempType = TimelineNodeType.tokenizer;
|
||||||
mutationKey: ['createChunk'],
|
} else if (
|
||||||
mutationFn: async (payload: any) => {
|
name === TimelineNodeType.characterSplitter ||
|
||||||
// let service = kbService.create_chunk;
|
name === TimelineNodeType.titleSplitter ||
|
||||||
// if (payload.chunk_id) {
|
name === TimelineNodeType.splitter
|
||||||
// service = kbService.set_chunk;
|
) {
|
||||||
// }
|
tempType = TimelineNodeType.splitter;
|
||||||
// const { data } = await service(payload);
|
}
|
||||||
// if (data.code === 0) {
|
const timeNode = {
|
||||||
message.success(t('message.created'));
|
...TimelineNodeObj[name],
|
||||||
setTimeout(() => {
|
clickable: true,
|
||||||
queryClient.invalidateQueries({ queryKey: ['fetchChunkList'] });
|
id: index,
|
||||||
}, 1000); // Delay to ensure the list is updated
|
className: 'w-32',
|
||||||
// }
|
completed: false,
|
||||||
// return data?.code;
|
date: formatSecondsToHumanReadable(
|
||||||
},
|
node.params?.outputs?._elapsed_time?.value || 0,
|
||||||
});
|
),
|
||||||
|
type: tempType,
|
||||||
|
detail: { value: times[key], key: key },
|
||||||
|
};
|
||||||
|
console.log('timeNodetype-->', type);
|
||||||
|
nodes.push(timeNode);
|
||||||
|
|
||||||
return { data, loading, rerun: mutateAsync };
|
if (times[key].downstream && times[key].downstream.length > 0) {
|
||||||
|
const nextKey = times[key].downstream[0];
|
||||||
|
|
||||||
|
// nodes.push(timeNode);
|
||||||
|
getNode(nextKey, index + 1, tempType);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
getNode(upperFirst(TimelineNodeType.begin), 1, TimelineNodeType.begin);
|
||||||
|
// setTimelineNodeArr(nodes as unknown as ITimelineNodeObj & {id: number | string})
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}, [data]);
|
||||||
|
return {
|
||||||
|
timelineNodes,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
useGetChunkHighlights,
|
useGetChunkHighlights,
|
||||||
useHandleChunkCardClick,
|
useHandleChunkCardClick,
|
||||||
useRerunDataflow,
|
useRerunDataflow,
|
||||||
|
useTimelineDataFlow,
|
||||||
} from './hooks';
|
} from './hooks';
|
||||||
|
|
||||||
import DocumentHeader from './components/document-preview/document-header';
|
import DocumentHeader from './components/document-preview/document-header';
|
||||||
@ -29,13 +30,11 @@ import {
|
|||||||
useNavigatePage,
|
useNavigatePage,
|
||||||
} from '@/hooks/logic-hooks/navigate-hooks';
|
} from '@/hooks/logic-hooks/navigate-hooks';
|
||||||
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
|
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
|
||||||
import { ChunkerContainer } from './chunker';
|
|
||||||
import { useGetDocumentUrl } from './components/document-preview/hooks';
|
import { useGetDocumentUrl } from './components/document-preview/hooks';
|
||||||
import TimelineDataFlow, {
|
import TimelineDataFlow from './components/time-line';
|
||||||
TimelineNodeArr,
|
import { TimelineNodeType } from './constant';
|
||||||
TimelineNodeType,
|
|
||||||
} from './components/time-line';
|
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
import { IDslComponent } from './interface';
|
||||||
import ParserContainer from './parser';
|
import ParserContainer from './parser';
|
||||||
|
|
||||||
const Chunk = () => {
|
const Chunk = () => {
|
||||||
@ -45,9 +44,10 @@ const Chunk = () => {
|
|||||||
const { selectedChunkId } = useHandleChunkCardClick();
|
const { selectedChunkId } = useHandleChunkCardClick();
|
||||||
const [activeStepId, setActiveStepId] = useState<number | string>(0);
|
const [activeStepId, setActiveStepId] = useState<number | string>(0);
|
||||||
const { data: dataset } = useFetchPipelineFileLogDetail();
|
const { data: dataset } = useFetchPipelineFileLogDetail();
|
||||||
const { isChange, setIsChange } = useRerunDataflow();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { timelineNodes } = useTimelineDataFlow(dataset);
|
||||||
|
|
||||||
const { navigateToDataset, getQueryString, navigateToDatasetList } =
|
const { navigateToDataset, getQueryString, navigateToDatasetList } =
|
||||||
useNavigatePage();
|
useNavigatePage();
|
||||||
const fileUrl = useGetDocumentUrl();
|
const fileUrl = useGetDocumentUrl();
|
||||||
@ -69,8 +69,15 @@ const Chunk = () => {
|
|||||||
return 'unknown';
|
return 'unknown';
|
||||||
}, [documentInfo]);
|
}, [documentInfo]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleReRunFunc,
|
||||||
|
isChange,
|
||||||
|
setIsChange,
|
||||||
|
loading: reRunLoading,
|
||||||
|
} = useRerunDataflow({
|
||||||
|
data: dataset,
|
||||||
|
});
|
||||||
const handleStepChange = (id: number | string, step: TimelineNode) => {
|
const handleStepChange = (id: number | string, step: TimelineNode) => {
|
||||||
console.log(id, step);
|
|
||||||
if (isChange) {
|
if (isChange) {
|
||||||
Modal.show({
|
Modal.show({
|
||||||
visible: true,
|
visible: true,
|
||||||
@ -114,12 +121,14 @@ const Chunk = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { type } = useGetKnowledgeSearchParams();
|
const { type } = useGetKnowledgeSearchParams();
|
||||||
|
|
||||||
const currentTimeNode: TimelineNode = useMemo(() => {
|
const currentTimeNode: TimelineNode = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
TimelineNodeArr.find((node) => node.id === activeStepId) ||
|
timelineNodes.find((node) => node.id === activeStepId) ||
|
||||||
({} as TimelineNode)
|
({} as TimelineNode)
|
||||||
);
|
);
|
||||||
}, [activeStepId]);
|
}, [activeStepId, timelineNodes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader>
|
<PageHeader>
|
||||||
@ -134,10 +143,10 @@ const Chunk = () => {
|
|||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
<BreadcrumbLink
|
<BreadcrumbLink
|
||||||
onClick={navigateToDataset(
|
onClick={navigateToDataset(
|
||||||
getQueryString(QueryStringMap.id) as string,
|
getQueryString(QueryStringMap.KnowledgeId) as string,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{dataset.name}
|
{t('knowledgeDetails.overview')}
|
||||||
</BreadcrumbLink>
|
</BreadcrumbLink>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
<BreadcrumbSeparator />
|
<BreadcrumbSeparator />
|
||||||
@ -152,6 +161,8 @@ const Chunk = () => {
|
|||||||
<TimelineDataFlow
|
<TimelineDataFlow
|
||||||
activeFunc={handleStepChange}
|
activeFunc={handleStepChange}
|
||||||
activeId={activeStepId}
|
activeId={activeStepId}
|
||||||
|
data={dataset}
|
||||||
|
timelineNodes={timelineNodes}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -173,20 +184,31 @@ const Chunk = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="h-dvh border-r -mt-3"></div>
|
<div className="h-dvh border-r -mt-3"></div>
|
||||||
<div className="w-3/5 h-full">
|
<div className="w-3/5 h-full">
|
||||||
{currentTimeNode?.type === TimelineNodeType.chunk && (
|
{/* {currentTimeNode?.type === TimelineNodeType.splitter && (
|
||||||
<ChunkerContainer
|
<ChunkerContainer
|
||||||
isChange={isChange}
|
isChange={isChange}
|
||||||
setIsChange={setIsChange}
|
setIsChange={setIsChange}
|
||||||
step={currentTimeNode as TimelineNode}
|
step={currentTimeNode as TimelineNode}
|
||||||
/>
|
/>
|
||||||
)}
|
)} */}
|
||||||
{currentTimeNode?.type === TimelineNodeType.parser && (
|
{/* {currentTimeNode?.type === TimelineNodeType.parser && ( */}
|
||||||
|
{(currentTimeNode?.type === TimelineNodeType.parser ||
|
||||||
|
currentTimeNode?.type === TimelineNodeType.splitter) && (
|
||||||
<ParserContainer
|
<ParserContainer
|
||||||
isChange={isChange}
|
isChange={isChange}
|
||||||
|
reRunLoading={reRunLoading}
|
||||||
setIsChange={setIsChange}
|
setIsChange={setIsChange}
|
||||||
step={currentTimeNode as TimelineNode}
|
step={currentTimeNode as TimelineNode}
|
||||||
|
data={
|
||||||
|
currentTimeNode.detail as {
|
||||||
|
value: IDslComponent;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reRunFunc={handleReRunFunc}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{/* )} */}
|
||||||
<Spotlight opcity={0.6} coverage={60} />
|
<Spotlight opcity={0.6} coverage={60} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
62
web/src/pages/dataflow-result/interface.ts
Normal file
62
web/src/pages/dataflow-result/interface.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
interface ComponentParams {
|
||||||
|
debug_inputs: Record<string, any>;
|
||||||
|
delay_after_error: number;
|
||||||
|
description: string;
|
||||||
|
exception_default_value: any;
|
||||||
|
exception_goto: any;
|
||||||
|
exception_method: any;
|
||||||
|
inputs: Record<string, any>;
|
||||||
|
max_retries: number;
|
||||||
|
message_history_window_size: number;
|
||||||
|
outputs: {
|
||||||
|
_created_time: Record<string, any>;
|
||||||
|
_elapsed_time: Record<string, any>;
|
||||||
|
name: Record<string, any>;
|
||||||
|
output_format: { type: string; value: string };
|
||||||
|
json: { type: string; value: string };
|
||||||
|
};
|
||||||
|
persist_logs: boolean;
|
||||||
|
timeout: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ComponentObject {
|
||||||
|
component_name: string;
|
||||||
|
params: ComponentParams;
|
||||||
|
}
|
||||||
|
export interface IDslComponent {
|
||||||
|
downstream: Array<string>;
|
||||||
|
obj: ComponentObject;
|
||||||
|
upstream: Array<string>;
|
||||||
|
}
|
||||||
|
export interface IPipelineFileLogDetail {
|
||||||
|
avatar: string;
|
||||||
|
create_date: string;
|
||||||
|
create_time: number;
|
||||||
|
document_id: string;
|
||||||
|
document_name: string;
|
||||||
|
document_suffix: string;
|
||||||
|
document_type: string;
|
||||||
|
dsl: {
|
||||||
|
components: {
|
||||||
|
[key: string]: IDslComponent;
|
||||||
|
};
|
||||||
|
task_id: string;
|
||||||
|
path: Array<string>;
|
||||||
|
};
|
||||||
|
id: string;
|
||||||
|
kb_id: string;
|
||||||
|
operation_status: string;
|
||||||
|
parser_id: string;
|
||||||
|
pipeline_id: string;
|
||||||
|
pipeline_title: string;
|
||||||
|
process_begin_at: string;
|
||||||
|
process_duration: number;
|
||||||
|
progress: number;
|
||||||
|
progress_msg: string;
|
||||||
|
source_from: string;
|
||||||
|
status: string;
|
||||||
|
task_type: string;
|
||||||
|
tenant_id: string;
|
||||||
|
update_date: string;
|
||||||
|
update_time: number;
|
||||||
|
}
|
||||||
@ -2,41 +2,121 @@ import { TimelineNode } from '@/components/originui/timeline';
|
|||||||
import Spotlight from '@/components/spotlight';
|
import Spotlight from '@/components/spotlight';
|
||||||
import { Spin } from '@/components/ui/spin';
|
import { Spin } from '@/components/ui/spin';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import FormatPreserveEditor from './components/parse-editer';
|
import CheckboxSets from './components/chunk-result-bar/checkbox-sets';
|
||||||
|
import FormatPreserEditor from './components/parse-editer';
|
||||||
import RerunButton from './components/rerun-button';
|
import RerunButton from './components/rerun-button';
|
||||||
import { useFetchParserList, useFetchPaserText } from './hooks';
|
import { TimelineNodeType } from './constant';
|
||||||
|
import { useFetchParserList } from './hooks';
|
||||||
|
import { IDslComponent } from './interface';
|
||||||
interface IProps {
|
interface IProps {
|
||||||
isChange: boolean;
|
isChange: boolean;
|
||||||
setIsChange: (isChange: boolean) => void;
|
setIsChange: (isChange: boolean) => void;
|
||||||
step?: TimelineNode;
|
step?: TimelineNode;
|
||||||
|
data: { value: IDslComponent; key: string };
|
||||||
|
reRunLoading: boolean;
|
||||||
|
reRunFunc: (data: { value: IDslComponent; key: string }) => void;
|
||||||
}
|
}
|
||||||
const ParserContainer = (props: IProps) => {
|
const ParserContainer = (props: IProps) => {
|
||||||
const { isChange, setIsChange, step } = props;
|
const { isChange, setIsChange, step, data, reRunFunc, reRunLoading } = props;
|
||||||
const { data: initialValue, rerun: onSave } = useFetchPaserText();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { loading } = useFetchParserList();
|
const { loading } = useFetchParserList();
|
||||||
|
const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]);
|
||||||
|
const initialValue = useMemo(() => {
|
||||||
|
const outputs = data?.value?.obj?.params?.outputs;
|
||||||
|
const key = outputs?.output_format?.value;
|
||||||
|
const value = outputs[key]?.value;
|
||||||
|
const type = outputs[key]?.type;
|
||||||
|
console.log('outputs-->', outputs);
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
type,
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
const [initialText, setInitialText] = useState(initialValue);
|
const [initialText, setInitialText] = useState(initialValue);
|
||||||
const handleSave = (newContent: string) => {
|
const handleSave = (newContent: any) => {
|
||||||
console.log('保存内容:', newContent);
|
console.log('newContent-change-->', newContent, initialValue);
|
||||||
if (newContent !== initialText) {
|
if (JSON.stringify(newContent) !== JSON.stringify(initialValue)) {
|
||||||
setIsChange(true);
|
setIsChange(true);
|
||||||
onSave(newContent);
|
setInitialText(newContent);
|
||||||
} else {
|
} else {
|
||||||
setIsChange(false);
|
setIsChange(false);
|
||||||
}
|
}
|
||||||
// Here, the API is called to send newContent to the backend
|
// Here, the API is called to send newContent to the backend
|
||||||
};
|
};
|
||||||
const handleReRunFunc = () => {
|
|
||||||
|
const handleReRunFunc = useCallback(() => {
|
||||||
|
const newData: { value: IDslComponent; key: string } = {
|
||||||
|
...data,
|
||||||
|
value: {
|
||||||
|
...data.value,
|
||||||
|
obj: {
|
||||||
|
...data.value.obj,
|
||||||
|
params: {
|
||||||
|
...(data.value?.obj?.params || {}),
|
||||||
|
outputs: {
|
||||||
|
...(data.value?.obj?.params?.outputs || {}),
|
||||||
|
[initialText.key]: {
|
||||||
|
type: initialText.type,
|
||||||
|
value: initialText.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
reRunFunc(newData);
|
||||||
setIsChange(false);
|
setIsChange(false);
|
||||||
};
|
}, [data, initialText, reRunFunc, setIsChange]);
|
||||||
|
|
||||||
|
const handleRemoveChunk = useCallback(async () => {
|
||||||
|
if (selectedChunkIds.length > 0) {
|
||||||
|
initialText.value = initialText.value.filter(
|
||||||
|
(item: any, index: number) => !selectedChunkIds.includes(index + ''),
|
||||||
|
);
|
||||||
|
setSelectedChunkIds([]);
|
||||||
|
}
|
||||||
|
}, [selectedChunkIds, initialText]);
|
||||||
|
|
||||||
|
const handleCheckboxClick = useCallback(
|
||||||
|
(id: string | number, checked: boolean) => {
|
||||||
|
console.log('handleCheckboxClick', id, checked, selectedChunkIds);
|
||||||
|
setSelectedChunkIds((prev) => {
|
||||||
|
if (checked) {
|
||||||
|
return [...prev, id.toString()];
|
||||||
|
} else {
|
||||||
|
return prev.filter((item) => item.toString() !== id.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectAllChunk = useCallback(
|
||||||
|
(checked: boolean) => {
|
||||||
|
setSelectedChunkIds(
|
||||||
|
checked ? initialText.value.map((x, index: number) => index) : [],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[initialText.value],
|
||||||
|
);
|
||||||
|
|
||||||
|
const isChunck =
|
||||||
|
step?.type === TimelineNodeType.characterSplitter ||
|
||||||
|
step?.type === TimelineNodeType.titleSplitter ||
|
||||||
|
step?.type === TimelineNodeType.splitter;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isChange && (
|
{isChange && (
|
||||||
<div className=" absolute top-2 right-6">
|
<div className=" absolute top-2 right-6">
|
||||||
<RerunButton step={step} onRerun={handleReRunFunc} />
|
<RerunButton
|
||||||
|
step={step}
|
||||||
|
onRerun={handleReRunFunc}
|
||||||
|
loading={reRunLoading}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={classNames('flex flex-col w-full')}>
|
<div className={classNames('flex flex-col w-full')}>
|
||||||
@ -51,11 +131,33 @@ const ParserContainer = (props: IProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className=" border rounded-lg p-[20px] box-border h-[calc(100vh-180px)] overflow-auto scrollbar-none">
|
|
||||||
<FormatPreserveEditor
|
{isChunck && (
|
||||||
|
<div className="pt-[5px] pb-[5px]">
|
||||||
|
<CheckboxSets
|
||||||
|
selectAllChunk={selectAllChunk}
|
||||||
|
removeChunk={handleRemoveChunk}
|
||||||
|
checked={selectedChunkIds.length === initialText.value.length}
|
||||||
|
selectedChunkIds={selectedChunkIds}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className=" border rounded-lg p-[20px] box-border h-[calc(100vh-180px)] w-[calc(100%-20px)] overflow-auto scrollbar-none">
|
||||||
|
<FormatPreserEditor
|
||||||
initialValue={initialText}
|
initialValue={initialText}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
className="!h-[calc(100vh-220px)]"
|
className={
|
||||||
|
initialText.key !== 'json' ? '!h-[calc(100vh-220px)]' : ''
|
||||||
|
}
|
||||||
|
isChunck={isChunck}
|
||||||
|
isDelete={
|
||||||
|
step?.type === TimelineNodeType.characterSplitter ||
|
||||||
|
step?.type === TimelineNodeType.titleSplitter ||
|
||||||
|
step?.type === TimelineNodeType.splitter
|
||||||
|
}
|
||||||
|
handleCheckboxClick={handleCheckboxClick}
|
||||||
|
selectedChunkIds={selectedChunkIds}
|
||||||
/>
|
/>
|
||||||
<Spotlight opcity={0.6} coverage={60} />
|
<Spotlight opcity={0.6} coverage={60} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,10 +4,12 @@ import {
|
|||||||
} from '@/hooks/logic-hooks';
|
} from '@/hooks/logic-hooks';
|
||||||
import kbService, {
|
import kbService, {
|
||||||
listDataPipelineLogDocument,
|
listDataPipelineLogDocument,
|
||||||
|
listPipelineDatasetLogs,
|
||||||
} from '@/services/knowledge-service';
|
} from '@/services/knowledge-service';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useParams, useSearchParams } from 'umi';
|
import { useParams, useSearchParams } from 'umi';
|
||||||
|
import { LogTabs } from './dataset-common';
|
||||||
|
|
||||||
export interface IOverviewTital {
|
export interface IOverviewTital {
|
||||||
cancelled: number;
|
cancelled: number;
|
||||||
@ -61,7 +63,7 @@ export interface IFileLogItem {
|
|||||||
update_time: number;
|
update_time: number;
|
||||||
}
|
}
|
||||||
export interface IFileLogList {
|
export interface IFileLogList {
|
||||||
docs: IFileLogItem[];
|
logs: IFileLogItem[];
|
||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +72,14 @@ const useFetchFileLogList = () => {
|
|||||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
const [active, setActive] = useState<(typeof LogTabs)[keyof typeof LogTabs]>(
|
||||||
|
LogTabs.FILE_LOGS,
|
||||||
|
);
|
||||||
const knowledgeBaseId = searchParams.get('id') || id;
|
const knowledgeBaseId = searchParams.get('id') || id;
|
||||||
|
const fetchFunc =
|
||||||
|
active === LogTabs.DATASET_LOGS
|
||||||
|
? listPipelineDatasetLogs
|
||||||
|
: listDataPipelineLogDocument;
|
||||||
const { data } = useQuery<IFileLogList>({
|
const { data } = useQuery<IFileLogList>({
|
||||||
queryKey: [
|
queryKey: [
|
||||||
'fileLogList',
|
'fileLogList',
|
||||||
@ -78,9 +87,10 @@ const useFetchFileLogList = () => {
|
|||||||
pagination.current,
|
pagination.current,
|
||||||
pagination.pageSize,
|
pagination.pageSize,
|
||||||
searchString,
|
searchString,
|
||||||
|
active,
|
||||||
],
|
],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data: res = {} } = await listDataPipelineLogDocument({
|
const { data: res = {} } = await fetchFunc({
|
||||||
kb_id: knowledgeBaseId,
|
kb_id: knowledgeBaseId,
|
||||||
page: pagination.current,
|
page: pagination.current,
|
||||||
page_size: pagination.pageSize,
|
page_size: pagination.pageSize,
|
||||||
@ -102,6 +112,8 @@ const useFetchFileLogList = () => {
|
|||||||
searchString,
|
searchString,
|
||||||
handleInputChange: onInputChange,
|
handleInputChange: onInputChange,
|
||||||
pagination: { ...pagination, total: data?.total },
|
pagination: { ...pagination, total: data?.total },
|
||||||
|
active,
|
||||||
|
setActive,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -71,9 +71,7 @@ const CardFooterProcess: FC<CardFooterProcessProps> = ({
|
|||||||
};
|
};
|
||||||
const FileLogsPage: FC = () => {
|
const FileLogsPage: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [active, setActive] = useState<(typeof LogTabs)[keyof typeof LogTabs]>(
|
|
||||||
LogTabs.FILE_LOGS,
|
|
||||||
);
|
|
||||||
const [topAllData, setTopAllData] = useState({
|
const [topAllData, setTopAllData] = useState({
|
||||||
totalFiles: {
|
totalFiles: {
|
||||||
value: 0,
|
value: 0,
|
||||||
@ -111,12 +109,14 @@ const FileLogsPage: FC = () => {
|
|||||||
searchString,
|
searchString,
|
||||||
handleInputChange,
|
handleInputChange,
|
||||||
pagination,
|
pagination,
|
||||||
|
active,
|
||||||
|
setActive,
|
||||||
} = useFetchFileLogList();
|
} = useFetchFileLogList();
|
||||||
|
|
||||||
const tableList = useMemo(() => {
|
const tableList = useMemo(() => {
|
||||||
console.log('tableList', tableOriginData);
|
console.log('tableList', tableOriginData);
|
||||||
if (tableOriginData && tableOriginData.docs?.length) {
|
if (tableOriginData && tableOriginData.logs?.length) {
|
||||||
return tableOriginData.docs.map((item) => {
|
return tableOriginData.logs.map((item) => {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
fileName: item.document_name,
|
fileName: item.document_name,
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import {
|
|||||||
import { TFunction } from 'i18next';
|
import { TFunction } from 'i18next';
|
||||||
import { ClipboardList, Eye } from 'lucide-react';
|
import { ClipboardList, Eye } from 'lucide-react';
|
||||||
import { FC, useMemo, useState } from 'react';
|
import { FC, useMemo, useState } from 'react';
|
||||||
|
import { useParams } from 'umi';
|
||||||
import { RunningStatus } from '../dataset/constant';
|
import { RunningStatus } from '../dataset/constant';
|
||||||
import ProcessLogModal from '../process-log-modal';
|
import ProcessLogModal from '../process-log-modal';
|
||||||
import { LogTabs, ProcessingType } from './dataset-common';
|
import { LogTabs, ProcessingType } from './dataset-common';
|
||||||
@ -58,9 +59,11 @@ interface FileLogsTableProps {
|
|||||||
export const getFileLogsTableColumns = (
|
export const getFileLogsTableColumns = (
|
||||||
t: TFunction<'translation', string>,
|
t: TFunction<'translation', string>,
|
||||||
showLog: (row: Row<IFileLogItem & DocumentLog>, active: LogTabs) => void,
|
showLog: (row: Row<IFileLogItem & DocumentLog>, active: LogTabs) => void,
|
||||||
|
kowledgeId: string,
|
||||||
navigateToDataflowResult: (
|
navigateToDataflowResult: (
|
||||||
id: string,
|
id: string,
|
||||||
knowledgeId?: string | undefined,
|
knowledgeId: string,
|
||||||
|
doc_id?: string,
|
||||||
) => () => void,
|
) => () => void,
|
||||||
) => {
|
) => {
|
||||||
// const { t } = useTranslate('knowledgeDetails');
|
// const { t } = useTranslate('knowledgeDetails');
|
||||||
@ -175,7 +178,11 @@ export const getFileLogsTableColumns = (
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="p-1"
|
className="p-1"
|
||||||
onClick={navigateToDataflowResult(row.original.id)}
|
onClick={navigateToDataflowResult(
|
||||||
|
row.original.id,
|
||||||
|
kowledgeId,
|
||||||
|
row.original.document_id,
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<ClipboardList />
|
<ClipboardList />
|
||||||
</Button>
|
</Button>
|
||||||
@ -288,7 +295,8 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
|
|||||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
const { navigateToDataflowResult } = useNavigatePage();
|
const { navigateToDataflowResult } = useNavigatePage();
|
||||||
const [logInfo, setLogInfo] = useState<IFileLogItem>({});
|
const [logInfo, setLogInfo] = useState<IFileLogItem>({});
|
||||||
const showLog = (row: Row<IFileLogItem & DocumentLog>, active: LogTabs) => {
|
const kowledgeId = useParams().id;
|
||||||
|
const showLog = (row: Row<IFileLogItem & DocumentLog>) => {
|
||||||
const logDetail = {
|
const logDetail = {
|
||||||
taskId: row.original.id,
|
taskId: row.original.id,
|
||||||
fileName: row.original.document_name,
|
fileName: row.original.document_name,
|
||||||
@ -306,7 +314,12 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
|
|||||||
|
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
return active === LogTabs.FILE_LOGS
|
return active === LogTabs.FILE_LOGS
|
||||||
? getFileLogsTableColumns(t, showLog, navigateToDataflowResult)
|
? getFileLogsTableColumns(
|
||||||
|
t,
|
||||||
|
showLog,
|
||||||
|
kowledgeId || '',
|
||||||
|
navigateToDataflowResult,
|
||||||
|
)
|
||||||
: getDatasetLogsTableColumns(t, showLog);
|
: getDatasetLogsTableColumns(t, showLog);
|
||||||
}, [active, t]);
|
}, [active, t]);
|
||||||
|
|
||||||
|
|||||||
@ -80,7 +80,7 @@ export const useShowLog = (documents: IDocumentInfo[]) => {
|
|||||||
fileSize: findRecord.size + '',
|
fileSize: findRecord.size + '',
|
||||||
source: findRecord.source_type,
|
source: findRecord.source_type,
|
||||||
task: findRecord.status,
|
task: findRecord.status,
|
||||||
state: findRecord.run,
|
status: findRecord.run,
|
||||||
startTime: findRecord.process_begin_at,
|
startTime: findRecord.process_begin_at,
|
||||||
endTime: findRecord.process_begin_at,
|
endTime: findRecord.process_begin_at,
|
||||||
duration: findRecord.process_duration + 's',
|
duration: findRecord.process_duration + 's',
|
||||||
|
|||||||
@ -7,11 +7,6 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
import {
|
|
||||||
HoverCard,
|
|
||||||
HoverCardContent,
|
|
||||||
HoverCardTrigger,
|
|
||||||
} from '@/components/ui/hover-card';
|
|
||||||
import { Progress } from '@/components/ui/progress';
|
import { Progress } from '@/components/ui/progress';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||||
@ -19,7 +14,7 @@ import { CircleX } from 'lucide-react';
|
|||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DocumentType, RunningStatus } from './constant';
|
import { DocumentType, RunningStatus } from './constant';
|
||||||
import { ParsingCard, PopoverContent } from './parsing-card';
|
import { ParsingCard } from './parsing-card';
|
||||||
import { UseChangeDocumentParserShowType } from './use-change-document-parser';
|
import { UseChangeDocumentParserShowType } from './use-change-document-parser';
|
||||||
import { useHandleRunDocumentByIds } from './use-run-document';
|
import { useHandleRunDocumentByIds } from './use-run-document';
|
||||||
import { UseSaveMetaShowType } from './use-save-meta';
|
import { UseSaveMetaShowType } from './use-save-meta';
|
||||||
@ -122,23 +117,13 @@ export function ParsingStatusCell({
|
|||||||
)}
|
)}
|
||||||
{isParserRunning(run) ? (
|
{isParserRunning(run) ? (
|
||||||
<>
|
<>
|
||||||
<HoverCard>
|
<div
|
||||||
<HoverCardTrigger asChild>
|
className="flex items-center gap-1 cursor-pointer"
|
||||||
<div
|
onClick={() => handleShowLog(record)}
|
||||||
className="flex items-center gap-1"
|
>
|
||||||
onClick={() => handleShowLog(record)}
|
<Progress value={p} className="h-1 flex-1 min-w-10" />
|
||||||
>
|
{p}%
|
||||||
<Progress value={p} className="h-1 flex-1 min-w-10" />
|
</div>
|
||||||
{p}%
|
|
||||||
</div>
|
|
||||||
</HoverCardTrigger>
|
|
||||||
<HoverCardContent className="w-[40vw]">
|
|
||||||
<PopoverContent
|
|
||||||
record={record}
|
|
||||||
handleShowLog={handleShowLog}
|
|
||||||
></PopoverContent>
|
|
||||||
</HoverCardContent>
|
|
||||||
</HoverCard>
|
|
||||||
<div
|
<div
|
||||||
className="cursor-pointer flex items-center gap-3"
|
className="cursor-pointer flex items-center gap-3"
|
||||||
onClick={
|
onClick={
|
||||||
|
|||||||
@ -29,6 +29,7 @@ const {
|
|||||||
fetchAgentLogs,
|
fetchAgentLogs,
|
||||||
fetchExternalAgentInputs,
|
fetchExternalAgentInputs,
|
||||||
prompt,
|
prompt,
|
||||||
|
cancelDataflow,
|
||||||
} = api;
|
} = api;
|
||||||
|
|
||||||
const methods = {
|
const methods = {
|
||||||
@ -120,6 +121,10 @@ const methods = {
|
|||||||
url: prompt,
|
url: prompt,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
},
|
},
|
||||||
|
cancelDataflow: {
|
||||||
|
url: cancelDataflow,
|
||||||
|
method: 'put',
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const agentService = registerNextServer<keyof typeof methods>(methods);
|
const agentService = registerNextServer<keyof typeof methods>(methods);
|
||||||
|
|||||||
@ -41,6 +41,7 @@ const {
|
|||||||
retrievalTestShare,
|
retrievalTestShare,
|
||||||
getKnowledgeBasicInfo,
|
getKnowledgeBasicInfo,
|
||||||
fetchDataPipelineLog,
|
fetchDataPipelineLog,
|
||||||
|
fetchPipelineDatasetLogs,
|
||||||
} = api;
|
} = api;
|
||||||
|
|
||||||
const methods = {
|
const methods = {
|
||||||
@ -179,6 +180,10 @@ const methods = {
|
|||||||
url: fetchDataPipelineLog,
|
url: fetchDataPipelineLog,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
},
|
},
|
||||||
|
fetchPipelineDatasetLogs: {
|
||||||
|
url: fetchPipelineDatasetLogs,
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
get_pipeline_detail: {
|
get_pipeline_detail: {
|
||||||
url: api.get_pipeline_detail,
|
url: api.get_pipeline_detail,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
@ -223,5 +228,9 @@ export const listDataPipelineLogDocument = (
|
|||||||
params?: IFetchKnowledgeListRequestParams,
|
params?: IFetchKnowledgeListRequestParams,
|
||||||
body?: IFetchDocumentListRequestBody,
|
body?: IFetchDocumentListRequestBody,
|
||||||
) => request.post(api.fetchDataPipelineLog, { data: body || {}, params });
|
) => request.post(api.fetchDataPipelineLog, { data: body || {}, params });
|
||||||
|
export const listPipelineDatasetLogs = (
|
||||||
|
params?: IFetchKnowledgeListRequestParams,
|
||||||
|
body?: IFetchDocumentListRequestBody,
|
||||||
|
) => request.post(api.fetchPipelineDatasetLogs, { data: body || {}, params });
|
||||||
|
|
||||||
export default kbService;
|
export default kbService;
|
||||||
|
|||||||
@ -49,6 +49,7 @@ export default {
|
|||||||
// data pipeline log
|
// data pipeline log
|
||||||
fetchDataPipelineLog: `${api_host}/kb/list_pipeline_logs`,
|
fetchDataPipelineLog: `${api_host}/kb/list_pipeline_logs`,
|
||||||
get_pipeline_detail: `${api_host}/kb/pipeline_log_detail`,
|
get_pipeline_detail: `${api_host}/kb/pipeline_log_detail`,
|
||||||
|
fetchPipelineDatasetLogs: `${api_host}/kb/list_pipeline_dataset_logs`,
|
||||||
|
|
||||||
// tags
|
// tags
|
||||||
listTag: (knowledgeId: string) => `${api_host}/kb/${knowledgeId}/tags`,
|
listTag: (knowledgeId: string) => `${api_host}/kb/${knowledgeId}/tags`,
|
||||||
@ -169,6 +170,7 @@ export default {
|
|||||||
fetchExternalAgentInputs: (canvasId: string) =>
|
fetchExternalAgentInputs: (canvasId: string) =>
|
||||||
`${ExternalApi}${api_host}/agentbots/${canvasId}/inputs`,
|
`${ExternalApi}${api_host}/agentbots/${canvasId}/inputs`,
|
||||||
prompt: `${api_host}/canvas/prompts`,
|
prompt: `${api_host}/canvas/prompts`,
|
||||||
|
cancelDataflow: (id: string) => `${api_host}/canvas/cancel/${id}`,
|
||||||
|
|
||||||
// mcp server
|
// mcp server
|
||||||
listMcpServer: `${api_host}/mcp_server/list`,
|
listMcpServer: `${api_host}/mcp_server/list`,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { toFixed } from './common-util';
|
||||||
|
|
||||||
export function formatDate(date: any) {
|
export function formatDate(date: any) {
|
||||||
if (!date) {
|
if (!date) {
|
||||||
@ -43,3 +44,20 @@ export function formatStandardDate(date: any) {
|
|||||||
}
|
}
|
||||||
return parsedDate.format('YYYY-MM-DD');
|
return parsedDate.format('YYYY-MM-DD');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatSecondsToHumanReadable(seconds: number): string {
|
||||||
|
if (isNaN(seconds) || seconds < 0) {
|
||||||
|
return '0s';
|
||||||
|
}
|
||||||
|
|
||||||
|
const h = Math.floor(seconds / 3600);
|
||||||
|
const m = Math.floor((seconds % 3600) / 60);
|
||||||
|
const s = toFixed(seconds % 60, 3);
|
||||||
|
|
||||||
|
const parts = [];
|
||||||
|
if (h > 0) parts.push(`${h}h`);
|
||||||
|
if (m > 0) parts.push(`${m}m`);
|
||||||
|
if (s || parts.length === 0) parts.push(`${s}s`);
|
||||||
|
|
||||||
|
return parts.join('');
|
||||||
|
}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
border: 'var(--colors-outline-neutral-strong)',
|
border: 'var(--border-default)',
|
||||||
input: 'hsl(var(--input))',
|
input: 'hsl(var(--input))',
|
||||||
ring: 'hsl(var(--ring))',
|
ring: 'hsl(var(--ring))',
|
||||||
background: 'var(--background)',
|
background: 'var(--background)',
|
||||||
|
|||||||
@ -100,7 +100,6 @@
|
|||||||
--bg-card: rgba(0, 0, 0, 0.05);
|
--bg-card: rgba(0, 0, 0, 0.05);
|
||||||
--bg-component: #ffffff;
|
--bg-component: #ffffff;
|
||||||
--bg-input: rgba(255, 255, 255, 0);
|
--bg-input: rgba(255, 255, 255, 0);
|
||||||
--bg-accent: rgba(76, 164, 231, 0.05);
|
|
||||||
/* Button ,Body text, Input completed text */
|
/* Button ,Body text, Input completed text */
|
||||||
--text-primary: #161618;
|
--text-primary: #161618;
|
||||||
--text-secondary: #75787a;
|
--text-secondary: #75787a;
|
||||||
|
|||||||
Reference in New Issue
Block a user