mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Feat: Delete flow related code. #9869 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -1,79 +1,14 @@
|
|||||||
import { ResponseType } from '@/interfaces/database/base';
|
import { DSL, IFlow } from '@/interfaces/database/flow';
|
||||||
import { DSL, IFlow, IFlowTemplate } from '@/interfaces/database/flow';
|
|
||||||
import { IDebugSingleRequestBody } from '@/interfaces/request/flow';
|
import { IDebugSingleRequestBody } from '@/interfaces/request/flow';
|
||||||
import i18n from '@/locales/config';
|
import i18n from '@/locales/config';
|
||||||
import { useGetSharedChatSearchParams } from '@/pages/chat/shared-hooks';
|
import { useGetSharedChatSearchParams } from '@/pages/chat/shared-hooks';
|
||||||
import { BeginId } from '@/pages/flow/constant';
|
|
||||||
import flowService from '@/services/flow-service';
|
import flowService from '@/services/flow-service';
|
||||||
import { buildMessageListWithUuid } from '@/utils/chat';
|
import { buildMessageListWithUuid } from '@/utils/chat';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { set } from 'lodash';
|
import { set } from 'lodash';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useParams } from 'umi';
|
import { useParams } from 'umi';
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
|
|
||||||
export const EmptyDsl = {
|
|
||||||
graph: {
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
id: BeginId,
|
|
||||||
type: 'beginNode',
|
|
||||||
position: {
|
|
||||||
x: 50,
|
|
||||||
y: 200,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
label: 'Begin',
|
|
||||||
name: 'begin',
|
|
||||||
},
|
|
||||||
sourcePosition: 'left',
|
|
||||||
targetPosition: 'right',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
edges: [],
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
begin: {
|
|
||||||
obj: {
|
|
||||||
component_name: 'Begin',
|
|
||||||
params: {},
|
|
||||||
},
|
|
||||||
downstream: ['Answer:China'], // other edge target is downstream, edge source is current node id
|
|
||||||
upstream: [], // edge source is upstream, edge target is current node id
|
|
||||||
},
|
|
||||||
},
|
|
||||||
messages: [],
|
|
||||||
reference: [],
|
|
||||||
history: [],
|
|
||||||
path: [],
|
|
||||||
answer: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useFetchFlowTemplates = (): ResponseType<IFlowTemplate[]> => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const { data } = useQuery({
|
|
||||||
queryKey: ['fetchFlowTemplates'],
|
|
||||||
initialData: [],
|
|
||||||
queryFn: async () => {
|
|
||||||
const { data } = await flowService.listTemplates();
|
|
||||||
if (Array.isArray(data?.data)) {
|
|
||||||
data.data.unshift({
|
|
||||||
id: uuid(),
|
|
||||||
title: t('flow.blank'),
|
|
||||||
description: t('flow.createFromNothing'),
|
|
||||||
dsl: EmptyDsl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useFetchFlowList = (): { data: IFlow[]; loading: boolean } => {
|
export const useFetchFlowList = (): { data: IFlow[]; loading: boolean } => {
|
||||||
const { data, isFetching: loading } = useQuery({
|
const { data, isFetching: loading } = useQuery({
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
.contextMenu {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
border-style: solid;
|
|
||||||
box-shadow: 10px 19px 20px rgba(0, 0, 0, 10%);
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10;
|
|
||||||
button {
|
|
||||||
border: none;
|
|
||||||
display: block;
|
|
||||||
padding: 0.5em;
|
|
||||||
text-align: left;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
import { NodeMouseHandler, useReactFlow } from '@xyflow/react';
|
|
||||||
import { useCallback, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
export interface INodeContextMenu {
|
|
||||||
id: string;
|
|
||||||
top: number;
|
|
||||||
left: number;
|
|
||||||
right?: number;
|
|
||||||
bottom?: number;
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NodeContextMenu({
|
|
||||||
id,
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
bottom,
|
|
||||||
...props
|
|
||||||
}: INodeContextMenu) {
|
|
||||||
const { getNode, setNodes, addNodes, setEdges } = useReactFlow();
|
|
||||||
|
|
||||||
const duplicateNode = useCallback(() => {
|
|
||||||
const node = getNode(id);
|
|
||||||
const position = {
|
|
||||||
x: node?.position?.x || 0 + 50,
|
|
||||||
y: node?.position?.y || 0 + 50,
|
|
||||||
};
|
|
||||||
|
|
||||||
addNodes({
|
|
||||||
...(node || {}),
|
|
||||||
data: node?.data,
|
|
||||||
selected: false,
|
|
||||||
dragging: false,
|
|
||||||
id: `${node?.id}-copy`,
|
|
||||||
position,
|
|
||||||
});
|
|
||||||
}, [id, getNode, addNodes]);
|
|
||||||
|
|
||||||
const deleteNode = useCallback(() => {
|
|
||||||
setNodes((nodes) => nodes.filter((node) => node.id !== id));
|
|
||||||
setEdges((edges) => edges.filter((edge) => edge.source !== id));
|
|
||||||
}, [id, setNodes, setEdges]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{ top, left, right, bottom }}
|
|
||||||
className={styles.contextMenu}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<p style={{ margin: '0.5em' }}>
|
|
||||||
<small>node: {id}</small>
|
|
||||||
</p>
|
|
||||||
<button onClick={duplicateNode} type={'button'}>
|
|
||||||
duplicate
|
|
||||||
</button>
|
|
||||||
<button onClick={deleteNode} type={'button'}>
|
|
||||||
delete
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @deprecated
|
|
||||||
*/
|
|
||||||
export const useHandleNodeContextMenu = (sideWidth: number) => {
|
|
||||||
const [menu, setMenu] = useState<INodeContextMenu>({} as INodeContextMenu);
|
|
||||||
const ref = useRef<any>(null);
|
|
||||||
|
|
||||||
const onNodeContextMenu: NodeMouseHandler = useCallback(
|
|
||||||
(event, node) => {
|
|
||||||
// Prevent native context menu from showing
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
// Calculate position of the context menu. We want to make sure it
|
|
||||||
// doesn't get positioned off-screen.
|
|
||||||
const pane = ref.current?.getBoundingClientRect();
|
|
||||||
// setMenu({
|
|
||||||
// id: node.id,
|
|
||||||
// top: event.clientY < pane.height - 200 ? event.clientY : 0,
|
|
||||||
// left: event.clientX < pane.width - 200 ? event.clientX : 0,
|
|
||||||
// right: event.clientX >= pane.width - 200 ? pane.width - event.clientX : 0,
|
|
||||||
// bottom:
|
|
||||||
// event.clientY >= pane.height - 200 ? pane.height - event.clientY : 0,
|
|
||||||
// });
|
|
||||||
|
|
||||||
setMenu({
|
|
||||||
id: node.id,
|
|
||||||
top: event.clientY - 144,
|
|
||||||
left: event.clientX - sideWidth,
|
|
||||||
// top: event.clientY < pane.height - 200 ? event.clientY - 72 : 0,
|
|
||||||
// left: event.clientX < pane.width - 200 ? event.clientX : 0,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[sideWidth],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Close the context menu if it's open whenever the window is clicked.
|
|
||||||
const onPaneClick = useCallback(
|
|
||||||
() => setMenu({} as INodeContextMenu),
|
|
||||||
[setMenu],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { onNodeContextMenu, menu, onPaneClick, ref };
|
|
||||||
};
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
.edgeButton {
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
background: #eee;
|
|
||||||
border: 1px solid #fff;
|
|
||||||
padding: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 50%;
|
|
||||||
font-size: 10px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edgeButton:hover {
|
|
||||||
box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.edgeButtonDark {
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
background: #0e0c0c;
|
|
||||||
border: 1px solid #fff;
|
|
||||||
padding: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 50%;
|
|
||||||
font-size: 10px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edgeButtonDark:hover {
|
|
||||||
box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
@ -1,108 +0,0 @@
|
|||||||
import {
|
|
||||||
BaseEdge,
|
|
||||||
EdgeLabelRenderer,
|
|
||||||
EdgeProps,
|
|
||||||
getBezierPath,
|
|
||||||
} from '@xyflow/react';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
|
|
||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
export function ButtonEdge({
|
|
||||||
id,
|
|
||||||
sourceX,
|
|
||||||
sourceY,
|
|
||||||
targetX,
|
|
||||||
targetY,
|
|
||||||
sourcePosition,
|
|
||||||
targetPosition,
|
|
||||||
source,
|
|
||||||
target,
|
|
||||||
style = {},
|
|
||||||
markerEnd,
|
|
||||||
selected,
|
|
||||||
}: EdgeProps) {
|
|
||||||
const deleteEdgeById = useGraphStore((state) => state.deleteEdgeById);
|
|
||||||
const [edgePath, labelX, labelY] = getBezierPath({
|
|
||||||
sourceX,
|
|
||||||
sourceY,
|
|
||||||
sourcePosition,
|
|
||||||
targetX,
|
|
||||||
targetY,
|
|
||||||
targetPosition,
|
|
||||||
});
|
|
||||||
const { theme } = useTheme();
|
|
||||||
const selectedStyle = useMemo(() => {
|
|
||||||
return selected ? { strokeWidth: 2, stroke: '#1677ff' } : {};
|
|
||||||
}, [selected]);
|
|
||||||
|
|
||||||
const onEdgeClick = () => {
|
|
||||||
deleteEdgeById(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
// highlight the nodes that the workflow passes through
|
|
||||||
const { data: flowDetail } = useFetchFlow();
|
|
||||||
|
|
||||||
const graphPath = useMemo(() => {
|
|
||||||
// TODO: this will be called multiple times
|
|
||||||
const path = flowDetail?.dsl?.path ?? [];
|
|
||||||
// The second to last
|
|
||||||
const previousGraphPath: string[] = path.at(-2) ?? [];
|
|
||||||
let graphPath: string[] = path.at(-1) ?? [];
|
|
||||||
// The last of the second to last article
|
|
||||||
const previousLatestElement = previousGraphPath.at(-1);
|
|
||||||
if (previousGraphPath.length > 0 && previousLatestElement) {
|
|
||||||
graphPath = [previousLatestElement, ...graphPath];
|
|
||||||
}
|
|
||||||
return graphPath;
|
|
||||||
}, [flowDetail.dsl?.path]);
|
|
||||||
|
|
||||||
const highlightStyle = useMemo(() => {
|
|
||||||
const idx = graphPath.findIndex((x) => x === source);
|
|
||||||
if (idx !== -1) {
|
|
||||||
// The set of elements following source
|
|
||||||
const slicedGraphPath = graphPath.slice(idx + 1);
|
|
||||||
if (slicedGraphPath.some((x) => x === target)) {
|
|
||||||
return { strokeWidth: 2, stroke: 'red' };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}, [source, target, graphPath]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<BaseEdge
|
|
||||||
path={edgePath}
|
|
||||||
markerEnd={markerEnd}
|
|
||||||
style={{ ...style, ...selectedStyle, ...highlightStyle }}
|
|
||||||
/>
|
|
||||||
<EdgeLabelRenderer>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
|
||||||
fontSize: 12,
|
|
||||||
// everything inside EdgeLabelRenderer has no pointer events by default
|
|
||||||
// if you have an interactive element, set pointer-events: all
|
|
||||||
pointerEvents: 'all',
|
|
||||||
zIndex: 1001, // https://github.com/xyflow/xyflow/discussions/3498
|
|
||||||
}}
|
|
||||||
className="nodrag nopan"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className={
|
|
||||||
theme === 'dark' ? styles.edgeButtonDark : styles.edgeButton
|
|
||||||
}
|
|
||||||
type="button"
|
|
||||||
onClick={onEdgeClick}
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</EdgeLabelRenderer>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
.canvasWrapper {
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
:global(.react-flow__node-group) {
|
|
||||||
.commonNode();
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,237 +0,0 @@
|
|||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from '@/components/ui/tooltip';
|
|
||||||
import {
|
|
||||||
Background,
|
|
||||||
ConnectionMode,
|
|
||||||
ControlButton,
|
|
||||||
Controls,
|
|
||||||
NodeTypes,
|
|
||||||
ReactFlow,
|
|
||||||
} from '@xyflow/react';
|
|
||||||
import '@xyflow/react/dist/style.css';
|
|
||||||
import { Book, FolderInput, FolderOutput } from 'lucide-react';
|
|
||||||
import ChatDrawer from '../chat/drawer';
|
|
||||||
import FormDrawer from '../flow-drawer';
|
|
||||||
import {
|
|
||||||
useHandleDrop,
|
|
||||||
useSelectCanvasData,
|
|
||||||
useValidateConnection,
|
|
||||||
useWatchNodeFormDataChange,
|
|
||||||
} from '../hooks';
|
|
||||||
import { useBeforeDelete } from '../hooks/use-before-delete';
|
|
||||||
import { useHandleExportOrImportJsonFile } from '../hooks/use-export-json';
|
|
||||||
import { useOpenDocument } from '../hooks/use-open-document';
|
|
||||||
import { useShowDrawer } from '../hooks/use-show-drawer';
|
|
||||||
import JsonUploadModal from '../json-upload-modal';
|
|
||||||
import RunDrawer from '../run-drawer';
|
|
||||||
import { ButtonEdge } from './edge';
|
|
||||||
import styles from './index.less';
|
|
||||||
import { RagNode } from './node';
|
|
||||||
import { BeginNode } from './node/begin-node';
|
|
||||||
import { CategorizeNode } from './node/categorize-node';
|
|
||||||
import { EmailNode } from './node/email-node';
|
|
||||||
import { GenerateNode } from './node/generate-node';
|
|
||||||
import { InvokeNode } from './node/invoke-node';
|
|
||||||
import { IterationNode, IterationStartNode } from './node/iteration-node';
|
|
||||||
import { KeywordNode } from './node/keyword-node';
|
|
||||||
import { LogicNode } from './node/logic-node';
|
|
||||||
import { MessageNode } from './node/message-node';
|
|
||||||
import NoteNode from './node/note-node';
|
|
||||||
import { RelevantNode } from './node/relevant-node';
|
|
||||||
import { RetrievalNode } from './node/retrieval-node';
|
|
||||||
import { RewriteNode } from './node/rewrite-node';
|
|
||||||
import { SwitchNode } from './node/switch-node';
|
|
||||||
import { TemplateNode } from './node/template-node';
|
|
||||||
|
|
||||||
export const nodeTypes: NodeTypes = {
|
|
||||||
ragNode: RagNode,
|
|
||||||
categorizeNode: CategorizeNode,
|
|
||||||
beginNode: BeginNode,
|
|
||||||
relevantNode: RelevantNode,
|
|
||||||
logicNode: LogicNode,
|
|
||||||
noteNode: NoteNode,
|
|
||||||
switchNode: SwitchNode,
|
|
||||||
generateNode: GenerateNode,
|
|
||||||
retrievalNode: RetrievalNode,
|
|
||||||
messageNode: MessageNode,
|
|
||||||
rewriteNode: RewriteNode,
|
|
||||||
keywordNode: KeywordNode,
|
|
||||||
invokeNode: InvokeNode,
|
|
||||||
templateNode: TemplateNode,
|
|
||||||
emailNode: EmailNode,
|
|
||||||
group: IterationNode,
|
|
||||||
iterationStartNode: IterationStartNode,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const edgeTypes = {
|
|
||||||
buttonEdge: ButtonEdge,
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
drawerVisible: boolean;
|
|
||||||
hideDrawer(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|
||||||
const {
|
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
onConnect,
|
|
||||||
onEdgesChange,
|
|
||||||
onNodesChange,
|
|
||||||
onSelectionChange,
|
|
||||||
} = useSelectCanvasData();
|
|
||||||
const isValidConnection = useValidateConnection();
|
|
||||||
|
|
||||||
const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop();
|
|
||||||
|
|
||||||
const {
|
|
||||||
handleExportJson,
|
|
||||||
handleImportJson,
|
|
||||||
fileUploadVisible,
|
|
||||||
onFileUploadOk,
|
|
||||||
hideFileUploadModal,
|
|
||||||
} = useHandleExportOrImportJsonFile();
|
|
||||||
|
|
||||||
const openDocument = useOpenDocument();
|
|
||||||
|
|
||||||
const {
|
|
||||||
onNodeClick,
|
|
||||||
onPaneClick,
|
|
||||||
clickedNode,
|
|
||||||
formDrawerVisible,
|
|
||||||
hideFormDrawer,
|
|
||||||
singleDebugDrawerVisible,
|
|
||||||
hideSingleDebugDrawer,
|
|
||||||
showSingleDebugDrawer,
|
|
||||||
chatVisible,
|
|
||||||
runVisible,
|
|
||||||
hideRunOrChatDrawer,
|
|
||||||
showChatModal,
|
|
||||||
} = useShowDrawer({
|
|
||||||
drawerVisible,
|
|
||||||
hideDrawer,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { handleBeforeDelete } = useBeforeDelete();
|
|
||||||
|
|
||||||
useWatchNodeFormDataChange();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.canvasWrapper}>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
style={{ position: 'absolute', top: 10, left: 0 }}
|
|
||||||
>
|
|
||||||
<defs>
|
|
||||||
<marker
|
|
||||||
fill="rgb(157 149 225)"
|
|
||||||
id="logo"
|
|
||||||
viewBox="0 0 40 40"
|
|
||||||
refX="8"
|
|
||||||
refY="5"
|
|
||||||
markerUnits="strokeWidth"
|
|
||||||
markerWidth="20"
|
|
||||||
markerHeight="20"
|
|
||||||
orient="auto-start-reverse"
|
|
||||||
>
|
|
||||||
<path d="M 0 0 L 10 5 L 0 10 z" />
|
|
||||||
</marker>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
<ReactFlow
|
|
||||||
connectionMode={ConnectionMode.Loose}
|
|
||||||
nodes={nodes}
|
|
||||||
onNodesChange={onNodesChange}
|
|
||||||
edges={edges}
|
|
||||||
onEdgesChange={onEdgesChange}
|
|
||||||
fitView
|
|
||||||
onConnect={onConnect}
|
|
||||||
nodeTypes={nodeTypes}
|
|
||||||
edgeTypes={edgeTypes}
|
|
||||||
onDrop={onDrop}
|
|
||||||
onDragOver={onDragOver}
|
|
||||||
onNodeClick={onNodeClick}
|
|
||||||
onPaneClick={onPaneClick}
|
|
||||||
onInit={setReactFlowInstance}
|
|
||||||
onSelectionChange={onSelectionChange}
|
|
||||||
nodeOrigin={[0.5, 0]}
|
|
||||||
isValidConnection={isValidConnection}
|
|
||||||
defaultEdgeOptions={{
|
|
||||||
type: 'buttonEdge',
|
|
||||||
markerEnd: 'logo',
|
|
||||||
style: {
|
|
||||||
strokeWidth: 2,
|
|
||||||
stroke: 'rgb(202 197 245)',
|
|
||||||
},
|
|
||||||
zIndex: 1001, // https://github.com/xyflow/xyflow/discussions/3498
|
|
||||||
}}
|
|
||||||
deleteKeyCode={['Delete', 'Backspace']}
|
|
||||||
onBeforeDelete={handleBeforeDelete}
|
|
||||||
>
|
|
||||||
<Background />
|
|
||||||
<Controls className="text-black !flex-col-reverse">
|
|
||||||
<ControlButton onClick={handleImportJson}>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<FolderInput className="!fill-none" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>Import</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</ControlButton>
|
|
||||||
<ControlButton onClick={handleExportJson}>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<FolderOutput className="!fill-none" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>Export</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</ControlButton>
|
|
||||||
<ControlButton onClick={openDocument}>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Book className="!fill-none" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>Document</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</ControlButton>
|
|
||||||
</Controls>
|
|
||||||
</ReactFlow>
|
|
||||||
{formDrawerVisible && (
|
|
||||||
<FormDrawer
|
|
||||||
node={clickedNode}
|
|
||||||
visible={formDrawerVisible}
|
|
||||||
hideModal={hideFormDrawer}
|
|
||||||
singleDebugDrawerVisible={singleDebugDrawerVisible}
|
|
||||||
hideSingleDebugDrawer={hideSingleDebugDrawer}
|
|
||||||
showSingleDebugDrawer={showSingleDebugDrawer}
|
|
||||||
></FormDrawer>
|
|
||||||
)}
|
|
||||||
{chatVisible && (
|
|
||||||
<ChatDrawer
|
|
||||||
visible={chatVisible}
|
|
||||||
hideModal={hideRunOrChatDrawer}
|
|
||||||
></ChatDrawer>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{runVisible && (
|
|
||||||
<RunDrawer
|
|
||||||
hideModal={hideRunOrChatDrawer}
|
|
||||||
showModal={showChatModal}
|
|
||||||
></RunDrawer>
|
|
||||||
)}
|
|
||||||
{fileUploadVisible && (
|
|
||||||
<JsonUploadModal
|
|
||||||
onOk={onFileUploadOk}
|
|
||||||
visible={fileUploadVisible}
|
|
||||||
hideModal={hideFileUploadModal}
|
|
||||||
></JsonUploadModal>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FlowCanvas;
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { IBeginNode } from '@/interfaces/database/flow';
|
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
|
||||||
import { Flex } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import get from 'lodash/get';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import {
|
|
||||||
BeginQueryType,
|
|
||||||
BeginQueryTypeIconMap,
|
|
||||||
Operator,
|
|
||||||
operatorMap,
|
|
||||||
} from '../../constant';
|
|
||||||
import { BeginQuery } from '../../interface';
|
|
||||||
import OperatorIcon from '../../operator-icon';
|
|
||||||
import { RightHandleStyle } from './handle-icon';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
// TODO: do not allow other nodes to connect to this node
|
|
||||||
export function BeginNode({ selected, data }: NodeProps<IBeginNode>) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const query: BeginQuery[] = get(data, 'form.query', []);
|
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={classNames(
|
|
||||||
styles.ragNode,
|
|
||||||
theme === 'dark' ? styles.dark : '',
|
|
||||||
{
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable
|
|
||||||
className={styles.handle}
|
|
||||||
style={RightHandleStyle}
|
|
||||||
></Handle>
|
|
||||||
|
|
||||||
<Flex align="center" justify={'center'} gap={10}>
|
|
||||||
<OperatorIcon
|
|
||||||
name={data.label as Operator}
|
|
||||||
fontSize={24}
|
|
||||||
color={operatorMap[data.label as Operator].color}
|
|
||||||
></OperatorIcon>
|
|
||||||
<div className="truncate text-center font-semibold text-sm">
|
|
||||||
{t(`flow.begin`)}
|
|
||||||
</div>
|
|
||||||
</Flex>
|
|
||||||
<Flex gap={8} vertical className={styles.generateParameters}>
|
|
||||||
{query.map((x, idx) => {
|
|
||||||
const Icon = BeginQueryTypeIconMap[x.type as BeginQueryType];
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
key={idx}
|
|
||||||
align="center"
|
|
||||||
gap={6}
|
|
||||||
className={styles.conditionBlock}
|
|
||||||
>
|
|
||||||
<Icon className="size-4" />
|
|
||||||
<label htmlFor="">{x.key}</label>
|
|
||||||
<span className={styles.parameterValue}>{x.name}</span>
|
|
||||||
<span className="flex-1">{x.optional ? 'Yes' : 'No'}</span>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from '@/components/ui/card';
|
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/components/ui/select';
|
|
||||||
|
|
||||||
export function CardWithForm() {
|
|
||||||
return (
|
|
||||||
<Card className="w-[350px]">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Create project</CardTitle>
|
|
||||||
<CardDescription>Deploy your new project in one-click.</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<form>
|
|
||||||
<div className="grid w-full items-center gap-4">
|
|
||||||
<div className="flex flex-col space-y-1.5">
|
|
||||||
<Label htmlFor="name">Name</Label>
|
|
||||||
<Input id="name" placeholder="Name of your project" />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col space-y-1.5">
|
|
||||||
<Label htmlFor="framework">Framework</Label>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger id="framework">
|
|
||||||
<SelectValue placeholder="Select" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent position="popper">
|
|
||||||
<SelectItem value="next">Next.js</SelectItem>
|
|
||||||
<SelectItem value="sveltekit">SvelteKit</SelectItem>
|
|
||||||
<SelectItem value="astro">Astro</SelectItem>
|
|
||||||
<SelectItem value="nuxt">Nuxt.js</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="flex justify-between">
|
|
||||||
<Button variant="outline">Cancel</Button>
|
|
||||||
<Button>Deploy</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
import { Handle, Position } from '@xyflow/react';
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
const DEFAULT_HANDLE_STYLE = {
|
|
||||||
width: 6,
|
|
||||||
height: 6,
|
|
||||||
bottom: -5,
|
|
||||||
fontSize: 8,
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IProps extends React.PropsWithChildren {
|
|
||||||
top: number;
|
|
||||||
right: number;
|
|
||||||
id: string;
|
|
||||||
idx?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CategorizeHandle = ({ top, right, id, children }: IProps) => {
|
|
||||||
return (
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
id={id}
|
|
||||||
isConnectable
|
|
||||||
style={{
|
|
||||||
...DEFAULT_HANDLE_STYLE,
|
|
||||||
top: `${top}%`,
|
|
||||||
right: `${right}%`,
|
|
||||||
background: 'red',
|
|
||||||
color: 'black',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className={styles.categorizeAnchorPointText}>{children || id}</span>
|
|
||||||
</Handle>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CategorizeHandle;
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import LLMLabel from '@/components/llm-select/llm-label';
|
|
||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { ICategorizeNode } from '@/interfaces/database/flow';
|
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
|
||||||
import { Flex } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import { RightHandleStyle } from './handle-icon';
|
|
||||||
import { useBuildCategorizeHandlePositions } from './hooks';
|
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
|
||||||
|
|
||||||
export function CategorizeNode({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
selected,
|
|
||||||
}: NodeProps<ICategorizeNode>) {
|
|
||||||
const { positions } = useBuildCategorizeHandlePositions({ data, id });
|
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={classNames(
|
|
||||||
styles.logicNode,
|
|
||||||
theme === 'dark' ? styles.dark : '',
|
|
||||||
{
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
type="target"
|
|
||||||
position={Position.Left}
|
|
||||||
isConnectable
|
|
||||||
className={styles.handle}
|
|
||||||
id={'a'}
|
|
||||||
></Handle>
|
|
||||||
|
|
||||||
<NodeHeader
|
|
||||||
id={id}
|
|
||||||
name={data.name}
|
|
||||||
label={data.label}
|
|
||||||
className={styles.nodeHeader}
|
|
||||||
></NodeHeader>
|
|
||||||
|
|
||||||
<Flex vertical gap={8}>
|
|
||||||
<div className={styles.nodeText}>
|
|
||||||
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
|
|
||||||
</div>
|
|
||||||
{positions.map((position, idx) => {
|
|
||||||
return (
|
|
||||||
<div key={idx}>
|
|
||||||
<div className={styles.nodeText}>{position.text}</div>
|
|
||||||
<Handle
|
|
||||||
key={position.text}
|
|
||||||
id={position.text}
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable
|
|
||||||
className={styles.handle}
|
|
||||||
style={{ ...RightHandleStyle, top: position.top }}
|
|
||||||
></Handle>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
import OperateDropdown from '@/components/operate-dropdown';
|
|
||||||
import { CopyOutlined } from '@ant-design/icons';
|
|
||||||
import { Flex, MenuProps } from 'antd';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { Operator } from '../../constant';
|
|
||||||
import { useDuplicateNode } from '../../hooks';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
id: string;
|
|
||||||
iconFontColor?: string;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const NodeDropdown = ({ id, iconFontColor, label }: IProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
|
|
||||||
const deleteIterationNodeById = useGraphStore(
|
|
||||||
(store) => store.deleteIterationNodeById,
|
|
||||||
);
|
|
||||||
|
|
||||||
const deleteNode = useCallback(() => {
|
|
||||||
if (label === Operator.Iteration) {
|
|
||||||
deleteIterationNodeById(id);
|
|
||||||
} else {
|
|
||||||
deleteNodeById(id);
|
|
||||||
}
|
|
||||||
}, [label, deleteIterationNodeById, id, deleteNodeById]);
|
|
||||||
|
|
||||||
const duplicateNode = useDuplicateNode();
|
|
||||||
|
|
||||||
const items: MenuProps['items'] = [
|
|
||||||
{
|
|
||||||
key: '2',
|
|
||||||
onClick: () => duplicateNode(id, label),
|
|
||||||
label: (
|
|
||||||
<Flex justify={'space-between'}>
|
|
||||||
{t('common.copy')}
|
|
||||||
<CopyOutlined />
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<OperateDropdown
|
|
||||||
iconFontSize={22}
|
|
||||||
height={14}
|
|
||||||
deleteItem={deleteNode}
|
|
||||||
items={items}
|
|
||||||
needsDeletionValidation={false}
|
|
||||||
iconFontColor={iconFontColor}
|
|
||||||
></OperateDropdown>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NodeDropdown;
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
import { IEmailNode } from '@/interfaces/database/flow';
|
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
|
||||||
import { Flex } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
|
||||||
|
|
||||||
export function EmailNode({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
isConnectable = true,
|
|
||||||
selected,
|
|
||||||
}: NodeProps<IEmailNode>) {
|
|
||||||
const [showDetails, setShowDetails] = useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={classNames(styles.ragNode, {
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
id="c"
|
|
||||||
type="source"
|
|
||||||
position={Position.Left}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={LeftHandleStyle}
|
|
||||||
></Handle>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={RightHandleStyle}
|
|
||||||
id="b"
|
|
||||||
></Handle>
|
|
||||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
|
||||||
|
|
||||||
<Flex vertical gap={8} className={styles.emailNodeContainer}>
|
|
||||||
<div
|
|
||||||
className={styles.emailConfig}
|
|
||||||
onClick={() => setShowDetails(!showDetails)}
|
|
||||||
>
|
|
||||||
<div className={styles.configItem}>
|
|
||||||
<span className={styles.configLabel}>SMTP:</span>
|
|
||||||
<span className={styles.configValue}>{data.form?.smtp_server}</span>
|
|
||||||
</div>
|
|
||||||
<div className={styles.configItem}>
|
|
||||||
<span className={styles.configLabel}>Port:</span>
|
|
||||||
<span className={styles.configValue}>{data.form?.smtp_port}</span>
|
|
||||||
</div>
|
|
||||||
<div className={styles.configItem}>
|
|
||||||
<span className={styles.configLabel}>From:</span>
|
|
||||||
<span className={styles.configValue}>{data.form?.email}</span>
|
|
||||||
</div>
|
|
||||||
<div className={styles.expandIcon}>{showDetails ? '▼' : '▶'}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{showDetails && (
|
|
||||||
<div className={styles.jsonExample}>
|
|
||||||
<div className={styles.jsonTitle}>Expected Input JSON:</div>
|
|
||||||
<pre className={styles.jsonContent}>
|
|
||||||
{`{
|
|
||||||
"to_email": "...",
|
|
||||||
"cc_email": "...",
|
|
||||||
"subject": "...",
|
|
||||||
"content": "..."
|
|
||||||
}`}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
import LLMLabel from '@/components/llm-select/llm-label';
|
|
||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { IGenerateNode } from '@/interfaces/database/flow';
|
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
|
||||||
|
|
||||||
export function GenerateNode({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
isConnectable = true,
|
|
||||||
selected,
|
|
||||||
}: NodeProps<IGenerateNode>) {
|
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={classNames(
|
|
||||||
styles.logicNode,
|
|
||||||
theme === 'dark' ? styles.dark : '',
|
|
||||||
{
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
id="c"
|
|
||||||
type="source"
|
|
||||||
position={Position.Left}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={LeftHandleStyle}
|
|
||||||
></Handle>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={RightHandleStyle}
|
|
||||||
id="b"
|
|
||||||
></Handle>
|
|
||||||
|
|
||||||
<NodeHeader
|
|
||||||
id={id}
|
|
||||||
name={data.name}
|
|
||||||
label={data.label}
|
|
||||||
className={styles.nodeHeader}
|
|
||||||
></NodeHeader>
|
|
||||||
|
|
||||||
<div className={styles.nodeText}>
|
|
||||||
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { CSSProperties } from 'react';
|
|
||||||
|
|
||||||
export const HandleIcon = () => {
|
|
||||||
return (
|
|
||||||
<PlusOutlined
|
|
||||||
style={{ fontSize: 6, color: 'white', position: 'absolute', zIndex: 10 }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RightHandleStyle: CSSProperties = {
|
|
||||||
right: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LeftHandleStyle: CSSProperties = {
|
|
||||||
left: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default HandleIcon;
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
import { useUpdateNodeInternals } from '@xyflow/react';
|
|
||||||
import get from 'lodash/get';
|
|
||||||
import { useEffect, useMemo } from 'react';
|
|
||||||
import { SwitchElseTo } from '../../constant';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ICategorizeItemResult,
|
|
||||||
ISwitchCondition,
|
|
||||||
RAGFlowNodeType,
|
|
||||||
} from '@/interfaces/database/flow';
|
|
||||||
import { generateSwitchHandleText } from '../../utils';
|
|
||||||
|
|
||||||
export const useBuildCategorizeHandlePositions = ({
|
|
||||||
data,
|
|
||||||
id,
|
|
||||||
}: {
|
|
||||||
id: string;
|
|
||||||
data: RAGFlowNodeType['data'];
|
|
||||||
}) => {
|
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
|
||||||
|
|
||||||
const categoryData: ICategorizeItemResult = useMemo(() => {
|
|
||||||
return get(data, `form.category_description`, {});
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const positions = useMemo(() => {
|
|
||||||
const list: Array<{
|
|
||||||
text: string;
|
|
||||||
top: number;
|
|
||||||
idx: number;
|
|
||||||
}> = [];
|
|
||||||
|
|
||||||
Object.keys(categoryData)
|
|
||||||
.sort((a, b) => categoryData[a].index - categoryData[b].index)
|
|
||||||
.forEach((x, idx) => {
|
|
||||||
list.push({
|
|
||||||
text: x,
|
|
||||||
idx,
|
|
||||||
top: idx === 0 ? 98 + 20 : list[idx - 1].top + 8 + 26,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}, [categoryData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updateNodeInternals(id);
|
|
||||||
}, [id, updateNodeInternals, categoryData]);
|
|
||||||
|
|
||||||
return { positions };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useBuildSwitchHandlePositions = ({
|
|
||||||
data,
|
|
||||||
id,
|
|
||||||
}: {
|
|
||||||
id: string;
|
|
||||||
data: RAGFlowNodeType['data'];
|
|
||||||
}) => {
|
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
|
||||||
|
|
||||||
const conditions: ISwitchCondition[] = useMemo(() => {
|
|
||||||
return get(data, 'form.conditions', []);
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const positions = useMemo(() => {
|
|
||||||
const list: Array<{
|
|
||||||
text: string;
|
|
||||||
top: number;
|
|
||||||
idx: number;
|
|
||||||
condition?: ISwitchCondition;
|
|
||||||
}> = [];
|
|
||||||
|
|
||||||
[...conditions, ''].forEach((x, idx) => {
|
|
||||||
let top = idx === 0 ? 58 + 20 : list[idx - 1].top + 32; // case number (Case 1) height + flex gap
|
|
||||||
if (idx - 1 >= 0) {
|
|
||||||
const previousItems = conditions[idx - 1]?.items ?? [];
|
|
||||||
if (previousItems.length > 0) {
|
|
||||||
top += 12; // ConditionBlock padding
|
|
||||||
top += previousItems.length * 22; // condition variable height
|
|
||||||
top += (previousItems.length - 1) * 25; // operator height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
list.push({
|
|
||||||
text:
|
|
||||||
idx < conditions.length
|
|
||||||
? generateSwitchHandleText(idx)
|
|
||||||
: SwitchElseTo,
|
|
||||||
idx,
|
|
||||||
top,
|
|
||||||
condition: typeof x === 'string' ? undefined : x,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}, [conditions]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updateNodeInternals(id);
|
|
||||||
}, [id, updateNodeInternals, conditions]);
|
|
||||||
|
|
||||||
return { positions };
|
|
||||||
};
|
|
||||||
@ -1,285 +0,0 @@
|
|||||||
.dark {
|
|
||||||
background: rgb(63, 63, 63) !important;
|
|
||||||
}
|
|
||||||
.ragNode {
|
|
||||||
.commonNode();
|
|
||||||
.nodeName {
|
|
||||||
font-size: 10px;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
color: #777;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.description {
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.categorizeAnchorPointText {
|
|
||||||
position: absolute;
|
|
||||||
top: -4px;
|
|
||||||
left: 8px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@lightBackgroundColor: rgba(150, 150, 150, 0.1);
|
|
||||||
@darkBackgroundColor: rgba(150, 150, 150, 0.2);
|
|
||||||
|
|
||||||
.selectedNode {
|
|
||||||
border: 1.5px solid rgb(59, 118, 244);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectedIterationNode {
|
|
||||||
border-bottom: 1.5px solid rgb(59, 118, 244);
|
|
||||||
border-left: 1.5px solid rgb(59, 118, 244);
|
|
||||||
border-right: 1.5px solid rgb(59, 118, 244);
|
|
||||||
}
|
|
||||||
|
|
||||||
.iterationHeader {
|
|
||||||
.commonNodeShadow();
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectedHeader {
|
|
||||||
border-top: 1.9px solid rgb(59, 118, 244);
|
|
||||||
border-left: 1.9px solid rgb(59, 118, 244);
|
|
||||||
border-right: 1.9px solid rgb(59, 118, 244);
|
|
||||||
}
|
|
||||||
|
|
||||||
.handle {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
background: rgb(59, 88, 253);
|
|
||||||
border: 1px solid white;
|
|
||||||
z-index: 1;
|
|
||||||
background-image: url('@/assets/svg/plus.svg');
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jsonView {
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow: auto;
|
|
||||||
max-width: 300px;
|
|
||||||
max-height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logicNode {
|
|
||||||
.commonNode();
|
|
||||||
|
|
||||||
.nodeName {
|
|
||||||
font-size: 10px;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
color: #777;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.categorizeAnchorPointText {
|
|
||||||
position: absolute;
|
|
||||||
top: -4px;
|
|
||||||
left: 8px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.relevantSourceLabel {
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.noteNode {
|
|
||||||
.commonNode();
|
|
||||||
min-width: 140px;
|
|
||||||
width: auto;
|
|
||||||
height: 100%;
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 10px;
|
|
||||||
min-height: 128px;
|
|
||||||
.noteTitle {
|
|
||||||
background-color: #edfcff;
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 6px 6px 4px;
|
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
}
|
|
||||||
.noteTitleDark {
|
|
||||||
background-color: #edfcff;
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 6px 6px 4px;
|
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
}
|
|
||||||
.noteForm {
|
|
||||||
margin-top: 4px;
|
|
||||||
height: calc(100% - 50px);
|
|
||||||
}
|
|
||||||
.noteName {
|
|
||||||
padding: 0px 4px;
|
|
||||||
}
|
|
||||||
.noteTextarea {
|
|
||||||
resize: none;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
height: 100%;
|
|
||||||
&:focus {
|
|
||||||
border: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.iterationNode {
|
|
||||||
.commonNodeShadow();
|
|
||||||
border-bottom-left-radius: 10px;
|
|
||||||
border-bottom-right-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nodeText {
|
|
||||||
padding-inline: 0.4em;
|
|
||||||
padding-block: 0.2em 0.1em;
|
|
||||||
background: @lightBackgroundColor;
|
|
||||||
border-radius: 3px;
|
|
||||||
min-height: 22px;
|
|
||||||
.textEllipsis();
|
|
||||||
}
|
|
||||||
|
|
||||||
.nodeHeader {
|
|
||||||
padding-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.zeroDivider {
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conditionBlock {
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 6px;
|
|
||||||
background: @lightBackgroundColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conditionLine {
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 0 4px;
|
|
||||||
background: @darkBackgroundColor;
|
|
||||||
.textEllipsis();
|
|
||||||
}
|
|
||||||
|
|
||||||
.conditionKey {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conditionOperator {
|
|
||||||
padding: 0 2px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.relevantLabel {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.knowledgeNodeName {
|
|
||||||
.textEllipsis();
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageNodeContainer {
|
|
||||||
overflow-y: auto;
|
|
||||||
max-height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.generateParameters {
|
|
||||||
padding-top: 8px;
|
|
||||||
label {
|
|
||||||
flex: 2;
|
|
||||||
.textEllipsis();
|
|
||||||
}
|
|
||||||
.parameterValue {
|
|
||||||
flex: 3;
|
|
||||||
.conditionLine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.emailNodeContainer {
|
|
||||||
padding: 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
.emailConfig {
|
|
||||||
background: rgba(0, 0, 0, 0.02);
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 8px;
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
.configItem {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.configLabel {
|
|
||||||
color: #666;
|
|
||||||
width: 45px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.configValue {
|
|
||||||
color: #333;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.expandIcon {
|
|
||||||
position: absolute;
|
|
||||||
right: 8px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
color: #666;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.jsonExample {
|
|
||||||
background: #f5f5f5;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 8px;
|
|
||||||
margin-top: 4px;
|
|
||||||
animation: slideDown 0.2s ease-out;
|
|
||||||
|
|
||||||
.jsonTitle {
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jsonContent {
|
|
||||||
margin: 0;
|
|
||||||
color: #333;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideDown {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-10px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { IRagNode } from '@/interfaces/database/flow';
|
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
|
||||||
|
|
||||||
export function RagNode({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
isConnectable = true,
|
|
||||||
selected,
|
|
||||||
}: NodeProps<IRagNode>) {
|
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={classNames(
|
|
||||||
styles.ragNode,
|
|
||||||
theme === 'dark' ? styles.dark : '',
|
|
||||||
{
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
id="c"
|
|
||||||
type="source"
|
|
||||||
position={Position.Left}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={LeftHandleStyle}
|
|
||||||
></Handle>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
id="b"
|
|
||||||
style={RightHandleStyle}
|
|
||||||
></Handle>
|
|
||||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { IInvokeNode } from '@/interfaces/database/flow';
|
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
|
||||||
import { Flex } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
|
||||||
|
|
||||||
export function InvokeNode({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
isConnectable = true,
|
|
||||||
selected,
|
|
||||||
}: NodeProps<IInvokeNode>) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { theme } = useTheme();
|
|
||||||
const url = get(data, 'form.url');
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={classNames(
|
|
||||||
styles.ragNode,
|
|
||||||
theme === 'dark' ? styles.dark : '',
|
|
||||||
{
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
id="c"
|
|
||||||
type="source"
|
|
||||||
position={Position.Left}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={LeftHandleStyle}
|
|
||||||
></Handle>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
id="b"
|
|
||||||
style={RightHandleStyle}
|
|
||||||
></Handle>
|
|
||||||
<NodeHeader
|
|
||||||
id={id}
|
|
||||||
name={data.name}
|
|
||||||
label={data.label}
|
|
||||||
className={styles.nodeHeader}
|
|
||||||
></NodeHeader>
|
|
||||||
<Flex vertical>
|
|
||||||
<div>{t('flow.url')}</div>
|
|
||||||
<div className={styles.nodeText}>{url}</div>
|
|
||||||
</Flex>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,127 +0,0 @@
|
|||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import {
|
|
||||||
IIterationNode,
|
|
||||||
IIterationStartNode,
|
|
||||||
} from '@/interfaces/database/flow';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { Handle, NodeProps, NodeResizeControl, Position } from '@xyflow/react';
|
|
||||||
import { ListRestart } from 'lucide-react';
|
|
||||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
|
||||||
|
|
||||||
function ResizeIcon() {
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
strokeWidth="2"
|
|
||||||
stroke="#5025f9"
|
|
||||||
fill="none"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
right: 5,
|
|
||||||
bottom: 5,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<polyline points="16 20 20 20 20 16" />
|
|
||||||
<line x1="14" y1="14" x2="20" y2="20" />
|
|
||||||
<polyline points="8 4 4 4 4 8" />
|
|
||||||
<line x1="4" y1="4" x2="10" y2="10" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const controlStyle = {
|
|
||||||
background: 'transparent',
|
|
||||||
border: 'none',
|
|
||||||
cursor: 'nwse-resize',
|
|
||||||
};
|
|
||||||
|
|
||||||
export function IterationNode({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
isConnectable = true,
|
|
||||||
selected,
|
|
||||||
}: NodeProps<IIterationNode>) {
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={cn(
|
|
||||||
'w-full h-full bg-zinc-200 opacity-70',
|
|
||||||
styles.iterationNode,
|
|
||||||
{
|
|
||||||
['bg-gray-800']: theme === 'dark',
|
|
||||||
[styles.selectedIterationNode]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<NodeResizeControl style={controlStyle} minWidth={100} minHeight={50}>
|
|
||||||
<ResizeIcon />
|
|
||||||
</NodeResizeControl>
|
|
||||||
<Handle
|
|
||||||
id="c"
|
|
||||||
type="source"
|
|
||||||
position={Position.Left}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={LeftHandleStyle}
|
|
||||||
></Handle>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
id="b"
|
|
||||||
style={RightHandleStyle}
|
|
||||||
></Handle>
|
|
||||||
<NodeHeader
|
|
||||||
id={id}
|
|
||||||
name={data.name}
|
|
||||||
label={data.label}
|
|
||||||
wrapperClassName={cn(
|
|
||||||
'p-2 bg-white rounded-t-[10px] absolute w-full top-[-60px] left-[-0.3px]',
|
|
||||||
styles.iterationHeader,
|
|
||||||
{
|
|
||||||
[`${styles.dark} text-white`]: theme === 'dark',
|
|
||||||
[styles.selectedHeader]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
></NodeHeader>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function IterationStartNode({
|
|
||||||
isConnectable = true,
|
|
||||||
selected,
|
|
||||||
}: NodeProps<IIterationStartNode>) {
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={cn('bg-white p-2 rounded-xl', {
|
|
||||||
[styles.dark]: theme === 'dark',
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={RightHandleStyle}
|
|
||||||
isConnectableEnd={false}
|
|
||||||
></Handle>
|
|
||||||
<div>
|
|
||||||
<ListRestart className="size-7" />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
import LLMLabel from '@/components/llm-select/llm-label';
|
|
||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { IKeywordNode } from '@/interfaces/database/flow';
|
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
|
||||||
|
|
||||||
export function KeywordNode({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
isConnectable = true,
|
|
||||||
selected,
|
|
||||||
}: NodeProps<IKeywordNode>) {
|
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={classNames(
|
|
||||||
styles.logicNode,
|
|
||||||
theme === 'dark' ? styles.dark : '',
|
|
||||||
{
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
id="c"
|
|
||||||
type="source"
|
|
||||||
position={Position.Left}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={LeftHandleStyle}
|
|
||||||
></Handle>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={RightHandleStyle}
|
|
||||||
id="b"
|
|
||||||
></Handle>
|
|
||||||
|
|
||||||
<NodeHeader
|
|
||||||
id={id}
|
|
||||||
name={data.name}
|
|
||||||
label={data.label}
|
|
||||||
className={styles.nodeHeader}
|
|
||||||
></NodeHeader>
|
|
||||||
|
|
||||||
<div className={styles.nodeText}>
|
|
||||||
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { ILogicNode } from '@/interfaces/database/flow';
|
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
|
||||||
|
|
||||||
export function LogicNode({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
isConnectable = true,
|
|
||||||
selected,
|
|
||||||
}: NodeProps<ILogicNode>) {
|
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={classNames(
|
|
||||||
styles.logicNode,
|
|
||||||
theme === 'dark' ? styles.dark : '',
|
|
||||||
{
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
id="c"
|
|
||||||
type="source"
|
|
||||||
position={Position.Left}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={LeftHandleStyle}
|
|
||||||
></Handle>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={RightHandleStyle}
|
|
||||||
id="b"
|
|
||||||
></Handle>
|
|
||||||
<NodeHeader id={id} name={data.name} label={data.label}></NodeHeader>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { IMessageNode } from '@/interfaces/database/flow';
|
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
|
||||||
import { Flex } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
|
||||||
|
|
||||||
export function MessageNode({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
isConnectable = true,
|
|
||||||
selected,
|
|
||||||
}: NodeProps<IMessageNode>) {
|
|
||||||
const messages: string[] = get(data, 'form.messages', []);
|
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={classNames(
|
|
||||||
styles.logicNode,
|
|
||||||
theme === 'dark' ? styles.dark : '',
|
|
||||||
{
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
id="c"
|
|
||||||
type="source"
|
|
||||||
position={Position.Left}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={LeftHandleStyle}
|
|
||||||
></Handle>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={RightHandleStyle}
|
|
||||||
id="b"
|
|
||||||
></Handle>
|
|
||||||
<NodeHeader
|
|
||||||
id={id}
|
|
||||||
name={data.name}
|
|
||||||
label={data.label}
|
|
||||||
className={classNames({
|
|
||||||
[styles.nodeHeader]: messages.length > 0,
|
|
||||||
})}
|
|
||||||
></NodeHeader>
|
|
||||||
|
|
||||||
<Flex vertical gap={8} className={styles.messageNodeContainer}>
|
|
||||||
{messages.map((message, idx) => {
|
|
||||||
return (
|
|
||||||
<div className={styles.nodeText} key={idx}>
|
|
||||||
{message}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Flex } from 'antd';
|
|
||||||
import { Play } from 'lucide-react';
|
|
||||||
import { Operator, operatorMap } from '../../constant';
|
|
||||||
import OperatorIcon from '../../operator-icon';
|
|
||||||
import { needsSingleStepDebugging } from '../../utils';
|
|
||||||
import NodeDropdown from './dropdown';
|
|
||||||
import { NextNodePopover } from './popover';
|
|
||||||
|
|
||||||
import { RunTooltip } from '../../flow-tooltip';
|
|
||||||
interface IProps {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
name: string;
|
|
||||||
gap?: number;
|
|
||||||
className?: string;
|
|
||||||
wrapperClassName?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ExcludedRunStateOperators = [Operator.Answer];
|
|
||||||
|
|
||||||
export function RunStatus({ id, name, label }: IProps) {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
return (
|
|
||||||
<section className="flex justify-end items-center pb-1 gap-2 text-blue-600">
|
|
||||||
{needsSingleStepDebugging(label) && (
|
|
||||||
<RunTooltip>
|
|
||||||
<Play className="size-3 cursor-pointer" data-play />
|
|
||||||
</RunTooltip> // data-play is used to trigger single step debugging
|
|
||||||
)}
|
|
||||||
<NextNodePopover nodeId={id} name={name}>
|
|
||||||
<span className="cursor-pointer text-[10px]">
|
|
||||||
{t('operationResults')}
|
|
||||||
</span>
|
|
||||||
</NextNodePopover>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const NodeHeader = ({
|
|
||||||
label,
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
gap = 4,
|
|
||||||
className,
|
|
||||||
wrapperClassName,
|
|
||||||
}: IProps) => {
|
|
||||||
return (
|
|
||||||
<section className={wrapperClassName}>
|
|
||||||
{!ExcludedRunStateOperators.includes(label as Operator) && (
|
|
||||||
<RunStatus id={id} name={name} label={label}></RunStatus>
|
|
||||||
)}
|
|
||||||
<Flex
|
|
||||||
flex={1}
|
|
||||||
align="center"
|
|
||||||
justify={'space-between'}
|
|
||||||
gap={gap}
|
|
||||||
className={className}
|
|
||||||
>
|
|
||||||
<OperatorIcon
|
|
||||||
name={label as Operator}
|
|
||||||
color={operatorMap[label as Operator]?.color}
|
|
||||||
></OperatorIcon>
|
|
||||||
<span className="truncate text-center font-semibold text-sm">
|
|
||||||
{name}
|
|
||||||
</span>
|
|
||||||
<NodeDropdown id={id} label={label}></NodeDropdown>
|
|
||||||
</Flex>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NodeHeader;
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
import { NodeProps, NodeResizeControl } from '@xyflow/react';
|
|
||||||
import { Flex, Form, Input } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import NodeDropdown from './dropdown';
|
|
||||||
|
|
||||||
import SvgIcon from '@/components/svg-icon';
|
|
||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { INoteNode } from '@/interfaces/database/flow';
|
|
||||||
import { memo, useEffect } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import {
|
|
||||||
useHandleFormValuesChange,
|
|
||||||
useHandleNodeNameChange,
|
|
||||||
} from '../../hooks';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
const { TextArea } = Input;
|
|
||||||
|
|
||||||
const controlStyle = {
|
|
||||||
background: 'transparent',
|
|
||||||
border: 'none',
|
|
||||||
};
|
|
||||||
|
|
||||||
function NoteNode({ data, id }: NodeProps<INoteNode>) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const { theme } = useTheme();
|
|
||||||
|
|
||||||
const { name, handleNameBlur, handleNameChange } = useHandleNodeNameChange({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
const { handleValuesChange } = useHandleFormValuesChange(id);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.setFieldsValue(data?.form);
|
|
||||||
}, [form, data?.form]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<NodeResizeControl style={controlStyle} minWidth={190} minHeight={128}>
|
|
||||||
<SvgIcon
|
|
||||||
name="resize"
|
|
||||||
width={12}
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
right: 5,
|
|
||||||
bottom: 5,
|
|
||||||
cursor: 'nwse-resize',
|
|
||||||
}}
|
|
||||||
></SvgIcon>
|
|
||||||
</NodeResizeControl>
|
|
||||||
<section
|
|
||||||
className={classNames(
|
|
||||||
styles.noteNode,
|
|
||||||
theme === 'dark' ? styles.dark : '',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Flex
|
|
||||||
justify={'space-between'}
|
|
||||||
className={classNames('note-drag-handle')}
|
|
||||||
align="center"
|
|
||||||
gap={6}
|
|
||||||
>
|
|
||||||
<SvgIcon name="note" width={14}></SvgIcon>
|
|
||||||
<Input
|
|
||||||
value={name ?? t('flow.note')}
|
|
||||||
onBlur={handleNameBlur}
|
|
||||||
onChange={handleNameChange}
|
|
||||||
className={styles.noteName}
|
|
||||||
></Input>
|
|
||||||
<NodeDropdown id={id} label={data.label}></NodeDropdown>
|
|
||||||
</Flex>
|
|
||||||
<Form
|
|
||||||
onValuesChange={handleValuesChange}
|
|
||||||
form={form}
|
|
||||||
className={styles.noteForm}
|
|
||||||
>
|
|
||||||
<Form.Item name="text" noStyle>
|
|
||||||
<TextArea
|
|
||||||
rows={3}
|
|
||||||
placeholder={t('flow.notePlaceholder')}
|
|
||||||
className={styles.noteTextarea}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</section>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(NoteNode);
|
|
||||||
@ -1,313 +0,0 @@
|
|||||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
|
||||||
import get from 'lodash/get';
|
|
||||||
import React, {
|
|
||||||
MouseEventHandler,
|
|
||||||
useCallback,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import JsonView from 'react18-json-view';
|
|
||||||
import 'react18-json-view/src/style.css';
|
|
||||||
import { useReplaceIdWithText } from '../../hooks';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from '@/components/ui/popover';
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from '@/components/ui/table';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
Col,
|
|
||||||
Input,
|
|
||||||
Row,
|
|
||||||
Space,
|
|
||||||
Tabs,
|
|
||||||
Typography,
|
|
||||||
message,
|
|
||||||
} from 'antd';
|
|
||||||
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
|
|
||||||
|
|
||||||
interface IProps extends React.PropsWithChildren {
|
|
||||||
nodeId: string;
|
|
||||||
name?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NextNodePopover({ children, nodeId, name }: IProps) {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
const { data } = useFetchFlow();
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
const component = useMemo(() => {
|
|
||||||
return get(data, ['dsl', 'components', nodeId], {});
|
|
||||||
}, [nodeId, data]);
|
|
||||||
|
|
||||||
const inputs: Array<{ component_id: string; content: string }> = get(
|
|
||||||
component,
|
|
||||||
['obj', 'inputs'],
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
const output = get(component, ['obj', 'output'], {});
|
|
||||||
const { conf, messages, prompt } = get(
|
|
||||||
component,
|
|
||||||
['obj', 'params', 'infor'],
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
const { replacedOutput } = useReplaceIdWithText(output);
|
|
||||||
const stopPropagation: MouseEventHandler = useCallback((e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getLabel = useGetComponentLabelByValue(nodeId);
|
|
||||||
|
|
||||||
const [inputPage, setInputPage] = useState(1);
|
|
||||||
const pageSize = 3;
|
|
||||||
const pagedInputs = inputs.slice(
|
|
||||||
(inputPage - 1) * pageSize,
|
|
||||||
inputPage * pageSize,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger onClick={stopPropagation} asChild>
|
|
||||||
{children}
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent
|
|
||||||
align={'start'}
|
|
||||||
side={'right'}
|
|
||||||
sideOffset={20}
|
|
||||||
onClick={stopPropagation}
|
|
||||||
className="w-[800px] p-4"
|
|
||||||
style={{ maxHeight: 600, overflow: 'auto' }}
|
|
||||||
>
|
|
||||||
<Card
|
|
||||||
bordered={false}
|
|
||||||
style={{ marginBottom: 16, padding: 0 }}
|
|
||||||
bodyStyle={{ padding: 0 }}
|
|
||||||
>
|
|
||||||
<Typography.Title
|
|
||||||
level={5}
|
|
||||||
style={{
|
|
||||||
marginBottom: 16,
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: 18,
|
|
||||||
borderBottom: '1px solid #f0f0f0',
|
|
||||||
paddingBottom: 8,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{name} {t('operationResults')}
|
|
||||||
</Typography.Title>
|
|
||||||
</Card>
|
|
||||||
<Tabs
|
|
||||||
defaultActiveKey="input"
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
key: 'input',
|
|
||||||
label: t('input'),
|
|
||||||
children: (
|
|
||||||
<Card
|
|
||||||
size="small"
|
|
||||||
className="bg-gray-50 dark:bg-gray-800"
|
|
||||||
style={{ borderRadius: 8, border: '1px solid #e5e7eb' }}
|
|
||||||
bodyStyle={{ padding: 16 }}
|
|
||||||
>
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>{t('componentId')}</TableHead>
|
|
||||||
<TableHead className="w-[60px]">
|
|
||||||
{t('content')}
|
|
||||||
</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{pagedInputs.map((x, idx) => (
|
|
||||||
<TableRow key={idx + (inputPage - 1) * pageSize}>
|
|
||||||
<TableCell>{getLabel(x.component_id)}</TableCell>
|
|
||||||
<TableCell className="truncate">
|
|
||||||
{x.content}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
{/* Pagination */}
|
|
||||||
{inputs.length > pageSize && (
|
|
||||||
<Row justify="end" style={{ marginTop: 8 }}>
|
|
||||||
<Space>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
disabled={inputPage === 1}
|
|
||||||
onClick={() => setInputPage(inputPage - 1)}
|
|
||||||
>
|
|
||||||
Prev
|
|
||||||
</Button>
|
|
||||||
<span className="mx-2 text-sm">
|
|
||||||
{inputPage} / {Math.ceil(inputs.length / pageSize)}
|
|
||||||
</span>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
disabled={
|
|
||||||
inputPage === Math.ceil(inputs.length / pageSize)
|
|
||||||
}
|
|
||||||
onClick={() => setInputPage(inputPage + 1)}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</Row>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'output',
|
|
||||||
label: t('output'),
|
|
||||||
children: (
|
|
||||||
<Card
|
|
||||||
size="small"
|
|
||||||
className="bg-gray-50 dark:bg-gray-800"
|
|
||||||
style={{ borderRadius: 8, border: '1px solid #e5e7eb' }}
|
|
||||||
bodyStyle={{ padding: 16 }}
|
|
||||||
>
|
|
||||||
<JsonView
|
|
||||||
src={replacedOutput}
|
|
||||||
displaySize={30}
|
|
||||||
className="w-full max-h-[300px] break-words overflow-auto"
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'infor',
|
|
||||||
label: t('infor'),
|
|
||||||
children: (
|
|
||||||
<Card
|
|
||||||
size="small"
|
|
||||||
className="bg-gray-50 dark:bg-gray-800"
|
|
||||||
style={{ borderRadius: 8, border: '1px solid #e5e7eb' }}
|
|
||||||
bodyStyle={{ padding: 16 }}
|
|
||||||
>
|
|
||||||
<Row gutter={16}>
|
|
||||||
<Col span={12}>
|
|
||||||
{conf && (
|
|
||||||
<Card
|
|
||||||
size="small"
|
|
||||||
bordered={false}
|
|
||||||
style={{
|
|
||||||
marginBottom: 16,
|
|
||||||
background: 'transparent',
|
|
||||||
}}
|
|
||||||
bodyStyle={{ padding: 0 }}
|
|
||||||
>
|
|
||||||
<Typography.Text
|
|
||||||
strong
|
|
||||||
style={{
|
|
||||||
color: '#888',
|
|
||||||
marginBottom: 8,
|
|
||||||
display: 'block',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Configuration:
|
|
||||||
</Typography.Text>
|
|
||||||
<JsonView
|
|
||||||
src={conf}
|
|
||||||
displaySize={30}
|
|
||||||
className="w-full max-h-[120px] break-words overflow-auto"
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
{prompt && (
|
|
||||||
<Card
|
|
||||||
size="small"
|
|
||||||
bordered={false}
|
|
||||||
style={{ background: 'transparent' }}
|
|
||||||
bodyStyle={{ padding: 0 }}
|
|
||||||
>
|
|
||||||
<Row
|
|
||||||
align="middle"
|
|
||||||
justify="space-between"
|
|
||||||
style={{ marginBottom: 8 }}
|
|
||||||
>
|
|
||||||
<Col>
|
|
||||||
<Typography.Text strong style={{ color: '#888' }}>
|
|
||||||
Prompt:
|
|
||||||
</Typography.Text>
|
|
||||||
</Col>
|
|
||||||
<Col>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
const inlineString = prompt
|
|
||||||
.replace(/\s+/g, ' ')
|
|
||||||
.trim();
|
|
||||||
navigator.clipboard.writeText(inlineString);
|
|
||||||
message.success(
|
|
||||||
'Prompt copied as single line!',
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Copy as single line
|
|
||||||
</Button>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Input.TextArea
|
|
||||||
value={prompt}
|
|
||||||
readOnly
|
|
||||||
autoSize={{ minRows: 2, maxRows: 6 }}
|
|
||||||
className="bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700"
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
{messages && (
|
|
||||||
<Card
|
|
||||||
size="small"
|
|
||||||
bordered={false}
|
|
||||||
style={{
|
|
||||||
marginBottom: 16,
|
|
||||||
background: 'transparent',
|
|
||||||
}}
|
|
||||||
bodyStyle={{ padding: 0 }}
|
|
||||||
>
|
|
||||||
<Typography.Text
|
|
||||||
strong
|
|
||||||
style={{
|
|
||||||
color: '#888',
|
|
||||||
marginBottom: 8,
|
|
||||||
display: 'block',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Messages:
|
|
||||||
</Typography.Text>
|
|
||||||
<div className="max-h-[300px] overflow-auto">
|
|
||||||
<JsonView
|
|
||||||
src={messages}
|
|
||||||
displaySize={30}
|
|
||||||
className="w-full break-words"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Card>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
|
||||||
import { Flex } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { RightHandleStyle } from './handle-icon';
|
|
||||||
|
|
||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { IRelevantNode } from '@/interfaces/database/flow';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import { useReplaceIdWithName } from '../../hooks';
|
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
|
||||||
|
|
||||||
export function RelevantNode({ id, data, selected }: NodeProps<IRelevantNode>) {
|
|
||||||
const yes = get(data, 'form.yes');
|
|
||||||
const no = get(data, 'form.no');
|
|
||||||
const replaceIdWithName = useReplaceIdWithName();
|
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={classNames(
|
|
||||||
styles.logicNode,
|
|
||||||
theme === 'dark' ? styles.dark : '',
|
|
||||||
{
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
type="target"
|
|
||||||
position={Position.Left}
|
|
||||||
isConnectable
|
|
||||||
className={styles.handle}
|
|
||||||
id={'a'}
|
|
||||||
></Handle>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable
|
|
||||||
className={styles.handle}
|
|
||||||
id={'yes'}
|
|
||||||
style={{ ...RightHandleStyle, top: 57 + 20 }}
|
|
||||||
></Handle>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable
|
|
||||||
className={styles.handle}
|
|
||||||
id={'no'}
|
|
||||||
style={{ ...RightHandleStyle, top: 115 + 20 }}
|
|
||||||
></Handle>
|
|
||||||
<NodeHeader
|
|
||||||
id={id}
|
|
||||||
name={data.name}
|
|
||||||
label={data.label}
|
|
||||||
className={styles.nodeHeader}
|
|
||||||
></NodeHeader>
|
|
||||||
|
|
||||||
<Flex vertical gap={10}>
|
|
||||||
<Flex vertical>
|
|
||||||
<div className={styles.relevantLabel}>Yes</div>
|
|
||||||
<div className={styles.nodeText}>{replaceIdWithName(yes)}</div>
|
|
||||||
</Flex>
|
|
||||||
<Flex vertical>
|
|
||||||
<div className={styles.relevantLabel}>No</div>
|
|
||||||
<div className={styles.nodeText}>{replaceIdWithName(no)}</div>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,115 +0,0 @@
|
|||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
|
||||||
import { IRetrievalNode } from '@/interfaces/database/flow';
|
|
||||||
import { UserOutlined } from '@ant-design/icons';
|
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
|
||||||
import { Avatar, Button, Flex } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import { useMemo, useState } from 'react';
|
|
||||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
|
||||||
|
|
||||||
export function RetrievalNode({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
isConnectable = true,
|
|
||||||
selected,
|
|
||||||
}: NodeProps<IRetrievalNode>) {
|
|
||||||
const knowledgeBaseIds: string[] = get(data, 'form.kb_ids', []);
|
|
||||||
const { theme } = useTheme();
|
|
||||||
const { list: knowledgeList } = useFetchKnowledgeList(true);
|
|
||||||
const [showAllKnowledge, setShowAllKnowledge] = useState(false);
|
|
||||||
const knowledgeBases = useMemo(() => {
|
|
||||||
return knowledgeBaseIds.map((x) => {
|
|
||||||
const item = knowledgeList.find((y) => x === y.id);
|
|
||||||
return {
|
|
||||||
name: item?.name,
|
|
||||||
avatar: item?.avatar,
|
|
||||||
id: x,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, [knowledgeList, knowledgeBaseIds]);
|
|
||||||
|
|
||||||
const displayedKnowledgeBases = showAllKnowledge
|
|
||||||
? knowledgeBases
|
|
||||||
: knowledgeBases.slice(0, 3);
|
|
||||||
function showAllItem(e: any) {
|
|
||||||
e.stopPropagation(); // Prevent event from bubbling to parent
|
|
||||||
setShowAllKnowledge(!showAllKnowledge);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={classNames(
|
|
||||||
styles.logicNode,
|
|
||||||
theme === 'dark' ? styles.dark : '',
|
|
||||||
{
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
id="c"
|
|
||||||
type="source"
|
|
||||||
position={Position.Left}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={LeftHandleStyle}
|
|
||||||
></Handle>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={RightHandleStyle}
|
|
||||||
id="b"
|
|
||||||
></Handle>
|
|
||||||
<NodeHeader
|
|
||||||
id={id}
|
|
||||||
name={data.name}
|
|
||||||
label={data.label}
|
|
||||||
className={classNames({
|
|
||||||
[styles.nodeHeader]: knowledgeBaseIds.length > 0,
|
|
||||||
})}
|
|
||||||
></NodeHeader>
|
|
||||||
<Flex vertical gap={8}>
|
|
||||||
{displayedKnowledgeBases.map((knowledge) => {
|
|
||||||
return (
|
|
||||||
<div className={styles.nodeText} key={knowledge.id}>
|
|
||||||
<Flex align={'center'} gap={6}>
|
|
||||||
<Avatar
|
|
||||||
size={26}
|
|
||||||
icon={<UserOutlined />}
|
|
||||||
src={knowledge.avatar}
|
|
||||||
/>
|
|
||||||
<Flex className={styles.knowledgeNodeName} flex={1}>
|
|
||||||
{knowledge.name}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{knowledgeBases.length > 3 && (
|
|
||||||
<div className={styles.nodeText}>
|
|
||||||
<Button
|
|
||||||
type="link"
|
|
||||||
size="small"
|
|
||||||
onClick={showAllItem}
|
|
||||||
style={{
|
|
||||||
padding: 0,
|
|
||||||
height: 'auto',
|
|
||||||
lineHeight: '1.5',
|
|
||||||
textAlign: 'left',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{showAllKnowledge
|
|
||||||
? 'Hide'
|
|
||||||
: `Show more ${knowledgeBases.length - 3} knowledge`}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
import LLMLabel from '@/components/llm-select/llm-label';
|
|
||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { IRewriteNode } from '@/interfaces/database/flow';
|
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
|
||||||
|
|
||||||
export function RewriteNode({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
isConnectable = true,
|
|
||||||
selected,
|
|
||||||
}: NodeProps<IRewriteNode>) {
|
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={classNames(
|
|
||||||
styles.logicNode,
|
|
||||||
theme === 'dark' ? styles.dark : '',
|
|
||||||
{
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
id="c"
|
|
||||||
type="source"
|
|
||||||
position={Position.Left}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={LeftHandleStyle}
|
|
||||||
></Handle>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={RightHandleStyle}
|
|
||||||
id="b"
|
|
||||||
></Handle>
|
|
||||||
|
|
||||||
<NodeHeader
|
|
||||||
id={id}
|
|
||||||
name={data.name}
|
|
||||||
label={data.label}
|
|
||||||
className={styles.nodeHeader}
|
|
||||||
></NodeHeader>
|
|
||||||
|
|
||||||
<div className={styles.nodeText}>
|
|
||||||
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,114 +0,0 @@
|
|||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { ISwitchCondition, ISwitchNode } from '@/interfaces/database/flow';
|
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
|
||||||
import { Divider, Flex } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
|
|
||||||
import { RightHandleStyle } from './handle-icon';
|
|
||||||
import { useBuildSwitchHandlePositions } from './hooks';
|
|
||||||
import styles from './index.less';
|
|
||||||
import NodeHeader from './node-header';
|
|
||||||
|
|
||||||
const getConditionKey = (idx: number, length: number) => {
|
|
||||||
if (idx === 0 && length !== 1) {
|
|
||||||
return 'If';
|
|
||||||
} else if (idx === length - 1) {
|
|
||||||
return 'Else';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'ElseIf';
|
|
||||||
};
|
|
||||||
|
|
||||||
const ConditionBlock = ({
|
|
||||||
condition,
|
|
||||||
nodeId,
|
|
||||||
}: {
|
|
||||||
condition: ISwitchCondition;
|
|
||||||
nodeId: string;
|
|
||||||
}) => {
|
|
||||||
const items = condition?.items ?? [];
|
|
||||||
const getLabel = useGetComponentLabelByValue(nodeId);
|
|
||||||
return (
|
|
||||||
<Flex vertical className={styles.conditionBlock}>
|
|
||||||
{items.map((x, idx) => (
|
|
||||||
<div key={idx}>
|
|
||||||
<Flex>
|
|
||||||
<div
|
|
||||||
className={classNames(styles.conditionLine, styles.conditionKey)}
|
|
||||||
>
|
|
||||||
{getLabel(x?.cpn_id)}
|
|
||||||
</div>
|
|
||||||
<span className={styles.conditionOperator}>{x?.operator}</span>
|
|
||||||
<Flex flex={1} className={styles.conditionLine}>
|
|
||||||
{x?.value}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
{idx + 1 < items.length && (
|
|
||||||
<Divider orientationMargin="0" className={styles.zeroDivider}>
|
|
||||||
{condition?.logical_operator}
|
|
||||||
</Divider>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function SwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) {
|
|
||||||
const { positions } = useBuildSwitchHandlePositions({ data, id });
|
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={classNames(
|
|
||||||
styles.logicNode,
|
|
||||||
theme === 'dark' ? styles.dark : '',
|
|
||||||
{
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
type="target"
|
|
||||||
position={Position.Left}
|
|
||||||
isConnectable
|
|
||||||
className={styles.handle}
|
|
||||||
id={'a'}
|
|
||||||
></Handle>
|
|
||||||
<NodeHeader
|
|
||||||
id={id}
|
|
||||||
name={data.name}
|
|
||||||
label={data.label}
|
|
||||||
className={styles.nodeHeader}
|
|
||||||
></NodeHeader>
|
|
||||||
<Flex vertical gap={10}>
|
|
||||||
{positions.map((position, idx) => {
|
|
||||||
return (
|
|
||||||
<div key={idx}>
|
|
||||||
<Flex vertical>
|
|
||||||
<Flex justify={'space-between'}>
|
|
||||||
<span>{idx < positions.length - 1 && position.text}</span>
|
|
||||||
<span>{getConditionKey(idx, positions.length)}</span>
|
|
||||||
</Flex>
|
|
||||||
{position.condition && (
|
|
||||||
<ConditionBlock
|
|
||||||
nodeId={id}
|
|
||||||
condition={position.condition}
|
|
||||||
></ConditionBlock>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
<Handle
|
|
||||||
key={position.text}
|
|
||||||
id={position.text}
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable
|
|
||||||
className={styles.handle}
|
|
||||||
style={{ ...RightHandleStyle, top: position.top }}
|
|
||||||
></Handle>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
import { useTheme } from '@/components/theme-provider';
|
|
||||||
import { Handle, NodeProps, Position } from '@xyflow/react';
|
|
||||||
import { Flex } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { get } from 'lodash';
|
|
||||||
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
|
|
||||||
import { IGenerateParameter } from '../../interface';
|
|
||||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
|
||||||
import NodeHeader from './node-header';
|
|
||||||
|
|
||||||
import { ITemplateNode } from '@/interfaces/database/flow';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
export function TemplateNode({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
isConnectable = true,
|
|
||||||
selected,
|
|
||||||
}: NodeProps<ITemplateNode>) {
|
|
||||||
const parameters: IGenerateParameter[] = get(data, 'form.parameters', []);
|
|
||||||
const getLabel = useGetComponentLabelByValue(id);
|
|
||||||
const { theme } = useTheme();
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={classNames(
|
|
||||||
styles.logicNode,
|
|
||||||
theme === 'dark' ? styles.dark : '',
|
|
||||||
|
|
||||||
{
|
|
||||||
[styles.selectedNode]: selected,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Handle
|
|
||||||
id="c"
|
|
||||||
type="source"
|
|
||||||
position={Position.Left}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={LeftHandleStyle}
|
|
||||||
></Handle>
|
|
||||||
<Handle
|
|
||||||
type="source"
|
|
||||||
position={Position.Right}
|
|
||||||
isConnectable={isConnectable}
|
|
||||||
className={styles.handle}
|
|
||||||
style={RightHandleStyle}
|
|
||||||
id="b"
|
|
||||||
></Handle>
|
|
||||||
|
|
||||||
<NodeHeader
|
|
||||||
id={id}
|
|
||||||
name={data.name}
|
|
||||||
label={data.label}
|
|
||||||
className={styles.nodeHeader}
|
|
||||||
></NodeHeader>
|
|
||||||
|
|
||||||
<Flex gap={8} vertical className={styles.generateParameters}>
|
|
||||||
{parameters.map((x) => (
|
|
||||||
<Flex
|
|
||||||
key={x.id}
|
|
||||||
align="center"
|
|
||||||
gap={6}
|
|
||||||
className={styles.conditionBlock}
|
|
||||||
>
|
|
||||||
<label htmlFor="">{x.key}</label>
|
|
||||||
<span className={styles.parameterValue}>
|
|
||||||
{getLabel(x.component_id)}
|
|
||||||
</span>
|
|
||||||
</Flex>
|
|
||||||
))}
|
|
||||||
</Flex>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
import MessageItem from '@/components/message-item';
|
|
||||||
import { MessageType } from '@/constants/chat';
|
|
||||||
import { useGetFileIcon } from '@/pages/chat/hooks';
|
|
||||||
import { buildMessageItemReference } from '@/pages/chat/utils';
|
|
||||||
import { Flex, Spin } from 'antd';
|
|
||||||
|
|
||||||
import { useSendNextMessage } from './hooks';
|
|
||||||
|
|
||||||
import MessageInput from '@/components/message-input';
|
|
||||||
import PdfDrawer from '@/components/pdf-drawer';
|
|
||||||
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
|
||||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
|
||||||
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
|
||||||
import { buildMessageUuidWithRole } from '@/utils/chat';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
const FlowChatBox = () => {
|
|
||||||
const {
|
|
||||||
sendLoading,
|
|
||||||
handleInputChange,
|
|
||||||
handlePressEnter,
|
|
||||||
value,
|
|
||||||
loading,
|
|
||||||
ref,
|
|
||||||
derivedMessages,
|
|
||||||
reference,
|
|
||||||
stopOutputMessage,
|
|
||||||
} = useSendNextMessage();
|
|
||||||
|
|
||||||
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
|
||||||
useClickDrawer();
|
|
||||||
useGetFileIcon();
|
|
||||||
const { data: userInfo } = useFetchUserInfo();
|
|
||||||
const { data: canvasInfo } = useFetchFlow();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Flex flex={1} className={styles.chatContainer} vertical>
|
|
||||||
<Flex flex={1} vertical className={styles.messageContainer}>
|
|
||||||
<div>
|
|
||||||
<Spin spinning={loading}>
|
|
||||||
{derivedMessages?.map((message, i) => {
|
|
||||||
return (
|
|
||||||
<MessageItem
|
|
||||||
loading={
|
|
||||||
message.role === MessageType.Assistant &&
|
|
||||||
sendLoading &&
|
|
||||||
derivedMessages.length - 1 === i
|
|
||||||
}
|
|
||||||
key={buildMessageUuidWithRole(message)}
|
|
||||||
nickname={userInfo.nickname}
|
|
||||||
avatar={userInfo.avatar}
|
|
||||||
avatarDialog={canvasInfo.avatar}
|
|
||||||
item={message}
|
|
||||||
reference={buildMessageItemReference(
|
|
||||||
{ message: derivedMessages, reference },
|
|
||||||
message,
|
|
||||||
)}
|
|
||||||
clickDocumentButton={clickDocumentButton}
|
|
||||||
index={i}
|
|
||||||
showLikeButton={false}
|
|
||||||
sendLoading={sendLoading}
|
|
||||||
></MessageItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Spin>
|
|
||||||
</div>
|
|
||||||
<div ref={ref} />
|
|
||||||
</Flex>
|
|
||||||
<MessageInput
|
|
||||||
showUploadIcon={false}
|
|
||||||
value={value}
|
|
||||||
sendLoading={sendLoading}
|
|
||||||
disabled={false}
|
|
||||||
sendDisabled={sendLoading}
|
|
||||||
conversationId=""
|
|
||||||
onPressEnter={handlePressEnter}
|
|
||||||
onInputChange={handleInputChange}
|
|
||||||
stopOutputMessage={stopOutputMessage}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
<PdfDrawer
|
|
||||||
visible={visible}
|
|
||||||
hideModal={hideModal}
|
|
||||||
documentId={documentId}
|
|
||||||
chunk={selectedChunk}
|
|
||||||
></PdfDrawer>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FlowChatBox;
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { Drawer } from 'antd';
|
|
||||||
import { getDrawerWidth } from '../utils';
|
|
||||||
import FlowChatBox from './box';
|
|
||||||
|
|
||||||
const ChatDrawer = ({ visible, hideModal }: IModalProps<any>) => {
|
|
||||||
const { data } = useFetchFlow();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Drawer
|
|
||||||
title={data.title}
|
|
||||||
placement="right"
|
|
||||||
onClose={hideModal}
|
|
||||||
open={visible}
|
|
||||||
getContainer={false}
|
|
||||||
width={getDrawerWidth()}
|
|
||||||
mask={false}
|
|
||||||
>
|
|
||||||
<FlowChatBox></FlowChatBox>
|
|
||||||
</Drawer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ChatDrawer;
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
import { MessageType } from '@/constants/chat';
|
|
||||||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
|
||||||
import {
|
|
||||||
useHandleMessageInputChange,
|
|
||||||
useSelectDerivedMessages,
|
|
||||||
useSendMessageWithSse,
|
|
||||||
} from '@/hooks/logic-hooks';
|
|
||||||
import { Message } from '@/interfaces/database/chat';
|
|
||||||
import i18n from '@/locales/config';
|
|
||||||
import api from '@/utils/api';
|
|
||||||
import { message } from 'antd';
|
|
||||||
import trim from 'lodash/trim';
|
|
||||||
import { useCallback, useEffect } from 'react';
|
|
||||||
import { useParams } from 'umi';
|
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import { receiveMessageError } from '../utils';
|
|
||||||
|
|
||||||
const antMessage = message;
|
|
||||||
|
|
||||||
export const useSelectNextMessages = () => {
|
|
||||||
const { data: flowDetail, loading } = useFetchFlow();
|
|
||||||
const reference = flowDetail.dsl.reference;
|
|
||||||
const {
|
|
||||||
derivedMessages,
|
|
||||||
ref,
|
|
||||||
addNewestQuestion,
|
|
||||||
addNewestAnswer,
|
|
||||||
removeLatestMessage,
|
|
||||||
removeMessageById,
|
|
||||||
removeMessagesAfterCurrentMessage,
|
|
||||||
} = useSelectDerivedMessages();
|
|
||||||
|
|
||||||
return {
|
|
||||||
reference,
|
|
||||||
loading,
|
|
||||||
derivedMessages,
|
|
||||||
ref,
|
|
||||||
addNewestQuestion,
|
|
||||||
addNewestAnswer,
|
|
||||||
removeLatestMessage,
|
|
||||||
removeMessageById,
|
|
||||||
removeMessagesAfterCurrentMessage,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSendNextMessage = () => {
|
|
||||||
const {
|
|
||||||
reference,
|
|
||||||
loading,
|
|
||||||
derivedMessages,
|
|
||||||
ref,
|
|
||||||
addNewestQuestion,
|
|
||||||
addNewestAnswer,
|
|
||||||
removeLatestMessage,
|
|
||||||
removeMessageById,
|
|
||||||
} = useSelectNextMessages();
|
|
||||||
const { id: flowId } = useParams();
|
|
||||||
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
|
||||||
const { refetch } = useFetchFlow();
|
|
||||||
|
|
||||||
const { send, answer, done, stopOutputMessage } = useSendMessageWithSse(
|
|
||||||
api.runCanvas,
|
|
||||||
);
|
|
||||||
|
|
||||||
const sendMessage = useCallback(
|
|
||||||
async ({ message }: { message: Message; messages?: Message[] }) => {
|
|
||||||
const params: Record<string, unknown> = {
|
|
||||||
id: flowId,
|
|
||||||
};
|
|
||||||
params.running_hint_text = i18n.t('flow.runningHintText', {
|
|
||||||
defaultValue: 'is running...🕞',
|
|
||||||
});
|
|
||||||
if (message.content) {
|
|
||||||
params.message = message.content;
|
|
||||||
params.message_id = message.id;
|
|
||||||
}
|
|
||||||
const res = await send(params);
|
|
||||||
|
|
||||||
if (receiveMessageError(res)) {
|
|
||||||
antMessage.error(res?.data?.message);
|
|
||||||
|
|
||||||
// cancel loading
|
|
||||||
setValue(message.content);
|
|
||||||
removeLatestMessage();
|
|
||||||
} else {
|
|
||||||
refetch(); // pull the message list after sending the message successfully
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[flowId, send, setValue, removeLatestMessage, refetch],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSendMessage = useCallback(
|
|
||||||
async (message: Message) => {
|
|
||||||
sendMessage({ message });
|
|
||||||
},
|
|
||||||
[sendMessage],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (answer.answer) {
|
|
||||||
addNewestAnswer(answer);
|
|
||||||
}
|
|
||||||
}, [answer, addNewestAnswer]);
|
|
||||||
|
|
||||||
const handlePressEnter = useCallback(() => {
|
|
||||||
if (trim(value) === '') return;
|
|
||||||
const id = uuid();
|
|
||||||
if (done) {
|
|
||||||
setValue('');
|
|
||||||
handleSendMessage({ id, content: value.trim(), role: MessageType.User });
|
|
||||||
}
|
|
||||||
addNewestQuestion({
|
|
||||||
content: value,
|
|
||||||
id,
|
|
||||||
role: MessageType.User,
|
|
||||||
});
|
|
||||||
}, [addNewestQuestion, handleSendMessage, done, setValue, value]);
|
|
||||||
|
|
||||||
const fetchPrologue = useCallback(async () => {
|
|
||||||
// fetch prologue
|
|
||||||
const sendRet = await send({ id: flowId });
|
|
||||||
if (receiveMessageError(sendRet)) {
|
|
||||||
message.error(sendRet?.data?.message);
|
|
||||||
} else {
|
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
}, [flowId, refetch, send]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchPrologue();
|
|
||||||
}, [fetchPrologue]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
handlePressEnter,
|
|
||||||
handleInputChange,
|
|
||||||
value,
|
|
||||||
sendLoading: !done,
|
|
||||||
reference,
|
|
||||||
loading,
|
|
||||||
derivedMessages,
|
|
||||||
ref,
|
|
||||||
removeMessageById,
|
|
||||||
stopOutputMessage,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
.chatContainer {
|
|
||||||
padding: 0;
|
|
||||||
height: 100%;
|
|
||||||
.messageContainer {
|
|
||||||
overflow-y: auto;
|
|
||||||
padding-right: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,6 +0,0 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { createContext } from 'react';
|
|
||||||
|
|
||||||
export const FlowFormContext = createContext<RAGFlowNodeType | undefined>(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
@ -1,312 +0,0 @@
|
|||||||
{
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"id": "63a2f242-8e71-4098-a46a-459a76d538bd",
|
|
||||||
"label": "",
|
|
||||||
"source": "begin",
|
|
||||||
"target": "answer:0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cc6dd8bb-e9dc-46e8-9009-5b96f98ae6c0",
|
|
||||||
"label": "",
|
|
||||||
"source": "generate:casual",
|
|
||||||
"target": "answer:0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "58dbf05a-07fc-4a0a-8c03-5f9117e48c35",
|
|
||||||
"label": "",
|
|
||||||
"source": "generate:answer",
|
|
||||||
"target": "answer:0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "dd0ff4f2-4d75-4e7d-a505-3e9533402823",
|
|
||||||
"label": "",
|
|
||||||
"source": "generate:complain",
|
|
||||||
"target": "answer:0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3dc7a511-9cde-4080-a572-6b06a64e0458",
|
|
||||||
"label": "",
|
|
||||||
"source": "generate:ask_contact",
|
|
||||||
"target": "answer:0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "20e31fee-c392-4257-860a-5844d264198e",
|
|
||||||
"label": "",
|
|
||||||
"source": "message:get_contact",
|
|
||||||
"target": "answer:0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "104ac8bb-5d75-4eca-8065-1d8fc2805b8e",
|
|
||||||
"label": "",
|
|
||||||
"source": "answer:0",
|
|
||||||
"target": "categorize:0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "755864b5-9eef-44ec-a560-9a23b5a3f9ee",
|
|
||||||
"label": "",
|
|
||||||
"source": "categorize:0",
|
|
||||||
"target": "retrieval:0",
|
|
||||||
"sourceHandle": "product_related"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7f68384d-3441-4bfa-bf13-69af67e857d2",
|
|
||||||
"label": "",
|
|
||||||
"source": "categorize:0",
|
|
||||||
"target": "generate:casual",
|
|
||||||
"sourceHandle": "casual"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "c9bf8e81-9345-4885-b565-be2f5b16f6ef",
|
|
||||||
"label": "",
|
|
||||||
"source": "categorize:0",
|
|
||||||
"target": "generate:complain",
|
|
||||||
"sourceHandle": "complain"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2f326699-621b-4d28-ab98-70d99ad21add",
|
|
||||||
"label": "",
|
|
||||||
"source": "categorize:0",
|
|
||||||
"target": "message:get_contact",
|
|
||||||
"sourceHandle": "answer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "03e45174-55df-47d6-8e5f-fbe2ffc148d7",
|
|
||||||
"label": "",
|
|
||||||
"source": "retrieval:0",
|
|
||||||
"target": "relevant:0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "a26027ac-e8a9-48e4-814c-3262b8d81913",
|
|
||||||
"label": "",
|
|
||||||
"source": "relevant:0",
|
|
||||||
"target": "generate:answer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "04d99dc7-6120-4169-98c6-7bc2813aa85b",
|
|
||||||
"label": "",
|
|
||||||
"source": "relevant:0",
|
|
||||||
"target": "generate:ask_contact"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "begin",
|
|
||||||
"type": "beginNode",
|
|
||||||
"position": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"label": "Begin",
|
|
||||||
"name": "LegalPoetsAttack",
|
|
||||||
"form": {
|
|
||||||
"prologue": "Hi! How can I help you?"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sourcePosition": "left",
|
|
||||||
"targetPosition": "right"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "answer:0",
|
|
||||||
"type": "ragNode",
|
|
||||||
"position": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"label": "Answer",
|
|
||||||
"name": "ThreeGeeseBehave",
|
|
||||||
"form": {}
|
|
||||||
},
|
|
||||||
"sourcePosition": "left",
|
|
||||||
"targetPosition": "right"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "categorize:0",
|
|
||||||
"type": "categorizeNode",
|
|
||||||
"position": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"label": "Categorize",
|
|
||||||
"name": "PublicComicsHammer",
|
|
||||||
"form": {
|
|
||||||
"llm_id": "deepseek-chat",
|
|
||||||
"category_description": {
|
|
||||||
"product_related": {
|
|
||||||
"description": "The question is about the product usage, appearance and how it works.",
|
|
||||||
"examples": "Why it always beaming?\nHow to install it onto the wall?\nIt leaks, what to do?\nException: Can't connect to ES cluster\nHow to build the RAGFlow image from scratch",
|
|
||||||
"to": "retrieval:0"
|
|
||||||
},
|
|
||||||
"casual": {
|
|
||||||
"description": "The question is not about the product usage, appearance and how it works. Just casual chat.",
|
|
||||||
"examples": "How are you doing?\nWhat is your name?\nAre you a robot?\nWhat's the weather?\nWill it rain?",
|
|
||||||
"to": "generate:casual"
|
|
||||||
},
|
|
||||||
"complain": {
|
|
||||||
"description": "Complain even curse about the product or service you provide. But the comment is not specific enough.",
|
|
||||||
"examples": "How bad is it.\nIt's really sucks.\nDamn, for God's sake, can it be more steady?\nShit, I just can't use this shit.\nI can't stand it anymore.",
|
|
||||||
"to": "generate:complain"
|
|
||||||
},
|
|
||||||
"answer": {
|
|
||||||
"description": "This answer provide a specific contact information, like e-mail, phone number, wechat number, line number, twitter, discord, etc,.",
|
|
||||||
"examples": "My phone number is 203921\nkevinhu.hk@gmail.com\nThis is my discord number: johndowson_29384",
|
|
||||||
"to": "message:get_contact"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"message_history_window_size": 8
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sourcePosition": "left",
|
|
||||||
"targetPosition": "right"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "generate:casual",
|
|
||||||
"type": "ragNode",
|
|
||||||
"position": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"label": "Generate",
|
|
||||||
"name": "SourKnivesPay",
|
|
||||||
"form": {
|
|
||||||
"llm_id": "deepseek-chat",
|
|
||||||
"prompt": "You are a customer support. But the customer wants to have a casual chat with you instead of consulting about the product. Be nice, funny, enthusiasm and concern.",
|
|
||||||
"temperature": 0.9,
|
|
||||||
"message_history_window_size": 12,
|
|
||||||
"cite": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sourcePosition": "left",
|
|
||||||
"targetPosition": "right"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "generate:complain",
|
|
||||||
"type": "ragNode",
|
|
||||||
"position": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"label": "Generate",
|
|
||||||
"name": "TameLlamasSniff",
|
|
||||||
"form": {
|
|
||||||
"llm_id": "deepseek-chat",
|
|
||||||
"prompt": "You are a customer support. the Customers complain even curse about the products but not specific enough. You need to ask him/her what's the specific problem with the product. Be nice, patient and concern to soothe your customers’ emotions at first place.",
|
|
||||||
"temperature": 0.9,
|
|
||||||
"message_history_window_size": 12,
|
|
||||||
"cite": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sourcePosition": "left",
|
|
||||||
"targetPosition": "right"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "retrieval:0",
|
|
||||||
"type": "ragNode",
|
|
||||||
"position": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"label": "Retrieval",
|
|
||||||
"name": "ShinyPathsDraw",
|
|
||||||
"form": {
|
|
||||||
"similarity_threshold": 0.2,
|
|
||||||
"keywords_similarity_weight": 0.3,
|
|
||||||
"top_n": 6,
|
|
||||||
"top_k": 1024,
|
|
||||||
"rerank_id": "BAAI/bge-reranker-v2-m3",
|
|
||||||
"kb_ids": ["869a236818b811ef91dffa163e197198"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sourcePosition": "left",
|
|
||||||
"targetPosition": "right"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "relevant:0",
|
|
||||||
"type": "relevantNode",
|
|
||||||
"position": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"label": "Relevant",
|
|
||||||
"name": "LegalPotsLick",
|
|
||||||
"form": {
|
|
||||||
"llm_id": "deepseek-chat",
|
|
||||||
"temperature": 0.02,
|
|
||||||
"yes": "generate:answer",
|
|
||||||
"no": "generate:ask_contact"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sourcePosition": "left",
|
|
||||||
"targetPosition": "right"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "generate:answer",
|
|
||||||
"type": "ragNode",
|
|
||||||
"position": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"label": "Generate",
|
|
||||||
"name": "YellowGamesReport",
|
|
||||||
"form": {
|
|
||||||
"llm_id": "deepseek-chat",
|
|
||||||
"prompt": "You are an intelligent assistant. Please answer the question based on content of knowledge base. When all knowledge base content is irrelevant to the question, your answer must include the sentence \"The answer you are looking for is not found in the knowledge base!\". Answers need to consider chat history.\n Knowledge base content is as following:\n {input}\n The above is the content of knowledge base.",
|
|
||||||
"temperature": 0.02
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sourcePosition": "left",
|
|
||||||
"targetPosition": "right"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "generate:ask_contact",
|
|
||||||
"type": "ragNode",
|
|
||||||
"position": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"label": "Generate",
|
|
||||||
"name": "FamousChefsRetire",
|
|
||||||
"form": {
|
|
||||||
"llm_id": "deepseek-chat",
|
|
||||||
"prompt": "You are a customer support. But you can't answer to customers' question. You need to request their contact like E-mail, phone number, Wechat number, LINE number, twitter, discord, etc,. Product experts will contact them later. Please do not ask the same question twice.",
|
|
||||||
"temperature": 0.9,
|
|
||||||
"message_history_window_size": 12,
|
|
||||||
"cite": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sourcePosition": "left",
|
|
||||||
"targetPosition": "right"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "message:get_contact",
|
|
||||||
"type": "ragNode",
|
|
||||||
"position": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"label": "Message",
|
|
||||||
"name": "BlueBooksTan",
|
|
||||||
"form": {
|
|
||||||
"messages": [
|
|
||||||
"Okay, I've already write this down. What else I can do for you?",
|
|
||||||
"Get it. What else I can do for you?",
|
|
||||||
"Thanks for your trust! Our expert will contact ASAP. So, anything else I can do for you?",
|
|
||||||
"Thanks! So, anything else I can do for you?"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sourcePosition": "left",
|
|
||||||
"targetPosition": "right"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
.formWrapper {
|
|
||||||
:global(.ant-form-item-label) {
|
|
||||||
font-weight: 600 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,250 +0,0 @@
|
|||||||
import { Authorization } from '@/constants/authorization';
|
|
||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
|
||||||
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
|
|
||||||
import { useHandleSubmittable } from '@/hooks/login-hooks';
|
|
||||||
import api from '@/utils/api';
|
|
||||||
import { getAuthorization } from '@/utils/authorization-util';
|
|
||||||
import { UploadOutlined } from '@ant-design/icons';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Form,
|
|
||||||
FormItemProps,
|
|
||||||
Input,
|
|
||||||
InputNumber,
|
|
||||||
Select,
|
|
||||||
Switch,
|
|
||||||
Upload,
|
|
||||||
} from 'antd';
|
|
||||||
import { UploadChangeParam, UploadFile } from 'antd/es/upload';
|
|
||||||
import { pick } from 'lodash';
|
|
||||||
import { Link } from 'lucide-react';
|
|
||||||
import React, { useCallback, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { BeginQueryType } from '../constant';
|
|
||||||
import { BeginQuery } from '../interface';
|
|
||||||
import { PopoverForm } from './popover-form';
|
|
||||||
|
|
||||||
import styles from './index.less';
|
|
||||||
import KnowledgeBaseItem from '@/components/knowledge-base-item';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
parameters: BeginQuery[];
|
|
||||||
ok(parameters: any[]): void;
|
|
||||||
isNext?: boolean;
|
|
||||||
loading?: boolean;
|
|
||||||
submitButtonDisabled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DebugContent = ({
|
|
||||||
parameters,
|
|
||||||
ok,
|
|
||||||
isNext = true,
|
|
||||||
loading = false,
|
|
||||||
submitButtonDisabled = false,
|
|
||||||
}: IProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const {
|
|
||||||
visible,
|
|
||||||
hideModal: hidePopover,
|
|
||||||
switchVisible,
|
|
||||||
showModal: showPopover,
|
|
||||||
} = useSetModalState();
|
|
||||||
const { setRecord, currentRecord } = useSetSelectedRecord<number>();
|
|
||||||
const { submittable } = useHandleSubmittable(form);
|
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
|
||||||
|
|
||||||
const handleShowPopover = useCallback(
|
|
||||||
(idx: number) => () => {
|
|
||||||
setRecord(idx);
|
|
||||||
showPopover();
|
|
||||||
},
|
|
||||||
[setRecord, showPopover],
|
|
||||||
);
|
|
||||||
|
|
||||||
const normFile = (e: any) => {
|
|
||||||
if (Array.isArray(e)) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
return e?.fileList;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onChange = useCallback(
|
|
||||||
(optional: boolean) =>
|
|
||||||
({ fileList }: UploadChangeParam<UploadFile>) => {
|
|
||||||
if (!optional) {
|
|
||||||
setIsUploading(fileList.some((x) => x.status === 'uploading'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderWidget = useCallback(
|
|
||||||
(q: BeginQuery, idx: number) => {
|
|
||||||
const props: FormItemProps & { key: number } = {
|
|
||||||
key: idx,
|
|
||||||
label: q.name ?? q.key,
|
|
||||||
name: idx,
|
|
||||||
};
|
|
||||||
if (q.optional === false) {
|
|
||||||
props.rules = [{ required: true }];
|
|
||||||
}
|
|
||||||
|
|
||||||
const urlList: { url: string; result: string }[] =
|
|
||||||
form.getFieldValue(idx) || [];
|
|
||||||
|
|
||||||
const BeginQueryTypeMap = {
|
|
||||||
[BeginQueryType.Line]: (
|
|
||||||
<Form.Item {...props}>
|
|
||||||
<Input></Input>
|
|
||||||
</Form.Item>
|
|
||||||
),
|
|
||||||
[BeginQueryType.Paragraph]: (
|
|
||||||
<Form.Item {...props}>
|
|
||||||
<Input.TextArea rows={1}></Input.TextArea>
|
|
||||||
</Form.Item>
|
|
||||||
),
|
|
||||||
[BeginQueryType.Options]: (
|
|
||||||
<Form.Item {...props}>
|
|
||||||
<Select
|
|
||||||
allowClear
|
|
||||||
options={q.options?.map((x) => ({ label: x, value: x })) ?? []}
|
|
||||||
></Select>
|
|
||||||
</Form.Item>
|
|
||||||
),
|
|
||||||
[BeginQueryType.File]: (
|
|
||||||
<React.Fragment key={idx}>
|
|
||||||
<Form.Item label={q.name ?? q.key} required={!q.optional}>
|
|
||||||
<div className="relative">
|
|
||||||
<Form.Item
|
|
||||||
{...props}
|
|
||||||
valuePropName="fileList"
|
|
||||||
getValueFromEvent={normFile}
|
|
||||||
noStyle
|
|
||||||
>
|
|
||||||
<Upload
|
|
||||||
name="file"
|
|
||||||
action={api.parse}
|
|
||||||
multiple
|
|
||||||
headers={{ [Authorization]: getAuthorization() }}
|
|
||||||
onChange={onChange(q.optional)}
|
|
||||||
>
|
|
||||||
<Button icon={<UploadOutlined />}>
|
|
||||||
{t('common.upload')}
|
|
||||||
</Button>
|
|
||||||
</Upload>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
{...pick(props, ['key', 'label', 'rules'])}
|
|
||||||
required={!q.optional}
|
|
||||||
className={urlList.length > 0 ? 'mb-1' : ''}
|
|
||||||
noStyle
|
|
||||||
>
|
|
||||||
<PopoverForm visible={visible} switchVisible={switchVisible}>
|
|
||||||
<Button
|
|
||||||
onClick={handleShowPopover(idx)}
|
|
||||||
className="absolute left-1/2 top-0"
|
|
||||||
icon={<Link className="size-3" />}
|
|
||||||
>
|
|
||||||
{t('flow.pasteFileLink')}
|
|
||||||
</Button>
|
|
||||||
</PopoverForm>
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name={idx} noStyle {...pick(props, ['rules'])} />
|
|
||||||
</React.Fragment>
|
|
||||||
),
|
|
||||||
[BeginQueryType.Integer]: (
|
|
||||||
<Form.Item {...props}>
|
|
||||||
<InputNumber></InputNumber>
|
|
||||||
</Form.Item>
|
|
||||||
),
|
|
||||||
[BeginQueryType.Boolean]: (
|
|
||||||
<Form.Item valuePropName={'checked'} {...props}>
|
|
||||||
<Switch></Switch>
|
|
||||||
</Form.Item>
|
|
||||||
),
|
|
||||||
[BeginQueryType.KnowledgeBases]: (
|
|
||||||
<KnowledgeBaseItem
|
|
||||||
name={idx.toString()}
|
|
||||||
label={q.name || q.key}
|
|
||||||
required={!q.optional}
|
|
||||||
></KnowledgeBaseItem>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
BeginQueryTypeMap[q.type as BeginQueryType] ??
|
|
||||||
BeginQueryTypeMap[BeginQueryType.Paragraph]
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[form, handleShowPopover, onChange, switchVisible, t, visible],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onOk = useCallback(async () => {
|
|
||||||
const values = await form.validateFields();
|
|
||||||
const nextValues = Object.entries(values).map(([key, value]) => {
|
|
||||||
const item = parameters[Number(key)];
|
|
||||||
let nextValue = value;
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
nextValue = ``;
|
|
||||||
|
|
||||||
if (item.type === 'kb') {
|
|
||||||
nextValue = value.join(',')
|
|
||||||
} else {
|
|
||||||
value.forEach((x) => {
|
|
||||||
nextValue +=
|
|
||||||
x?.originFileObj instanceof File
|
|
||||||
? `${x.name}\n${x.response?.data}\n----\n`
|
|
||||||
: `${x.url}\n${x.result}\n----\n`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { ...item, value: nextValue };
|
|
||||||
});
|
|
||||||
|
|
||||||
ok(nextValues);
|
|
||||||
}, [form, ok, parameters]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<section className={styles.formWrapper}>
|
|
||||||
<Form.Provider
|
|
||||||
onFormFinish={(name, { values, forms }) => {
|
|
||||||
if (name === 'urlForm') {
|
|
||||||
const { basicForm } = forms;
|
|
||||||
const urlInfo = basicForm.getFieldValue(currentRecord) || [];
|
|
||||||
basicForm.setFieldsValue({
|
|
||||||
[currentRecord]: [...urlInfo, { ...values, name: values.url }],
|
|
||||||
});
|
|
||||||
hidePopover();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Form
|
|
||||||
name="basicForm"
|
|
||||||
autoComplete="off"
|
|
||||||
layout={'vertical'}
|
|
||||||
form={form}
|
|
||||||
>
|
|
||||||
{parameters.map((x, idx) => {
|
|
||||||
return renderWidget(x, idx);
|
|
||||||
})}
|
|
||||||
</Form>
|
|
||||||
</Form.Provider>
|
|
||||||
</section>
|
|
||||||
<Button
|
|
||||||
type={'primary'}
|
|
||||||
block
|
|
||||||
onClick={onOk}
|
|
||||||
loading={loading}
|
|
||||||
disabled={!submittable || isUploading || submitButtonDisabled}
|
|
||||||
>
|
|
||||||
{t(isNext ? 'common.next' : 'flow.run')}
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DebugContent;
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
import { useParseDocument } from '@/hooks/document-hooks';
|
|
||||||
import { useResetFormOnCloseModal } from '@/hooks/logic-hooks';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { Button, Form, Input, Popover } from 'antd';
|
|
||||||
import { PropsWithChildren } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const reg =
|
|
||||||
/^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/;
|
|
||||||
|
|
||||||
export const PopoverForm = ({
|
|
||||||
children,
|
|
||||||
visible,
|
|
||||||
switchVisible,
|
|
||||||
}: PropsWithChildren<IModalProps<any>>) => {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const { parseDocument, loading } = useParseDocument();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
useResetFormOnCloseModal({
|
|
||||||
form,
|
|
||||||
visible,
|
|
||||||
});
|
|
||||||
|
|
||||||
const onOk = async () => {
|
|
||||||
const values = await form.validateFields();
|
|
||||||
const val = values.url;
|
|
||||||
|
|
||||||
if (reg.test(val)) {
|
|
||||||
const ret = await parseDocument(val);
|
|
||||||
if (ret?.data?.code === 0) {
|
|
||||||
form.setFieldValue('result', ret?.data?.data);
|
|
||||||
form.submit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const content = (
|
|
||||||
<Form form={form} name="urlForm">
|
|
||||||
<Form.Item
|
|
||||||
name="url"
|
|
||||||
rules={[{ required: true, type: 'url' }]}
|
|
||||||
className="m-0"
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
onPressEnter={(e) => e.preventDefault()}
|
|
||||||
placeholder={t('flow.pasteFileLink')}
|
|
||||||
suffix={
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={onOk}
|
|
||||||
size={'small'}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
{t('common.submit')}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name={'result'} noStyle />
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover
|
|
||||||
content={content}
|
|
||||||
open={visible}
|
|
||||||
trigger={'click'}
|
|
||||||
onOpenChange={switchVisible}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
.title {
|
|
||||||
flex-basis: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formWrapper {
|
|
||||||
:global(.ant-form-item-label) {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.operatorDescription {
|
|
||||||
font-size: 14px;
|
|
||||||
padding-top: 16px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formDrawer {
|
|
||||||
:global(.ant-drawer-content-wrapper) {
|
|
||||||
transform: translateX(0) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,218 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { CloseOutlined } from '@ant-design/icons';
|
|
||||||
import { Drawer, Flex, Form, Input } from 'antd';
|
|
||||||
import { get, isPlainObject, lowerFirst } from 'lodash';
|
|
||||||
import { Play } from 'lucide-react';
|
|
||||||
import { useEffect, useRef } from 'react';
|
|
||||||
import { BeginId, Operator, operatorMap } from '../constant';
|
|
||||||
import AkShareForm from '../form/akshare-form';
|
|
||||||
import AnswerForm from '../form/answer-form';
|
|
||||||
import ArXivForm from '../form/arxiv-form';
|
|
||||||
import BaiduFanyiForm from '../form/baidu-fanyi-form';
|
|
||||||
import BaiduForm from '../form/baidu-form';
|
|
||||||
import BeginForm from '../form/begin-form';
|
|
||||||
import BingForm from '../form/bing-form';
|
|
||||||
import CategorizeForm from '../form/categorize-form';
|
|
||||||
import CodeForm from '../form/code-form';
|
|
||||||
import CrawlerForm from '../form/crawler-form';
|
|
||||||
import DeepLForm from '../form/deepl-form';
|
|
||||||
import DuckDuckGoForm from '../form/duckduckgo-form';
|
|
||||||
import EmailForm from '../form/email-form';
|
|
||||||
import ExeSQLForm from '../form/exesql-form';
|
|
||||||
import GenerateForm from '../form/generate-form';
|
|
||||||
import GithubForm from '../form/github-form';
|
|
||||||
import GoogleForm from '../form/google-form';
|
|
||||||
import GoogleScholarForm from '../form/google-scholar-form';
|
|
||||||
import InvokeForm from '../form/invoke-form';
|
|
||||||
import Jin10Form from '../form/jin10-form';
|
|
||||||
import KeywordExtractForm from '../form/keyword-extract-form';
|
|
||||||
import MessageForm from '../form/message-form';
|
|
||||||
import PubMedForm from '../form/pubmed-form';
|
|
||||||
import QWeatherForm from '../form/qweather-form';
|
|
||||||
import RelevantForm from '../form/relevant-form';
|
|
||||||
import RetrievalForm from '../form/retrieval-form';
|
|
||||||
import RewriteQuestionForm from '../form/rewrite-question-form';
|
|
||||||
import SwitchForm from '../form/switch-form';
|
|
||||||
import TemplateForm from '../form/template-form';
|
|
||||||
import TuShareForm from '../form/tushare-form';
|
|
||||||
import WenCaiForm from '../form/wencai-form';
|
|
||||||
import WikipediaForm from '../form/wikipedia-form';
|
|
||||||
import YahooFinanceForm from '../form/yahoo-finance-form';
|
|
||||||
import { useHandleFormValuesChange, useHandleNodeNameChange } from '../hooks';
|
|
||||||
import OperatorIcon from '../operator-icon';
|
|
||||||
import {
|
|
||||||
buildCategorizeListFromObject,
|
|
||||||
getDrawerWidth,
|
|
||||||
needsSingleStepDebugging,
|
|
||||||
} from '../utils';
|
|
||||||
import SingleDebugDrawer from './single-debug-drawer';
|
|
||||||
|
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { FlowFormContext } from '../context';
|
|
||||||
import { RunTooltip } from '../flow-tooltip';
|
|
||||||
import IterationForm from '../form/iteration-from';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
node?: RAGFlowNodeType;
|
|
||||||
singleDebugDrawerVisible: IModalProps<any>['visible'];
|
|
||||||
hideSingleDebugDrawer: IModalProps<any>['hideModal'];
|
|
||||||
showSingleDebugDrawer: IModalProps<any>['showModal'];
|
|
||||||
}
|
|
||||||
|
|
||||||
const FormMap = {
|
|
||||||
[Operator.Begin]: BeginForm,
|
|
||||||
[Operator.Retrieval]: RetrievalForm,
|
|
||||||
[Operator.Generate]: GenerateForm,
|
|
||||||
[Operator.Answer]: AnswerForm,
|
|
||||||
[Operator.Categorize]: CategorizeForm,
|
|
||||||
[Operator.Message]: MessageForm,
|
|
||||||
[Operator.Relevant]: RelevantForm,
|
|
||||||
[Operator.RewriteQuestion]: RewriteQuestionForm,
|
|
||||||
[Operator.Baidu]: BaiduForm,
|
|
||||||
[Operator.DuckDuckGo]: DuckDuckGoForm,
|
|
||||||
[Operator.KeywordExtract]: KeywordExtractForm,
|
|
||||||
[Operator.Wikipedia]: WikipediaForm,
|
|
||||||
[Operator.PubMed]: PubMedForm,
|
|
||||||
[Operator.ArXiv]: ArXivForm,
|
|
||||||
[Operator.Google]: GoogleForm,
|
|
||||||
[Operator.Bing]: BingForm,
|
|
||||||
[Operator.GoogleScholar]: GoogleScholarForm,
|
|
||||||
[Operator.DeepL]: DeepLForm,
|
|
||||||
[Operator.GitHub]: GithubForm,
|
|
||||||
[Operator.BaiduFanyi]: BaiduFanyiForm,
|
|
||||||
[Operator.QWeather]: QWeatherForm,
|
|
||||||
[Operator.ExeSQL]: ExeSQLForm,
|
|
||||||
[Operator.Switch]: SwitchForm,
|
|
||||||
[Operator.WenCai]: WenCaiForm,
|
|
||||||
[Operator.AkShare]: AkShareForm,
|
|
||||||
[Operator.YahooFinance]: YahooFinanceForm,
|
|
||||||
[Operator.Jin10]: Jin10Form,
|
|
||||||
[Operator.TuShare]: TuShareForm,
|
|
||||||
[Operator.Crawler]: CrawlerForm,
|
|
||||||
[Operator.Invoke]: InvokeForm,
|
|
||||||
[Operator.Concentrator]: () => <></>,
|
|
||||||
[Operator.Note]: () => <></>,
|
|
||||||
[Operator.Template]: TemplateForm,
|
|
||||||
[Operator.Email]: EmailForm,
|
|
||||||
[Operator.Iteration]: IterationForm,
|
|
||||||
[Operator.IterationStart]: () => <></>,
|
|
||||||
[Operator.Code]: CodeForm,
|
|
||||||
};
|
|
||||||
|
|
||||||
const EmptyContent = () => <div></div>;
|
|
||||||
|
|
||||||
const FormDrawer = ({
|
|
||||||
visible,
|
|
||||||
hideModal,
|
|
||||||
node,
|
|
||||||
singleDebugDrawerVisible,
|
|
||||||
hideSingleDebugDrawer,
|
|
||||||
showSingleDebugDrawer,
|
|
||||||
}: IModalProps<any> & IProps) => {
|
|
||||||
const operatorName: Operator = node?.data.label as Operator;
|
|
||||||
const OperatorForm = FormMap[operatorName] ?? EmptyContent;
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const { name, handleNameBlur, handleNameChange } = useHandleNodeNameChange({
|
|
||||||
id: node?.id,
|
|
||||||
data: node?.data,
|
|
||||||
});
|
|
||||||
const previousId = useRef<string | undefined>(node?.id);
|
|
||||||
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
const { handleValuesChange } = useHandleFormValuesChange(node?.id);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (visible) {
|
|
||||||
if (node?.id !== previousId.current) {
|
|
||||||
form.resetFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operatorName === Operator.Categorize) {
|
|
||||||
const items = buildCategorizeListFromObject(
|
|
||||||
get(node, 'data.form.category_description', {}),
|
|
||||||
);
|
|
||||||
const formData = node?.data?.form;
|
|
||||||
if (isPlainObject(formData)) {
|
|
||||||
form.setFieldsValue({ ...formData, items });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
form.setFieldsValue(node?.data?.form);
|
|
||||||
}
|
|
||||||
previousId.current = node?.id;
|
|
||||||
}
|
|
||||||
}, [visible, form, node?.data?.form, node?.id, node, operatorName]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Drawer
|
|
||||||
title={
|
|
||||||
<Flex vertical>
|
|
||||||
<Flex gap={'middle'} align="center">
|
|
||||||
<OperatorIcon
|
|
||||||
name={operatorName}
|
|
||||||
color={operatorMap[operatorName]?.color}
|
|
||||||
></OperatorIcon>
|
|
||||||
<Flex align="center" gap={'small'} flex={1}>
|
|
||||||
<label htmlFor="" className={styles.title}>
|
|
||||||
{t('title')}
|
|
||||||
</label>
|
|
||||||
{node?.id === BeginId ? (
|
|
||||||
<span>{t(BeginId)}</span>
|
|
||||||
) : (
|
|
||||||
<Input
|
|
||||||
value={name}
|
|
||||||
onBlur={handleNameBlur}
|
|
||||||
onChange={handleNameChange}
|
|
||||||
></Input>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{needsSingleStepDebugging(operatorName) && (
|
|
||||||
<RunTooltip>
|
|
||||||
<Play
|
|
||||||
className="size-5 cursor-pointer"
|
|
||||||
onClick={showSingleDebugDrawer}
|
|
||||||
/>
|
|
||||||
</RunTooltip>
|
|
||||||
)}
|
|
||||||
<CloseOutlined onClick={hideModal} />
|
|
||||||
</Flex>
|
|
||||||
<span className={styles.operatorDescription}>
|
|
||||||
{t(`${lowerFirst(operatorName)}Description`)}
|
|
||||||
</span>
|
|
||||||
</Flex>
|
|
||||||
}
|
|
||||||
placement="right"
|
|
||||||
onClose={hideModal}
|
|
||||||
open={visible}
|
|
||||||
getContainer={false}
|
|
||||||
mask={false}
|
|
||||||
width={getDrawerWidth()}
|
|
||||||
closeIcon={null}
|
|
||||||
rootClassName={styles.formDrawer}
|
|
||||||
>
|
|
||||||
<section className={styles.formWrapper}>
|
|
||||||
{visible && (
|
|
||||||
<FlowFormContext.Provider value={node}>
|
|
||||||
<OperatorForm
|
|
||||||
onValuesChange={handleValuesChange}
|
|
||||||
form={form}
|
|
||||||
node={node}
|
|
||||||
></OperatorForm>
|
|
||||||
</FlowFormContext.Provider>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
{singleDebugDrawerVisible && (
|
|
||||||
<SingleDebugDrawer
|
|
||||||
visible={singleDebugDrawerVisible}
|
|
||||||
hideModal={hideSingleDebugDrawer}
|
|
||||||
componentId={node?.id}
|
|
||||||
></SingleDebugDrawer>
|
|
||||||
)}
|
|
||||||
</Drawer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FormDrawer;
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
import CopyToClipboard from '@/components/copy-to-clipboard';
|
|
||||||
import { useDebugSingle, useFetchInputElements } from '@/hooks/flow-hooks';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { CloseOutlined } from '@ant-design/icons';
|
|
||||||
import { Drawer } from 'antd';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import JsonView from 'react18-json-view';
|
|
||||||
import 'react18-json-view/src/style.css';
|
|
||||||
import DebugContent from '../../debug-content';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
componentId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SingleDebugDrawer = ({
|
|
||||||
componentId,
|
|
||||||
visible,
|
|
||||||
hideModal,
|
|
||||||
}: IModalProps<any> & IProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { data: list } = useFetchInputElements(componentId);
|
|
||||||
const { debugSingle, data, loading } = useDebugSingle();
|
|
||||||
|
|
||||||
const onOk = useCallback(
|
|
||||||
(nextValues: any[]) => {
|
|
||||||
if (componentId) {
|
|
||||||
debugSingle({ component_id: componentId, params: nextValues });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[componentId, debugSingle],
|
|
||||||
);
|
|
||||||
|
|
||||||
const content = JSON.stringify(data, null, 2);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Drawer
|
|
||||||
title={
|
|
||||||
<div className="flex justify-between">
|
|
||||||
{t('flow.testRun')}
|
|
||||||
<CloseOutlined onClick={hideModal} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
width={'100%'}
|
|
||||||
onClose={hideModal}
|
|
||||||
open={visible}
|
|
||||||
getContainer={false}
|
|
||||||
mask={false}
|
|
||||||
placement={'bottom'}
|
|
||||||
height={'95%'}
|
|
||||||
closeIcon={null}
|
|
||||||
>
|
|
||||||
<section className="overflow-y-auto">
|
|
||||||
<DebugContent
|
|
||||||
parameters={list}
|
|
||||||
ok={onOk}
|
|
||||||
isNext={false}
|
|
||||||
loading={loading}
|
|
||||||
submitButtonDisabled={list.length === 0}
|
|
||||||
></DebugContent>
|
|
||||||
{!isEmpty(data) ? (
|
|
||||||
<div className="mt-4 rounded-md bg-slate-200 border border-neutral-200">
|
|
||||||
<div className="flex justify-between p-2">
|
|
||||||
<span>JSON</span>
|
|
||||||
<CopyToClipboard text={content}></CopyToClipboard>
|
|
||||||
</div>
|
|
||||||
<JsonView
|
|
||||||
src={data}
|
|
||||||
displaySize
|
|
||||||
collapseStringsAfterLength={100000000000}
|
|
||||||
className="w-full h-[800px] break-words overflow-auto p-2 bg-slate-100"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</section>
|
|
||||||
</Drawer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SingleDebugDrawer;
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
.id {
|
|
||||||
.linkText();
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { Modal, Typography } from 'antd';
|
|
||||||
|
|
||||||
import { useParams } from 'umi';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
const { Paragraph, Link } = Typography;
|
|
||||||
|
|
||||||
const FlowIdModal = ({ hideModal }: IModalProps<any>) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const { id } = useParams();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={'Agent ID'}
|
|
||||||
open
|
|
||||||
onCancel={hideModal}
|
|
||||||
cancelButtonProps={{ style: { display: 'none' } }}
|
|
||||||
onOk={hideModal}
|
|
||||||
okText={t('close', { keyPrefix: 'common' })}
|
|
||||||
>
|
|
||||||
<Paragraph copyable={{ text: id }} className={styles.id}>
|
|
||||||
{id}
|
|
||||||
</Paragraph>
|
|
||||||
<Link
|
|
||||||
href="https://ragflow.io/docs/dev/http_api_reference#create-session-with-an-agent"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{t('howUseId')}
|
|
||||||
</Link>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FlowIdModal;
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { useFetchFlow, useSettingFlow } from '@/hooks/flow-hooks';
|
|
||||||
import { normFile } from '@/utils/file-util';
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { Form, Input, Modal, Radio, Upload } from 'antd';
|
|
||||||
import React, { useCallback, useEffect } from 'react';
|
|
||||||
export function useFlowSettingModal() {
|
|
||||||
const [visibleSettingModal, setVisibleSettingMModal] = React.useState(false);
|
|
||||||
|
|
||||||
return {
|
|
||||||
visibleSettingModal,
|
|
||||||
setVisibleSettingMModal,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
type FlowSettingModalProps = {
|
|
||||||
visible: boolean;
|
|
||||||
hideModal: () => void;
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
export const FlowSettingModal = ({
|
|
||||||
hideModal,
|
|
||||||
visible,
|
|
||||||
id,
|
|
||||||
}: FlowSettingModalProps) => {
|
|
||||||
const { data, refetch } = useFetchFlow();
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const { t } = useTranslate('flow.settings');
|
|
||||||
const { loading, settingFlow } = useSettingFlow();
|
|
||||||
// Initialize form with data when it becomes available
|
|
||||||
useEffect(() => {
|
|
||||||
if (data) {
|
|
||||||
form.setFieldsValue({
|
|
||||||
title: data.title,
|
|
||||||
description: data.description,
|
|
||||||
permission: data.permission,
|
|
||||||
avatar: data.avatar ? [{ thumbUrl: data.avatar }] : [],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data, form]);
|
|
||||||
|
|
||||||
const handleSubmit = useCallback(async () => {
|
|
||||||
if (!id) return;
|
|
||||||
try {
|
|
||||||
const { avatar, ...others } = await form.validateFields();
|
|
||||||
const param = {
|
|
||||||
...others,
|
|
||||||
id,
|
|
||||||
avatar: avatar && avatar.length > 0 ? avatar[0].thumbUrl : '',
|
|
||||||
};
|
|
||||||
settingFlow(param);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Validation failed:', error);
|
|
||||||
}
|
|
||||||
}, [form, id, settingFlow]);
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!loading && refetch && visible) {
|
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
}, [loading, refetch, visible]);
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
confirmLoading={loading}
|
|
||||||
title={t('agentSetting')}
|
|
||||||
open={visible}
|
|
||||||
onCancel={hideModal}
|
|
||||||
onOk={handleSubmit}
|
|
||||||
okText={t('save', { keyPrefix: 'common' })}
|
|
||||||
cancelText={t('cancel', { keyPrefix: 'common' })}
|
|
||||||
>
|
|
||||||
<Form
|
|
||||||
form={form}
|
|
||||||
labelCol={{ span: 6 }}
|
|
||||||
wrapperCol={{ span: 18 }}
|
|
||||||
layout="horizontal"
|
|
||||||
style={{ maxWidth: 600 }}
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
name="title"
|
|
||||||
label={t('title')}
|
|
||||||
rules={[{ required: true, message: 'Please input a title!' }]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="avatar"
|
|
||||||
label={t('photo')}
|
|
||||||
valuePropName="fileList"
|
|
||||||
getValueFromEvent={normFile}
|
|
||||||
>
|
|
||||||
<Upload
|
|
||||||
listType="picture-card"
|
|
||||||
maxCount={1}
|
|
||||||
beforeUpload={() => false}
|
|
||||||
showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
|
|
||||||
>
|
|
||||||
<button style={{ border: 0, background: 'none' }} type="button">
|
|
||||||
<PlusOutlined />
|
|
||||||
<div style={{ marginTop: 8 }}>{t('upload')}</div>
|
|
||||||
</button>
|
|
||||||
</Upload>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="description" label={t('description')}>
|
|
||||||
<Input.TextArea rows={4} />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
name="permission"
|
|
||||||
label={t('permissions')}
|
|
||||||
tooltip={t('permissionsTip')}
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
>
|
|
||||||
<Radio.Group>
|
|
||||||
<Radio value="me">{t('me')}</Radio>
|
|
||||||
<Radio value="team">{t('team')}</Radio>
|
|
||||||
</Radio.Group>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
.operatorCard {
|
|
||||||
:global(.ant-card-body) {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
.cubeIcon {
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.siderContent {
|
|
||||||
padding: 10px 4px;
|
|
||||||
overflow: auto;
|
|
||||||
height: calc(100vh - 80px);
|
|
||||||
}
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Card, Divider, Flex, Layout, Tooltip } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import lowerFirst from 'lodash/lowerFirst';
|
|
||||||
import React from 'react';
|
|
||||||
import { Operator, componentMenuList, operatorMap } from '../constant';
|
|
||||||
import { useHandleDrag } from '../hooks';
|
|
||||||
import OperatorIcon from '../operator-icon';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
const { Sider } = Layout;
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
setCollapsed: (width: boolean) => void;
|
|
||||||
collapsed: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dividerProps = {
|
|
||||||
marginTop: 10,
|
|
||||||
marginBottom: 10,
|
|
||||||
padding: 0,
|
|
||||||
borderBlockColor: '#b4afaf',
|
|
||||||
borderStyle: 'dotted',
|
|
||||||
};
|
|
||||||
|
|
||||||
const FlowSide = ({ setCollapsed, collapsed }: IProps) => {
|
|
||||||
const { handleDragStart } = useHandleDrag();
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Sider
|
|
||||||
collapsible
|
|
||||||
collapsed={collapsed}
|
|
||||||
collapsedWidth={0}
|
|
||||||
theme={'light'}
|
|
||||||
onCollapse={(value) => setCollapsed(value)}
|
|
||||||
>
|
|
||||||
<Flex vertical gap={10} className={styles.siderContent}>
|
|
||||||
{componentMenuList.map((x) => {
|
|
||||||
return (
|
|
||||||
<React.Fragment key={x.name}>
|
|
||||||
{x.name === Operator.Note && (
|
|
||||||
<Divider style={dividerProps}></Divider>
|
|
||||||
)}
|
|
||||||
{x.name === Operator.DuckDuckGo && (
|
|
||||||
<Divider style={dividerProps}></Divider>
|
|
||||||
)}
|
|
||||||
<Card
|
|
||||||
key={x.name}
|
|
||||||
hoverable
|
|
||||||
draggable
|
|
||||||
className={classNames(styles.operatorCard)}
|
|
||||||
onDragStart={handleDragStart(x.name)}
|
|
||||||
>
|
|
||||||
<Flex align="center" gap={15}>
|
|
||||||
<OperatorIcon
|
|
||||||
name={x.name}
|
|
||||||
color={operatorMap[x.name].color}
|
|
||||||
></OperatorIcon>
|
|
||||||
<section>
|
|
||||||
<Tooltip title={t(`${lowerFirst(x.name)}Description`)}>
|
|
||||||
<b>{t(lowerFirst(x.name))}</b>
|
|
||||||
</Tooltip>
|
|
||||||
</section>
|
|
||||||
</Flex>
|
|
||||||
</Card>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
</Sider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FlowSide;
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from '@/components/ui/tooltip';
|
|
||||||
import { PropsWithChildren } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
export const RunTooltip = ({ children }: PropsWithChildren) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger>{children}</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{t('flow.testRun')}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { Operator, RestrictedUpstreamMap } from './constant';
|
|
||||||
import useGraphStore from './store';
|
|
||||||
|
|
||||||
export const useBuildFormSelectOptions = (
|
|
||||||
operatorName: Operator,
|
|
||||||
selfId?: string, // exclude the current node
|
|
||||||
) => {
|
|
||||||
const nodes = useGraphStore((state) => state.nodes);
|
|
||||||
|
|
||||||
const buildCategorizeToOptions = useCallback(
|
|
||||||
(toList: string[]) => {
|
|
||||||
const excludedNodes: Operator[] = [
|
|
||||||
Operator.Note,
|
|
||||||
...(RestrictedUpstreamMap[operatorName] ?? []),
|
|
||||||
];
|
|
||||||
return nodes
|
|
||||||
.filter(
|
|
||||||
(x) =>
|
|
||||||
excludedNodes.every((y) => y !== x.data.label) &&
|
|
||||||
x.id !== selfId &&
|
|
||||||
!toList.some((y) => y === x.id), // filter out selected values in other to fields from the current drop-down box options
|
|
||||||
)
|
|
||||||
.map((x) => ({ label: x.data.name, value: x.id }));
|
|
||||||
},
|
|
||||||
[nodes, operatorName, selfId],
|
|
||||||
);
|
|
||||||
|
|
||||||
return buildCategorizeToOptions;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* dumped
|
|
||||||
* @param nodeId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const useHandleFormSelectChange = (nodeId?: string) => {
|
|
||||||
const { addEdge, deleteEdgeBySourceAndSourceHandle } = useGraphStore(
|
|
||||||
(state) => state,
|
|
||||||
);
|
|
||||||
const handleSelectChange = useCallback(
|
|
||||||
(name?: string) => (value?: string) => {
|
|
||||||
if (nodeId && name) {
|
|
||||||
if (value) {
|
|
||||||
addEdge({
|
|
||||||
source: nodeId,
|
|
||||||
target: value,
|
|
||||||
sourceHandle: name,
|
|
||||||
targetHandle: null,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// clear selected value
|
|
||||||
deleteEdgeBySourceAndSourceHandle({
|
|
||||||
source: nodeId,
|
|
||||||
sourceHandle: name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[addEdge, nodeId, deleteEdgeBySourceAndSourceHandle],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { handleSelectChange };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useBuildSortOptions = () => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
const options = useMemo(() => {
|
|
||||||
return ['data', 'relevance'].map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(x),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
return options;
|
|
||||||
};
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import TopNItem from '@/components/top-n-item';
|
|
||||||
import { Form } from 'antd';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const AkShareForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<TopNItem initialValue={10} max={99}></TopNItem>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AkShareForm;
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
const AnswerForm = () => {
|
|
||||||
return <div></div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AnswerForm;
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import TopNItem from '@/components/top-n-item';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Select } from 'antd';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const ArXivForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
const options = useMemo(() => {
|
|
||||||
return ['submittedDate', 'lastUpdatedDate', 'relevance'].map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(x),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
|
|
||||||
<TopNItem initialValue={10}></TopNItem>
|
|
||||||
<Form.Item label={t('sortBy')} name={'sort_by'}>
|
|
||||||
<Select options={options}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ArXivForm;
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Input, Select } from 'antd';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import {
|
|
||||||
BaiduFanyiDomainOptions,
|
|
||||||
BaiduFanyiSourceLangOptions,
|
|
||||||
} from '../../constant';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const BaiduFanyiForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const options = useMemo(() => {
|
|
||||||
return ['translate', 'fieldtranslate'].map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`baiduSecretKeyOptions.${x}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
const baiduFanyiOptions = useMemo(() => {
|
|
||||||
return BaiduFanyiDomainOptions.map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`baiduDomainOptions.${x}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
const baiduFanyiSourceLangOptions = useMemo(() => {
|
|
||||||
return BaiduFanyiSourceLangOptions.map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`baiduSourceLangOptions.${x}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<Form.Item label={t('appid')} name={'appid'}>
|
|
||||||
<Input></Input>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('secretKey')} name={'secret_key'}>
|
|
||||||
<Input></Input>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('transType')} name={'trans_type'}>
|
|
||||||
<Select options={options}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item noStyle dependencies={['model_type']}>
|
|
||||||
{({ getFieldValue }) =>
|
|
||||||
getFieldValue('trans_type') === 'fieldtranslate' && (
|
|
||||||
<Form.Item label={t('domain')} name={'domain'}>
|
|
||||||
<Select options={baiduFanyiOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('sourceLang')} name={'source_lang'}>
|
|
||||||
<Select options={baiduFanyiSourceLangOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('targetLang')} name={'target_lang'}>
|
|
||||||
<Select options={baiduFanyiSourceLangOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BaiduFanyiForm;
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import TopNItem from '@/components/top-n-item';
|
|
||||||
import { Form } from 'antd';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const BaiduForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<TopNItem initialValue={10}></TopNItem>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BaiduForm;
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Form, Input } from 'antd';
|
|
||||||
|
|
||||||
const BeginDynamicOptions = () => {
|
|
||||||
return (
|
|
||||||
<Form.List
|
|
||||||
name="options"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
validator: async (_, names) => {
|
|
||||||
if (!names || names.length < 1) {
|
|
||||||
return Promise.reject(new Error('At least 1 option'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{(fields, { add, remove }, { errors }) => (
|
|
||||||
<>
|
|
||||||
{fields.map((field, index) => (
|
|
||||||
<Form.Item
|
|
||||||
label={index === 0 ? 'Options' : ''}
|
|
||||||
required={false}
|
|
||||||
key={field.key}
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
{...field}
|
|
||||||
validateTrigger={['onChange', 'onBlur']}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
whitespace: true,
|
|
||||||
message: 'Please input option or delete this field.',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
noStyle
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
placeholder="option"
|
|
||||||
style={{ width: '90%', marginRight: 16 }}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
{fields.length > 1 ? (
|
|
||||||
<MinusCircleOutlined
|
|
||||||
className="dynamic-delete-button"
|
|
||||||
onClick={() => remove(field.name)}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</Form.Item>
|
|
||||||
))}
|
|
||||||
<Form.Item>
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
onClick={() => add()}
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
block
|
|
||||||
>
|
|
||||||
Add option
|
|
||||||
</Button>
|
|
||||||
<Form.ErrorList errors={errors} />
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Form.List>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BeginDynamicOptions;
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
|
||||||
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
|
||||||
import { BeginQuery, IOperatorForm } from '../../interface';
|
|
||||||
|
|
||||||
export const useEditQueryRecord = ({ form, onValuesChange }: IOperatorForm) => {
|
|
||||||
const { setRecord, currentRecord } = useSetSelectedRecord<BeginQuery>();
|
|
||||||
const { visible, hideModal, showModal } = useSetModalState();
|
|
||||||
const [index, setIndex] = useState(-1);
|
|
||||||
|
|
||||||
const otherThanCurrentQuery = useMemo(() => {
|
|
||||||
const query: BeginQuery[] = form?.getFieldValue('query') || [];
|
|
||||||
return query.filter((item, idx) => idx !== index);
|
|
||||||
}, [form, index]);
|
|
||||||
|
|
||||||
const handleEditRecord = useCallback(
|
|
||||||
(record: BeginQuery) => {
|
|
||||||
const query: BeginQuery[] = form?.getFieldValue('query') || [];
|
|
||||||
|
|
||||||
const nextQuery: BeginQuery[] =
|
|
||||||
index > -1 ? query.toSpliced(index, 1, record) : [...query, record];
|
|
||||||
|
|
||||||
onValuesChange?.(
|
|
||||||
{ query: nextQuery },
|
|
||||||
{ query: nextQuery, prologue: form?.getFieldValue('prologue') },
|
|
||||||
);
|
|
||||||
hideModal();
|
|
||||||
},
|
|
||||||
[form, hideModal, index, onValuesChange],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleShowModal = useCallback(
|
|
||||||
(idx?: number, record?: BeginQuery) => {
|
|
||||||
setIndex(idx ?? -1);
|
|
||||||
setRecord(record ?? ({} as BeginQuery));
|
|
||||||
showModal();
|
|
||||||
},
|
|
||||||
[setRecord, showModal],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
ok: handleEditRecord,
|
|
||||||
currentRecord,
|
|
||||||
setRecord,
|
|
||||||
visible,
|
|
||||||
hideModal,
|
|
||||||
showModal: handleShowModal,
|
|
||||||
otherThanCurrentQuery,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
.dynamicInputVariable {
|
|
||||||
background-color: #ebe9e950;
|
|
||||||
:global(.ant-collapse-content) {
|
|
||||||
background-color: #f6f6f657;
|
|
||||||
}
|
|
||||||
:global(.ant-collapse-content-box) {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
margin-bottom: 20px;
|
|
||||||
.title {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.addButton {
|
|
||||||
color: rgb(22, 119, 255);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.addButton {
|
|
||||||
color: rgb(22, 119, 255);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
import { PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Form, Input } from 'antd';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { BeginQuery, IOperatorForm } from '../../interface';
|
|
||||||
import { useEditQueryRecord } from './hooks';
|
|
||||||
import { ModalForm } from './paramater-modal';
|
|
||||||
import QueryTable from './query-table';
|
|
||||||
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
type FieldType = {
|
|
||||||
prologue?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const BeginForm = ({ onValuesChange, form }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const {
|
|
||||||
ok,
|
|
||||||
currentRecord,
|
|
||||||
visible,
|
|
||||||
hideModal,
|
|
||||||
showModal,
|
|
||||||
otherThanCurrentQuery,
|
|
||||||
} = useEditQueryRecord({
|
|
||||||
form,
|
|
||||||
onValuesChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleDeleteRecord = useCallback(
|
|
||||||
(idx: number) => {
|
|
||||||
const query = form?.getFieldValue('query') || [];
|
|
||||||
const nextQuery = query.filter(
|
|
||||||
(item: BeginQuery, index: number) => index !== idx,
|
|
||||||
);
|
|
||||||
onValuesChange?.(
|
|
||||||
{ query: nextQuery },
|
|
||||||
{ query: nextQuery, prologue: form?.getFieldValue('prologue') },
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[form, onValuesChange],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.Provider
|
|
||||||
onFormFinish={(name, { values }) => {
|
|
||||||
if (name === 'queryForm') {
|
|
||||||
ok(values as BeginQuery);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Form
|
|
||||||
name="basicForm"
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
layout="vertical"
|
|
||||||
>
|
|
||||||
<Form.Item<FieldType>
|
|
||||||
name={'prologue'}
|
|
||||||
label={t('chat.setAnOpener')}
|
|
||||||
tooltip={t('chat.setAnOpenerTip')}
|
|
||||||
initialValue={t('chat.setAnOpenerInitial')}
|
|
||||||
>
|
|
||||||
<Input.TextArea autoSize={{ minRows: 5 }} />
|
|
||||||
</Form.Item>
|
|
||||||
{/* Create a hidden field to make Form instance record this */}
|
|
||||||
<Form.Item name="query" noStyle />
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
shouldUpdate={(prevValues, curValues) =>
|
|
||||||
prevValues.query !== curValues.query
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{({ getFieldValue }) => {
|
|
||||||
const query: BeginQuery[] = getFieldValue('query') || [];
|
|
||||||
return (
|
|
||||||
<QueryTable
|
|
||||||
data={query}
|
|
||||||
showModal={showModal}
|
|
||||||
deleteRecord={handleDeleteRecord}
|
|
||||||
></QueryTable>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
htmlType="button"
|
|
||||||
style={{ margin: '0 8px' }}
|
|
||||||
onClick={() => showModal()}
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
block
|
|
||||||
className={styles.addButton}
|
|
||||||
>
|
|
||||||
{t('flow.addItem')}
|
|
||||||
</Button>
|
|
||||||
{visible && (
|
|
||||||
<ModalForm
|
|
||||||
visible={visible}
|
|
||||||
hideModal={hideModal}
|
|
||||||
initialValue={currentRecord}
|
|
||||||
onOk={ok}
|
|
||||||
otherThanCurrentQuery={otherThanCurrentQuery}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
</Form.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BeginForm;
|
|
||||||
@ -1,124 +0,0 @@
|
|||||||
import { useResetFormOnCloseModal } from '@/hooks/logic-hooks';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { Form, Input, Modal, Select, Switch } from 'antd';
|
|
||||||
import { DefaultOptionType } from 'antd/es/select';
|
|
||||||
import { useEffect, useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { BeginQueryType, BeginQueryTypeIconMap } from '../../constant';
|
|
||||||
import { BeginQuery } from '../../interface';
|
|
||||||
import BeginDynamicOptions from './begin-dynamic-options';
|
|
||||||
|
|
||||||
export const ModalForm = ({
|
|
||||||
visible,
|
|
||||||
initialValue,
|
|
||||||
hideModal,
|
|
||||||
otherThanCurrentQuery,
|
|
||||||
}: IModalProps<BeginQuery> & {
|
|
||||||
initialValue: BeginQuery;
|
|
||||||
otherThanCurrentQuery: BeginQuery[];
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const options = useMemo(() => {
|
|
||||||
return Object.values(BeginQueryType).reduce<DefaultOptionType[]>(
|
|
||||||
(pre, cur) => {
|
|
||||||
const Icon = BeginQueryTypeIconMap[cur];
|
|
||||||
|
|
||||||
return [
|
|
||||||
...pre,
|
|
||||||
{
|
|
||||||
label: (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Icon
|
|
||||||
className={`size-${cur === BeginQueryType.Options ? 4 : 5}`}
|
|
||||||
></Icon>
|
|
||||||
{cur}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
value: cur,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useResetFormOnCloseModal({
|
|
||||||
form,
|
|
||||||
visible: visible,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.setFieldsValue(initialValue);
|
|
||||||
}, [form, initialValue]);
|
|
||||||
|
|
||||||
const onOk = () => {
|
|
||||||
form.submit();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={t('flow.variableSettings')}
|
|
||||||
open={visible}
|
|
||||||
onOk={onOk}
|
|
||||||
onCancel={hideModal}
|
|
||||||
centered
|
|
||||||
>
|
|
||||||
<Form form={form} layout="vertical" name="queryForm" autoComplete="false">
|
|
||||||
<Form.Item
|
|
||||||
name="type"
|
|
||||||
label="Type"
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
initialValue={BeginQueryType.Line}
|
|
||||||
>
|
|
||||||
<Select options={options} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="key"
|
|
||||||
label="Key"
|
|
||||||
rules={[
|
|
||||||
{ required: true },
|
|
||||||
() => ({
|
|
||||||
validator(_, value) {
|
|
||||||
if (
|
|
||||||
!value ||
|
|
||||||
!otherThanCurrentQuery.some((x) => x.key === value)
|
|
||||||
) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
return Promise.reject(new Error('The key cannot be repeated!'));
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="name" label="Name" rules={[{ required: true }]}>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="optional"
|
|
||||||
label={'Optional'}
|
|
||||||
valuePropName="checked"
|
|
||||||
initialValue={false}
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
shouldUpdate={(prevValues, curValues) =>
|
|
||||||
prevValues.type !== curValues.type
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{({ getFieldValue }) => {
|
|
||||||
const type: BeginQueryType = getFieldValue('type');
|
|
||||||
return (
|
|
||||||
type === BeginQueryType.Options && (
|
|
||||||
<BeginDynamicOptions></BeginDynamicOptions>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
|
||||||
import type { TableProps } from 'antd';
|
|
||||||
import { Collapse, Space, Table, Tooltip } from 'antd';
|
|
||||||
import { BeginQuery } from '../../interface';
|
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
data: BeginQuery[];
|
|
||||||
deleteRecord(index: number): void;
|
|
||||||
showModal(index: number, record: BeginQuery): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QueryTable = ({ data, deleteRecord, showModal }: IProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const columns: TableProps<BeginQuery>['columns'] = [
|
|
||||||
{
|
|
||||||
title: 'Key',
|
|
||||||
dataIndex: 'key',
|
|
||||||
key: 'key',
|
|
||||||
ellipsis: {
|
|
||||||
showTitle: false,
|
|
||||||
},
|
|
||||||
render: (key) => (
|
|
||||||
<Tooltip placement="topLeft" title={key}>
|
|
||||||
{key}
|
|
||||||
</Tooltip>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('flow.name'),
|
|
||||||
dataIndex: 'name',
|
|
||||||
key: 'name',
|
|
||||||
ellipsis: {
|
|
||||||
showTitle: false,
|
|
||||||
},
|
|
||||||
render: (name) => (
|
|
||||||
<Tooltip placement="topLeft" title={name}>
|
|
||||||
{name}
|
|
||||||
</Tooltip>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('flow.type'),
|
|
||||||
dataIndex: 'type',
|
|
||||||
key: 'type',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('flow.optional'),
|
|
||||||
dataIndex: 'optional',
|
|
||||||
key: 'optional',
|
|
||||||
render: (optional) => (optional ? 'Yes' : 'No'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('common.action'),
|
|
||||||
key: 'action',
|
|
||||||
render: (_, record, idx) => (
|
|
||||||
<Space>
|
|
||||||
<EditOutlined onClick={() => showModal(idx, record)} />
|
|
||||||
<DeleteOutlined
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() => deleteRecord(idx)}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Collapse
|
|
||||||
defaultActiveKey={['1']}
|
|
||||||
className={styles.dynamicInputVariable}
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
label: <span className={styles.title}>{t('flow.input')}</span>,
|
|
||||||
children: (
|
|
||||||
<Table<BeginQuery>
|
|
||||||
columns={columns}
|
|
||||||
dataSource={data}
|
|
||||||
pagination={false}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default QueryTable;
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
import TopNItem from '@/components/top-n-item';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Input, Select } from 'antd';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { BingCountryOptions, BingLanguageOptions } from '../../constant';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const BingForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
const options = useMemo(() => {
|
|
||||||
return ['Webpages', 'News'].map((x) => ({ label: x, value: x }));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<TopNItem initialValue={10}></TopNItem>
|
|
||||||
<Form.Item label={t('channel')} name={'channel'}>
|
|
||||||
<Select options={options}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('apiKey')} name={'api_key'}>
|
|
||||||
<Input></Input>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('country')} name={'country'}>
|
|
||||||
<Select options={BingCountryOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('language')} name={'language'}>
|
|
||||||
<Select options={BingLanguageOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BingForm;
|
|
||||||
@ -1,225 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { CloseOutlined, PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { useUpdateNodeInternals } from '@xyflow/react';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Collapse,
|
|
||||||
Flex,
|
|
||||||
Form,
|
|
||||||
FormListFieldData,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
} from 'antd';
|
|
||||||
import { FormInstance } from 'antd/lib';
|
|
||||||
import { humanId } from 'human-id';
|
|
||||||
import trim from 'lodash/trim';
|
|
||||||
import {
|
|
||||||
ChangeEventHandler,
|
|
||||||
FocusEventHandler,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { Operator } from '../../constant';
|
|
||||||
import { useBuildFormSelectOptions } from '../../form-hooks';
|
|
||||||
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
nodeId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface INameInputProps {
|
|
||||||
value?: string;
|
|
||||||
onChange?: (value: string) => void;
|
|
||||||
otherNames?: string[];
|
|
||||||
validate(errors: string[]): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getOtherFieldValues = (
|
|
||||||
form: FormInstance,
|
|
||||||
formListName: string = 'items',
|
|
||||||
field: FormListFieldData,
|
|
||||||
latestField: string,
|
|
||||||
) =>
|
|
||||||
(form.getFieldValue([formListName]) ?? [])
|
|
||||||
.map((x: any) => x[latestField])
|
|
||||||
.filter(
|
|
||||||
(x: string) =>
|
|
||||||
x !== form.getFieldValue([formListName, field.name, latestField]),
|
|
||||||
);
|
|
||||||
|
|
||||||
const NameInput = ({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
otherNames,
|
|
||||||
validate,
|
|
||||||
}: INameInputProps) => {
|
|
||||||
const [name, setName] = useState<string | undefined>();
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
const handleNameChange: ChangeEventHandler<HTMLInputElement> = useCallback(
|
|
||||||
(e) => {
|
|
||||||
const val = e.target.value;
|
|
||||||
// trigger validation
|
|
||||||
if (otherNames?.some((x) => x === val)) {
|
|
||||||
validate([t('nameRepeatedMsg')]);
|
|
||||||
} else if (trim(val) === '') {
|
|
||||||
validate([t('nameRequiredMsg')]);
|
|
||||||
} else {
|
|
||||||
validate([]);
|
|
||||||
}
|
|
||||||
setName(val);
|
|
||||||
},
|
|
||||||
[otherNames, validate, t],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleNameBlur: FocusEventHandler<HTMLInputElement> = useCallback(
|
|
||||||
(e) => {
|
|
||||||
const val = e.target.value;
|
|
||||||
if (otherNames?.every((x) => x !== val) && trim(val) !== '') {
|
|
||||||
onChange?.(val);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[onChange, otherNames],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setName(value);
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
value={name}
|
|
||||||
onChange={handleNameChange}
|
|
||||||
onBlur={handleNameBlur}
|
|
||||||
></Input>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const FormSet = ({ nodeId, field }: IProps & { field: FormListFieldData }) => {
|
|
||||||
const form = Form.useFormInstance();
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const buildCategorizeToOptions = useBuildFormSelectOptions(
|
|
||||||
Operator.Categorize,
|
|
||||||
nodeId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<Form.Item
|
|
||||||
label={t('categoryName')}
|
|
||||||
name={[field.name, 'name']}
|
|
||||||
validateTrigger={['onChange', 'onBlur']}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
whitespace: true,
|
|
||||||
message: t('nameMessage'),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<NameInput
|
|
||||||
otherNames={getOtherFieldValues(form, 'items', field, 'name')}
|
|
||||||
validate={(errors: string[]) =>
|
|
||||||
form.setFields([
|
|
||||||
{
|
|
||||||
name: ['items', field.name, 'name'],
|
|
||||||
errors,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
}
|
|
||||||
></NameInput>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('description')} name={[field.name, 'description']}>
|
|
||||||
<Input.TextArea rows={3} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('examples')} name={[field.name, 'examples']}>
|
|
||||||
<Input.TextArea rows={3} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('nextStep')} name={[field.name, 'to']}>
|
|
||||||
<Select
|
|
||||||
allowClear
|
|
||||||
options={buildCategorizeToOptions(
|
|
||||||
getOtherFieldValues(form, 'items', field, 'to'),
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item hidden name={[field.name, 'index']}>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DynamicCategorize = ({ nodeId }: IProps) => {
|
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
|
||||||
const form = Form.useFormInstance();
|
|
||||||
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Form.List name="items">
|
|
||||||
{(fields, { add, remove }) => {
|
|
||||||
const handleAdd = () => {
|
|
||||||
const idx = form.getFieldValue([
|
|
||||||
'items',
|
|
||||||
fields.at(-1)?.name,
|
|
||||||
'index',
|
|
||||||
]);
|
|
||||||
add({
|
|
||||||
name: humanId(),
|
|
||||||
index: fields.length === 0 ? 0 : idx + 1,
|
|
||||||
});
|
|
||||||
if (nodeId) updateNodeInternals(nodeId);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex gap={18} vertical>
|
|
||||||
{fields.map((field) => (
|
|
||||||
<Collapse
|
|
||||||
size="small"
|
|
||||||
key={field.key}
|
|
||||||
className={styles.caseCard}
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
key: field.key,
|
|
||||||
label: (
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span>
|
|
||||||
{form.getFieldValue(['items', field.name, 'name'])}
|
|
||||||
</span>
|
|
||||||
<CloseOutlined
|
|
||||||
onClick={() => {
|
|
||||||
remove(field.name);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
children: (
|
|
||||||
<FormSet nodeId={nodeId} field={field}></FormSet>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
></Collapse>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
onClick={handleAdd}
|
|
||||||
block
|
|
||||||
className={styles.addButton}
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
>
|
|
||||||
{t('addCategory')}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.List>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DynamicCategorize;
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
import {
|
|
||||||
ICategorizeItem,
|
|
||||||
ICategorizeItemResult,
|
|
||||||
} from '@/interfaces/database/flow';
|
|
||||||
import omit from 'lodash/omit';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert the list in the following form into an object
|
|
||||||
* {
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"name": "Categorize 1",
|
|
||||||
"description": "111",
|
|
||||||
"examples": "ddd",
|
|
||||||
"to": "Retrieval:LazyEelsStick"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
const buildCategorizeObjectFromList = (list: Array<ICategorizeItem>) => {
|
|
||||||
return list.reduce<ICategorizeItemResult>((pre, cur) => {
|
|
||||||
if (cur?.name) {
|
|
||||||
pre[cur.name] = omit(cur, 'name');
|
|
||||||
}
|
|
||||||
return pre;
|
|
||||||
}, {});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useHandleFormValuesChange = ({
|
|
||||||
onValuesChange,
|
|
||||||
}: IOperatorForm) => {
|
|
||||||
const handleValuesChange = useCallback(
|
|
||||||
(changedValues: any, values: any) => {
|
|
||||||
onValuesChange?.(changedValues, {
|
|
||||||
...omit(values, 'items'),
|
|
||||||
category_description: buildCategorizeObjectFromList(values.items),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[onValuesChange],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { handleValuesChange };
|
|
||||||
};
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
@lightBackgroundColor: rgba(150, 150, 150, 0.07);
|
|
||||||
@darkBackgroundColor: rgba(150, 150, 150, 0.12);
|
|
||||||
|
|
||||||
.caseCard {
|
|
||||||
:global(.ant-collapse-content) {
|
|
||||||
background-color: @darkBackgroundColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.addButton {
|
|
||||||
color: rgb(22, 119, 255);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
import LLMSelect from '@/components/llm-select';
|
|
||||||
import MessageHistoryWindowSizeItem from '@/components/message-history-window-size-item';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form } from 'antd';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
import DynamicCategorize from './dynamic-categorize';
|
|
||||||
import { useHandleFormValuesChange } from './hooks';
|
|
||||||
|
|
||||||
const CategorizeForm = ({ form, onValuesChange, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const { handleValuesChange } = useHandleFormValuesChange({
|
|
||||||
form,
|
|
||||||
nodeId: node?.id,
|
|
||||||
onValuesChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={handleValuesChange}
|
|
||||||
initialValues={{ items: [{}] }}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<Form.Item
|
|
||||||
name={'llm_id'}
|
|
||||||
label={t('model', { keyPrefix: 'chat' })}
|
|
||||||
tooltip={t('modelTip', { keyPrefix: 'chat' })}
|
|
||||||
>
|
|
||||||
<LLMSelect></LLMSelect>
|
|
||||||
</Form.Item>
|
|
||||||
<MessageHistoryWindowSizeItem
|
|
||||||
initialValue={1}
|
|
||||||
></MessageHistoryWindowSizeItem>
|
|
||||||
<DynamicCategorize nodeId={node?.id}></DynamicCategorize>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CategorizeForm;
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Form, Input, Select } from 'antd';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query';
|
|
||||||
import { FormCollapse } from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
type DynamicInputVariableProps = {
|
|
||||||
name?: string;
|
|
||||||
node?: RAGFlowNodeType;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DynamicInputVariable = ({
|
|
||||||
name = 'arguments',
|
|
||||||
node,
|
|
||||||
}: DynamicInputVariableProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const valueOptions = useBuildComponentIdSelectOptions(
|
|
||||||
node?.id,
|
|
||||||
node?.parentId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormCollapse title={t('flow.inputVariables')}>
|
|
||||||
<Form.List name={name}>
|
|
||||||
{(fields, { add, remove }) => (
|
|
||||||
<>
|
|
||||||
{fields.map(({ key, name, ...restField }) => (
|
|
||||||
<div key={key} className="flex items-center gap-2 pb-4">
|
|
||||||
<Form.Item
|
|
||||||
{...restField}
|
|
||||||
name={[name, 'name']}
|
|
||||||
className="m-0 flex-1"
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
{...restField}
|
|
||||||
name={[name, 'component_id']}
|
|
||||||
className="m-0 flex-1"
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
placeholder={t('common.pleaseSelect')}
|
|
||||||
options={valueOptions}
|
|
||||||
></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<MinusCircleOutlined onClick={() => remove(name)} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<Form.Item>
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
onClick={() => add()}
|
|
||||||
block
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
>
|
|
||||||
{t('flow.addVariable')}
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Form.List>
|
|
||||||
</FormCollapse>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Form, Input, Select } from 'antd';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { FormCollapse } from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
type DynamicOutputVariableProps = {
|
|
||||||
name?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const options = [
|
|
||||||
'String',
|
|
||||||
'Number',
|
|
||||||
'Boolean',
|
|
||||||
'Array[String]',
|
|
||||||
'Array[Number]',
|
|
||||||
'Object',
|
|
||||||
].map((x) => ({ label: x, value: x }));
|
|
||||||
|
|
||||||
export const DynamicOutputVariable = ({
|
|
||||||
name = 'output',
|
|
||||||
}: DynamicOutputVariableProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormCollapse title={t('flow.output')}>
|
|
||||||
<Form.List name={name}>
|
|
||||||
{(fields, { add, remove }) => (
|
|
||||||
<>
|
|
||||||
{fields.map(({ key, name, ...restField }) => (
|
|
||||||
<div key={key} className="flex items-center gap-2 pb-4">
|
|
||||||
<Form.Item
|
|
||||||
{...restField}
|
|
||||||
name={[name, 'first']}
|
|
||||||
className="m-0 flex-1"
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
{...restField}
|
|
||||||
name={[name, 'last']}
|
|
||||||
className="m-0 flex-1"
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
placeholder={t('common.pleaseSelect')}
|
|
||||||
options={options}
|
|
||||||
></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<MinusCircleOutlined onClick={() => remove(name)} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<Form.Item>
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
onClick={() => add()}
|
|
||||||
block
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
>
|
|
||||||
{t('flow.addVariable')}
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Form.List>
|
|
||||||
</FormCollapse>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
.languageItem {
|
|
||||||
margin: 0;
|
|
||||||
:global(.ant-select-selector) {
|
|
||||||
background: transparent !important;
|
|
||||||
border: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
:global(.ant-select-selector:hover) {
|
|
||||||
border: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
:global(.ant-select-focused .ant-select-selector) {
|
|
||||||
border: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
import Editor, { loader } from '@monaco-editor/react';
|
|
||||||
import { Form, Select } from 'antd';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import { DynamicInputVariable } from './dynamic-input-variable';
|
|
||||||
|
|
||||||
import { CodeTemplateStrMap, ProgrammingLanguage } from '@/constants/agent';
|
|
||||||
import { ICodeForm } from '@/interfaces/database/flow';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
loader.config({ paths: { vs: '/vs' } });
|
|
||||||
|
|
||||||
const options = [
|
|
||||||
ProgrammingLanguage.Python,
|
|
||||||
ProgrammingLanguage.Javascript,
|
|
||||||
].map((x) => ({ value: x, label: x }));
|
|
||||||
|
|
||||||
const CodeForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const formData = node?.data.form as ICodeForm;
|
|
||||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
|
||||||
|
|
||||||
const handleChange = useCallback(
|
|
||||||
(value: ProgrammingLanguage) => {
|
|
||||||
if (node?.id) {
|
|
||||||
updateNodeForm(
|
|
||||||
node?.id,
|
|
||||||
CodeTemplateStrMap[value as ProgrammingLanguage],
|
|
||||||
['script'],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[node?.id, updateNodeForm],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<Form.Item
|
|
||||||
name={'script'}
|
|
||||||
label={
|
|
||||||
<Form.Item name={'lang'} className={styles.languageItem}>
|
|
||||||
<Select
|
|
||||||
defaultValue={ProgrammingLanguage.Python}
|
|
||||||
popupMatchSelectWidth={false}
|
|
||||||
options={options}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
}
|
|
||||||
className="bg-gray-100 rounded dark:bg-gray-800"
|
|
||||||
>
|
|
||||||
<Editor
|
|
||||||
height={600}
|
|
||||||
theme="vs-dark"
|
|
||||||
language={formData.lang}
|
|
||||||
options={{
|
|
||||||
minimap: { enabled: false },
|
|
||||||
automaticLayout: true,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CodeForm;
|
|
||||||
@ -1,133 +0,0 @@
|
|||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Collapse, Flex, Form, Input, Select } from 'antd';
|
|
||||||
import { PropsWithChildren, useCallback } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query';
|
|
||||||
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
name?: string;
|
|
||||||
node?: RAGFlowNodeType;
|
|
||||||
title?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum VariableType {
|
|
||||||
Reference = 'reference',
|
|
||||||
Input = 'input',
|
|
||||||
}
|
|
||||||
|
|
||||||
const getVariableName = (type: string) =>
|
|
||||||
type === VariableType.Reference ? 'component_id' : 'value';
|
|
||||||
|
|
||||||
const DynamicVariableForm = ({ name: formName, node }: IProps) => {
|
|
||||||
const nextFormName = formName || 'query';
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const valueOptions = useBuildComponentIdSelectOptions(
|
|
||||||
node?.id,
|
|
||||||
node?.parentId,
|
|
||||||
);
|
|
||||||
const form = Form.useFormInstance();
|
|
||||||
|
|
||||||
const options = [
|
|
||||||
{ value: VariableType.Reference, label: t('flow.reference') },
|
|
||||||
{ value: VariableType.Input, label: t('flow.text') },
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleTypeChange = useCallback(
|
|
||||||
(name: number) => () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
form.setFieldValue([nextFormName, name, 'component_id'], undefined);
|
|
||||||
form.setFieldValue([nextFormName, name, 'value'], undefined);
|
|
||||||
}, 0);
|
|
||||||
},
|
|
||||||
[form, nextFormName],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.List name={nextFormName}>
|
|
||||||
{(fields, { add, remove }) => (
|
|
||||||
<>
|
|
||||||
{fields.map(({ key, name, ...restField }) => (
|
|
||||||
<Flex key={key} gap={10} align={'baseline'}>
|
|
||||||
<Form.Item
|
|
||||||
{...restField}
|
|
||||||
name={[name, 'type']}
|
|
||||||
className={styles.variableType}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
options={options}
|
|
||||||
onChange={handleTypeChange(name)}
|
|
||||||
></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item noStyle dependencies={[name, 'type']}>
|
|
||||||
{({ getFieldValue }) => {
|
|
||||||
const type = getFieldValue([nextFormName, name, 'type']);
|
|
||||||
return (
|
|
||||||
<Form.Item
|
|
||||||
{...restField}
|
|
||||||
name={[name, getVariableName(type)]}
|
|
||||||
className={styles.variableValue}
|
|
||||||
>
|
|
||||||
{type === VariableType.Reference ? (
|
|
||||||
<Select
|
|
||||||
placeholder={t('common.pleaseSelect')}
|
|
||||||
options={valueOptions}
|
|
||||||
></Select>
|
|
||||||
) : (
|
|
||||||
<Input placeholder={t('common.pleaseInput')} />
|
|
||||||
)}
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
|
||||||
<MinusCircleOutlined onClick={() => remove(name)} />
|
|
||||||
</Flex>
|
|
||||||
))}
|
|
||||||
<Form.Item>
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
onClick={() => add({ type: VariableType.Reference })}
|
|
||||||
block
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
className={styles.addButton}
|
|
||||||
>
|
|
||||||
{t('flow.addVariable')}
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Form.List>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function FormCollapse({
|
|
||||||
children,
|
|
||||||
title,
|
|
||||||
}: PropsWithChildren<{ title: string }>) {
|
|
||||||
return (
|
|
||||||
<Collapse
|
|
||||||
className={styles.dynamicInputVariable}
|
|
||||||
defaultActiveKey={['1']}
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
label: <span className={styles.title}>{title}</span>,
|
|
||||||
children,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const DynamicInputVariable = ({ name, node, title }: IProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<FormCollapse title={title || t('flow.input')}>
|
|
||||||
<DynamicVariableForm name={name} node={node}></DynamicVariableForm>
|
|
||||||
</FormCollapse>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DynamicInputVariable;
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
.dynamicInputVariable {
|
|
||||||
background-color: #ebe9e950;
|
|
||||||
:global(.ant-collapse-content) {
|
|
||||||
background-color: #f6f6f657;
|
|
||||||
}
|
|
||||||
margin-bottom: 20px;
|
|
||||||
.title {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
.variableType {
|
|
||||||
width: 30%;
|
|
||||||
}
|
|
||||||
.variableValue {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.addButton {
|
|
||||||
color: rgb(22, 119, 255);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import { Form } from 'antd';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
|
|
||||||
const ConcentratorForm = ({ onValuesChange, form }: IOperatorForm) => {
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
labelCol={{ span: 8 }}
|
|
||||||
wrapperCol={{ span: 16 }}
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
></Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ConcentratorForm;
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Input, Select } from 'antd';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { CrawlerResultOptions } from '../../constant';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
const CrawlerForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const crawlerResultOptions = useMemo(() => {
|
|
||||||
return CrawlerResultOptions.map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`crawlerResultOptions.${x}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<Form.Item label={t('proxy')} name={'proxy'}>
|
|
||||||
<Input placeholder="like: http://127.0.0.1:8888"></Input>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t('extractType')}
|
|
||||||
name={'extract_type'}
|
|
||||||
initialValue="markdown"
|
|
||||||
>
|
|
||||||
<Select options={crawlerResultOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CrawlerForm;
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import TopNItem from '@/components/top-n-item';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Select } from 'antd';
|
|
||||||
import { DeepLSourceLangOptions, DeepLTargetLangOptions } from '../../constant';
|
|
||||||
import { useBuildSortOptions } from '../../form-hooks';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const DeepLForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const options = useBuildSortOptions();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<TopNItem initialValue={5}></TopNItem>
|
|
||||||
<Form.Item label={t('authKey')} name={'auth_key'}>
|
|
||||||
<Select options={options}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('sourceLang')} name={'source_lang'}>
|
|
||||||
<Select options={DeepLSourceLangOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('targetLang')} name={'target_lang'}>
|
|
||||||
<Select options={DeepLTargetLangOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DeepLForm;
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
import TopNItem from '@/components/top-n-item';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Select } from 'antd';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { Channel } from '../../constant';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const DuckDuckGoForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
const options = useMemo(() => {
|
|
||||||
return Object.values(Channel).map((x) => ({ value: x, label: t(x) }));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<TopNItem initialValue={10}></TopNItem>
|
|
||||||
<Form.Item
|
|
||||||
label={t('channel')}
|
|
||||||
name={'channel'}
|
|
||||||
tooltip={t('channelTip')}
|
|
||||||
initialValue={'text'}
|
|
||||||
>
|
|
||||||
<Select options={options}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DuckDuckGoForm;
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Input } from 'antd';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const EmailForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
|
|
||||||
{/* SMTP服务器配置 */}
|
|
||||||
<Form.Item label={t('smtpServer')} name={'smtp_server'}>
|
|
||||||
<Input placeholder="smtp.example.com" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('smtpPort')} name={'smtp_port'}>
|
|
||||||
<Input type="number" placeholder="587" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('senderEmail')} name={'email'}>
|
|
||||||
<Input placeholder="sender@example.com" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('authCode')} name={'password'}>
|
|
||||||
<Input.Password placeholder="your_password" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('senderName')} name={'sender_name'}>
|
|
||||||
<Input placeholder="Sender Name" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
{/* 动态参数说明 */}
|
|
||||||
<div style={{ marginBottom: 24 }}>
|
|
||||||
<h4>{t('dynamicParameters')}</h4>
|
|
||||||
<div>{t('jsonFormatTip')}</div>
|
|
||||||
<pre style={{ background: '#f5f5f5', padding: 12, borderRadius: 4 }}>
|
|
||||||
{`{
|
|
||||||
"to_email": "recipient@example.com",
|
|
||||||
"cc_email": "cc@example.com",
|
|
||||||
"subject": "Email Subject",
|
|
||||||
"content": "Email Content"
|
|
||||||
}`}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EmailForm;
|
|
||||||
@ -1,88 +0,0 @@
|
|||||||
import LLMSelect from '@/components/llm-select';
|
|
||||||
import TopNItem from '@/components/top-n-item';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { useTestDbConnect } from '@/hooks/flow-hooks';
|
|
||||||
import { Button, Flex, Form, Input, InputNumber, Select } from 'antd';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { ExeSQLOptions } from '../../constant';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const ExeSQLForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const { testDbConnect, loading } = useTestDbConnect();
|
|
||||||
|
|
||||||
const handleTest = useCallback(async () => {
|
|
||||||
const ret = await form?.validateFields();
|
|
||||||
testDbConnect(ret);
|
|
||||||
}, [form, testDbConnect]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<Form.Item
|
|
||||||
name={'llm_id'}
|
|
||||||
label={t('model', { keyPrefix: 'chat' })}
|
|
||||||
tooltip={t('modelTip', { keyPrefix: 'chat' })}
|
|
||||||
>
|
|
||||||
<LLMSelect></LLMSelect>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t('dbType')}
|
|
||||||
name={'db_type'}
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
>
|
|
||||||
<Select options={ExeSQLOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t('database')}
|
|
||||||
name={'database'}
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
>
|
|
||||||
<Input></Input>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t('username')}
|
|
||||||
name={'username'}
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
>
|
|
||||||
<Input></Input>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('host')} name={'host'} rules={[{ required: true }]}>
|
|
||||||
<Input></Input>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('port')} name={'port'} rules={[{ required: true }]}>
|
|
||||||
<InputNumber></InputNumber>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t('password')}
|
|
||||||
name={'password'}
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
>
|
|
||||||
<Input.Password></Input.Password>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t('loop')}
|
|
||||||
name={'loop'}
|
|
||||||
tooltip={t('loopTip')}
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
>
|
|
||||||
<InputNumber></InputNumber>
|
|
||||||
</Form.Item>
|
|
||||||
<TopNItem initialValue={30} max={1000}></TopNItem>
|
|
||||||
<Flex justify={'end'}>
|
|
||||||
<Button type={'primary'} loading={loading} onClick={handleTest}>
|
|
||||||
Test
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ExeSQLForm;
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
import { EditableCell, EditableRow } from '@/components/editable-cell';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { DeleteOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Flex, Select, Table, TableProps } from 'antd';
|
|
||||||
import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query';
|
|
||||||
import { IGenerateParameter } from '../../interface';
|
|
||||||
import { useHandleOperateParameters } from './hooks';
|
|
||||||
|
|
||||||
import styles from './index.less';
|
|
||||||
interface IProps {
|
|
||||||
node?: RAGFlowNodeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
const components = {
|
|
||||||
body: {
|
|
||||||
row: EditableRow,
|
|
||||||
cell: EditableCell,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const DynamicParameters = ({ node }: IProps) => {
|
|
||||||
const nodeId = node?.id;
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
const options = useBuildComponentIdSelectOptions(nodeId, node?.parentId);
|
|
||||||
const {
|
|
||||||
dataSource,
|
|
||||||
handleAdd,
|
|
||||||
handleRemove,
|
|
||||||
handleSave,
|
|
||||||
handleComponentIdChange,
|
|
||||||
} = useHandleOperateParameters(nodeId!);
|
|
||||||
|
|
||||||
const columns: TableProps<IGenerateParameter>['columns'] = [
|
|
||||||
{
|
|
||||||
title: t('key'),
|
|
||||||
dataIndex: 'key',
|
|
||||||
key: 'key',
|
|
||||||
width: '40%',
|
|
||||||
onCell: (record: IGenerateParameter) => ({
|
|
||||||
record,
|
|
||||||
editable: true,
|
|
||||||
dataIndex: 'key',
|
|
||||||
title: 'key',
|
|
||||||
handleSave,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('value'),
|
|
||||||
dataIndex: 'component_id',
|
|
||||||
key: 'component_id',
|
|
||||||
align: 'center',
|
|
||||||
width: '40%',
|
|
||||||
render(text, record) {
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
allowClear
|
|
||||||
options={options}
|
|
||||||
value={text}
|
|
||||||
onChange={handleComponentIdChange(record)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('operation'),
|
|
||||||
dataIndex: 'operation',
|
|
||||||
width: 20,
|
|
||||||
key: 'operation',
|
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
|
||||||
render(_, record) {
|
|
||||||
return <DeleteOutlined onClick={handleRemove(record.id)} />;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<Flex justify="end">
|
|
||||||
<Button size="small" onClick={handleAdd}>
|
|
||||||
{t('add')}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
<Table
|
|
||||||
dataSource={dataSource}
|
|
||||||
columns={columns}
|
|
||||||
rowKey={'id'}
|
|
||||||
className={styles.variableTable}
|
|
||||||
components={components}
|
|
||||||
rowClassName={() => styles.editableRow}
|
|
||||||
scroll={{ x: true }}
|
|
||||||
bordered
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DynamicParameters;
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import get from 'lodash/get';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import { IGenerateParameter } from '../../interface';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
|
|
||||||
export const useHandleOperateParameters = (nodeId: string) => {
|
|
||||||
const { getNode, updateNodeForm } = useGraphStore((state) => state);
|
|
||||||
const node = getNode(nodeId);
|
|
||||||
const dataSource: IGenerateParameter[] = useMemo(
|
|
||||||
() => get(node, 'data.form.parameters', []) as IGenerateParameter[],
|
|
||||||
[node],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleComponentIdChange = useCallback(
|
|
||||||
(row: IGenerateParameter) => (value: string) => {
|
|
||||||
const newData = [...dataSource];
|
|
||||||
const index = newData.findIndex((item) => row.id === item.id);
|
|
||||||
const item = newData[index];
|
|
||||||
newData.splice(index, 1, {
|
|
||||||
...item,
|
|
||||||
component_id: value,
|
|
||||||
});
|
|
||||||
|
|
||||||
updateNodeForm(nodeId, { parameters: newData });
|
|
||||||
},
|
|
||||||
[updateNodeForm, nodeId, dataSource],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRemove = useCallback(
|
|
||||||
(id?: string) => () => {
|
|
||||||
const newData = dataSource.filter((item) => item.id !== id);
|
|
||||||
updateNodeForm(nodeId, { parameters: newData });
|
|
||||||
},
|
|
||||||
[updateNodeForm, nodeId, dataSource],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleAdd = useCallback(() => {
|
|
||||||
updateNodeForm(nodeId, {
|
|
||||||
parameters: [
|
|
||||||
...dataSource,
|
|
||||||
{
|
|
||||||
id: uuid(),
|
|
||||||
key: '',
|
|
||||||
component_id: undefined,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}, [dataSource, nodeId, updateNodeForm]);
|
|
||||||
|
|
||||||
const handleSave = (row: IGenerateParameter) => {
|
|
||||||
const newData = [...dataSource];
|
|
||||||
const index = newData.findIndex((item) => row.id === item.id);
|
|
||||||
const item = newData[index];
|
|
||||||
newData.splice(index, 1, {
|
|
||||||
...item,
|
|
||||||
...row,
|
|
||||||
});
|
|
||||||
|
|
||||||
updateNodeForm(nodeId, { parameters: newData });
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleAdd,
|
|
||||||
handleRemove,
|
|
||||||
handleComponentIdChange,
|
|
||||||
handleSave,
|
|
||||||
dataSource,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
.variableTable {
|
|
||||||
margin-top: 14px;
|
|
||||||
}
|
|
||||||
.editableRow {
|
|
||||||
:global(.editable-cell) {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.editable-cell-value-wrap) {
|
|
||||||
padding: 5px 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
height: 30px !important;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
:global(.editable-cell-value-wrap) {
|
|
||||||
padding: 4px 11px;
|
|
||||||
border: 1px solid #d9d9d9;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
import LLMSelect from '@/components/llm-select';
|
|
||||||
import MessageHistoryWindowSizeItem from '@/components/message-history-window-size-item';
|
|
||||||
import { PromptEditor } from '@/components/prompt-editor';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Switch } from 'antd';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import LLMToolsSelect from '@/components/llm-tools-select';
|
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
const [isCurrentLlmSupportTools, setCurrentLlmSupportTools] = useState(false);
|
|
||||||
|
|
||||||
const onLlmSelectChanged = (_: string, option: any) => {
|
|
||||||
setCurrentLlmSupportTools(option.is_tools);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
name={'llm_id'}
|
|
||||||
label={t('model', { keyPrefix: 'chat' })}
|
|
||||||
tooltip={t('modelTip', { keyPrefix: 'chat' })}
|
|
||||||
>
|
|
||||||
<LLMSelect onInitialValue={onLlmSelectChanged} onChange={onLlmSelectChanged}></LLMSelect>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name={['prompt']}
|
|
||||||
label={t('systemPrompt')}
|
|
||||||
initialValue={t('promptText')}
|
|
||||||
tooltip={t('promptTip')}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: t('promptMessage'),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{/* <Input.TextArea rows={8}></Input.TextArea> */}
|
|
||||||
<PromptEditor></PromptEditor>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name={'llm_enabled_tools'}
|
|
||||||
label={t('modelEnabledTools', { keyPrefix: 'chat' })}
|
|
||||||
tooltip={t('modelEnabledToolsTip', { keyPrefix: 'chat' })}
|
|
||||||
>
|
|
||||||
<LLMToolsSelect disabled={!isCurrentLlmSupportTools}></LLMToolsSelect>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name={['cite']}
|
|
||||||
label={t('cite')}
|
|
||||||
initialValue={true}
|
|
||||||
valuePropName="checked"
|
|
||||||
tooltip={t('citeTip')}
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
<MessageHistoryWindowSizeItem
|
|
||||||
initialValue={12}
|
|
||||||
></MessageHistoryWindowSizeItem>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GenerateForm;
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import TopNItem from '@/components/top-n-item';
|
|
||||||
import { Form } from 'antd';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const GithubForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<TopNItem initialValue={5}></TopNItem>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GithubForm;
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
import TopNItem from '@/components/top-n-item';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Input, Select } from 'antd';
|
|
||||||
import { GoogleCountryOptions, GoogleLanguageOptions } from '../../constant';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const GoogleForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<TopNItem initialValue={10}></TopNItem>
|
|
||||||
<Form.Item label={t('apiKey')} name={'api_key'}>
|
|
||||||
<Input></Input>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('country')} name={'country'}>
|
|
||||||
<Select options={GoogleCountryOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('language')} name={'language'}>
|
|
||||||
<Select options={GoogleLanguageOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GoogleForm;
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
import TopNItem from '@/components/top-n-item';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { DatePicker, DatePickerProps, Form, Select, Switch } from 'antd';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { useBuildSortOptions } from '../../form-hooks';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const YearPicker = ({
|
|
||||||
onChange,
|
|
||||||
value,
|
|
||||||
}: {
|
|
||||||
onChange?: (val: number | undefined) => void;
|
|
||||||
value?: number | undefined;
|
|
||||||
}) => {
|
|
||||||
const handleChange: DatePickerProps['onChange'] = useCallback(
|
|
||||||
(val: any) => {
|
|
||||||
const nextVal = val?.format('YYYY');
|
|
||||||
onChange?.(nextVal ? Number(nextVal) : undefined);
|
|
||||||
},
|
|
||||||
[onChange],
|
|
||||||
);
|
|
||||||
// The year needs to be converted into a number and saved to the backend
|
|
||||||
const nextValue = useMemo(() => {
|
|
||||||
if (value) {
|
|
||||||
return dayjs(value.toString());
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}, [value]);
|
|
||||||
|
|
||||||
return <DatePicker picker="year" onChange={handleChange} value={nextValue} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const GoogleScholarForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
const options = useBuildSortOptions();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<TopNItem initialValue={5}></TopNItem>
|
|
||||||
<Form.Item
|
|
||||||
label={t('sortBy')}
|
|
||||||
name={'sort_by'}
|
|
||||||
initialValue={'relevance'}
|
|
||||||
>
|
|
||||||
<Select options={options}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('yearLow')} name={'year_low'}>
|
|
||||||
<YearPicker />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('yearHigh')} name={'year_high'}>
|
|
||||||
<YearPicker />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t('patents')}
|
|
||||||
name={'patents'}
|
|
||||||
valuePropName="checked"
|
|
||||||
initialValue={true}
|
|
||||||
>
|
|
||||||
<Switch></Switch>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GoogleScholarForm;
|
|
||||||
@ -1,130 +0,0 @@
|
|||||||
import { EditableCell, EditableRow } from '@/components/editable-cell';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|
||||||
import { DeleteOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Collapse, Flex, Input, Select, Table, TableProps } from 'antd';
|
|
||||||
import { trim } from 'lodash';
|
|
||||||
import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query';
|
|
||||||
import { IInvokeVariable } from '../../interface';
|
|
||||||
import { useHandleOperateParameters } from './hooks';
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
node?: RAGFlowNodeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
const components = {
|
|
||||||
body: {
|
|
||||||
row: EditableRow,
|
|
||||||
cell: EditableCell,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const DynamicVariablesForm = ({ node }: IProps) => {
|
|
||||||
const nodeId = node?.id;
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
const options = useBuildComponentIdSelectOptions(nodeId, node?.parentId);
|
|
||||||
const {
|
|
||||||
dataSource,
|
|
||||||
handleAdd,
|
|
||||||
handleRemove,
|
|
||||||
handleSave,
|
|
||||||
handleComponentIdChange,
|
|
||||||
handleValueChange,
|
|
||||||
} = useHandleOperateParameters(nodeId!);
|
|
||||||
|
|
||||||
const columns: TableProps<IInvokeVariable>['columns'] = [
|
|
||||||
{
|
|
||||||
title: t('key'),
|
|
||||||
dataIndex: 'key',
|
|
||||||
key: 'key',
|
|
||||||
onCell: (record: IInvokeVariable) => ({
|
|
||||||
record,
|
|
||||||
editable: true,
|
|
||||||
dataIndex: 'key',
|
|
||||||
title: 'key',
|
|
||||||
handleSave,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('componentId'),
|
|
||||||
dataIndex: 'component_id',
|
|
||||||
key: 'component_id',
|
|
||||||
align: 'center',
|
|
||||||
width: 140,
|
|
||||||
render(text, record) {
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
allowClear
|
|
||||||
options={options}
|
|
||||||
value={text}
|
|
||||||
disabled={trim(record.value) !== ''}
|
|
||||||
onChange={handleComponentIdChange(record)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('value'),
|
|
||||||
dataIndex: 'value',
|
|
||||||
key: 'value',
|
|
||||||
align: 'center',
|
|
||||||
width: 140,
|
|
||||||
render(text, record) {
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
value={text}
|
|
||||||
disabled={!!record.component_id}
|
|
||||||
onChange={handleValueChange(record)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('operation'),
|
|
||||||
dataIndex: 'operation',
|
|
||||||
width: 20,
|
|
||||||
key: 'operation',
|
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
|
||||||
render(_, record) {
|
|
||||||
return <DeleteOutlined onClick={handleRemove(record.id)} />;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Collapse
|
|
||||||
className={styles.dynamicParameterVariable}
|
|
||||||
defaultActiveKey={['1']}
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
label: (
|
|
||||||
<Flex justify={'space-between'}>
|
|
||||||
<span className={styles.title}>{t('parameter')}</span>
|
|
||||||
<Button size="small" onClick={handleAdd}>
|
|
||||||
{t('add')}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
children: (
|
|
||||||
<Table
|
|
||||||
dataSource={dataSource}
|
|
||||||
columns={columns}
|
|
||||||
rowKey={'id'}
|
|
||||||
components={components}
|
|
||||||
rowClassName={() => styles.editableRow}
|
|
||||||
scroll={{ x: true }}
|
|
||||||
bordered
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DynamicVariablesForm;
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
import get from 'lodash/get';
|
|
||||||
import {
|
|
||||||
ChangeEventHandler,
|
|
||||||
MouseEventHandler,
|
|
||||||
useCallback,
|
|
||||||
useMemo,
|
|
||||||
} from 'react';
|
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
import { IGenerateParameter, IInvokeVariable } from '../../interface';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
|
|
||||||
export const useHandleOperateParameters = (nodeId: string) => {
|
|
||||||
const { getNode, updateNodeForm } = useGraphStore((state) => state);
|
|
||||||
const node = getNode(nodeId);
|
|
||||||
const dataSource: IGenerateParameter[] = useMemo(
|
|
||||||
() => get(node, 'data.form.variables', []) as IGenerateParameter[],
|
|
||||||
[node],
|
|
||||||
);
|
|
||||||
|
|
||||||
const changeValue = useCallback(
|
|
||||||
(row: IInvokeVariable, field: string, value: string) => {
|
|
||||||
const newData = [...dataSource];
|
|
||||||
const index = newData.findIndex((item) => row.id === item.id);
|
|
||||||
const item = newData[index];
|
|
||||||
newData.splice(index, 1, {
|
|
||||||
...item,
|
|
||||||
[field]: value,
|
|
||||||
});
|
|
||||||
|
|
||||||
updateNodeForm(nodeId, { variables: newData });
|
|
||||||
},
|
|
||||||
[dataSource, nodeId, updateNodeForm],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleComponentIdChange = useCallback(
|
|
||||||
(row: IInvokeVariable) => (value: string) => {
|
|
||||||
changeValue(row, 'component_id', value);
|
|
||||||
},
|
|
||||||
[changeValue],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleValueChange = useCallback(
|
|
||||||
(row: IInvokeVariable): ChangeEventHandler<HTMLInputElement> =>
|
|
||||||
(e) => {
|
|
||||||
changeValue(row, 'value', e.target.value);
|
|
||||||
},
|
|
||||||
[changeValue],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRemove = useCallback(
|
|
||||||
(id?: string) => () => {
|
|
||||||
const newData = dataSource.filter((item) => item.id !== id);
|
|
||||||
updateNodeForm(nodeId, { variables: newData });
|
|
||||||
},
|
|
||||||
[updateNodeForm, nodeId, dataSource],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleAdd: MouseEventHandler = useCallback(
|
|
||||||
(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
updateNodeForm(nodeId, {
|
|
||||||
variables: [
|
|
||||||
...dataSource,
|
|
||||||
{
|
|
||||||
id: uuid(),
|
|
||||||
key: '',
|
|
||||||
component_id: undefined,
|
|
||||||
value: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[dataSource, nodeId, updateNodeForm],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSave = (row: IGenerateParameter) => {
|
|
||||||
const newData = [...dataSource];
|
|
||||||
const index = newData.findIndex((item) => row.id === item.id);
|
|
||||||
const item = newData[index];
|
|
||||||
newData.splice(index, 1, {
|
|
||||||
...item,
|
|
||||||
...row,
|
|
||||||
});
|
|
||||||
|
|
||||||
updateNodeForm(nodeId, { variables: newData });
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleAdd,
|
|
||||||
handleRemove,
|
|
||||||
handleComponentIdChange,
|
|
||||||
handleValueChange,
|
|
||||||
handleSave,
|
|
||||||
dataSource,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
.editableRow {
|
|
||||||
:global(.editable-cell) {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.editable-cell-value-wrap) {
|
|
||||||
padding: 5px 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
height: 30px !important;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
:global(.editable-cell-value-wrap) {
|
|
||||||
padding: 4px 11px;
|
|
||||||
border: 1px solid #d9d9d9;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dynamicParameterVariable {
|
|
||||||
background-color: #ebe9e950;
|
|
||||||
:global(.ant-collapse-content) {
|
|
||||||
background-color: #f6f6f634;
|
|
||||||
}
|
|
||||||
:global(.ant-collapse-content-box) {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
margin-bottom: 20px;
|
|
||||||
.title {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
.variableType {
|
|
||||||
width: 30%;
|
|
||||||
}
|
|
||||||
.variableValue {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.addButton {
|
|
||||||
color: rgb(22, 119, 255);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
import Editor, { loader } from '@monaco-editor/react';
|
|
||||||
import { Form, Input, InputNumber, Select, Space, Switch } from 'antd';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicVariablesForm from './dynamic-variables';
|
|
||||||
|
|
||||||
loader.config({ paths: { vs: '/vs' } });
|
|
||||||
|
|
||||||
enum Method {
|
|
||||||
GET = 'GET',
|
|
||||||
POST = 'POST',
|
|
||||||
PUT = 'PUT',
|
|
||||||
}
|
|
||||||
|
|
||||||
const MethodOptions = [Method.GET, Method.POST, Method.PUT].map((x) => ({
|
|
||||||
label: x,
|
|
||||||
value: x,
|
|
||||||
}));
|
|
||||||
|
|
||||||
interface TimeoutInputProps {
|
|
||||||
value?: number;
|
|
||||||
onChange?: (value: number | null) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TimeoutInput = ({ value, onChange }: TimeoutInputProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<Space>
|
|
||||||
<InputNumber value={value} onChange={onChange} /> {t('common.s')}
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const InvokeForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<Form.Item name={'url'} label={t('flow.url')}>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name={'method'}
|
|
||||||
label={t('flow.method')}
|
|
||||||
initialValue={Method.GET}
|
|
||||||
>
|
|
||||||
<Select options={MethodOptions} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name={'timeout'} label={t('flow.timeout')}>
|
|
||||||
<TimeoutInput></TimeoutInput>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name={'headers'} label={t('flow.headers')}>
|
|
||||||
<Editor height={200} defaultLanguage="json" theme="vs-dark" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name={'proxy'} label={t('flow.proxy')}>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name={'clean_html'}
|
|
||||||
label={t('flow.cleanHtml')}
|
|
||||||
tooltip={t('flow.cleanHtmlTip')}
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name={'datatype'} label={t('flow.datatype')}>
|
|
||||||
<Select
|
|
||||||
options={[
|
|
||||||
{ value: 'json', label: 'application/json' },
|
|
||||||
{ value: 'formdata', label: 'multipart/form-data' },
|
|
||||||
]}
|
|
||||||
allowClear={true}
|
|
||||||
></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<DynamicVariablesForm node={node}></DynamicVariablesForm>
|
|
||||||
</Form>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default InvokeForm;
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
import { CommaIcon, SemicolonIcon } from '@/assets/icon/next-icon';
|
|
||||||
import { Form, Select } from 'antd';
|
|
||||||
import {
|
|
||||||
CornerDownLeft,
|
|
||||||
IndentIncrease,
|
|
||||||
Minus,
|
|
||||||
Slash,
|
|
||||||
Underline,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const optionList = [
|
|
||||||
{
|
|
||||||
value: ',',
|
|
||||||
icon: CommaIcon,
|
|
||||||
text: 'comma',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: '\n',
|
|
||||||
icon: CornerDownLeft,
|
|
||||||
text: 'lineBreak',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'tab',
|
|
||||||
icon: IndentIncrease,
|
|
||||||
text: 'tab',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: '_',
|
|
||||||
icon: Underline,
|
|
||||||
text: 'underline',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: '/',
|
|
||||||
icon: Slash,
|
|
||||||
text: 'diagonal',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: '-',
|
|
||||||
icon: Minus,
|
|
||||||
text: 'minus',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: ';',
|
|
||||||
icon: SemicolonIcon,
|
|
||||||
text: 'semicolon',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const IterationForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const options = useMemo(() => {
|
|
||||||
return optionList.map((x) => {
|
|
||||||
let Icon = x.icon;
|
|
||||||
|
|
||||||
return {
|
|
||||||
value: x.value,
|
|
||||||
label: (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Icon className={'size-4'}></Icon>
|
|
||||||
{t(`flow.delimiterOptions.${x.text}`)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<Form.Item
|
|
||||||
name={['delimiter']}
|
|
||||||
label={t('knowledgeDetails.delimiter')}
|
|
||||||
initialValue={`\\n!?;。;!?`}
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
tooltip={t('flow.delimiterTip')}
|
|
||||||
>
|
|
||||||
<Select options={options}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default IterationForm;
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Input, Select } from 'antd';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import {
|
|
||||||
Jin10CalendarDatashapeOptions,
|
|
||||||
Jin10CalendarTypeOptions,
|
|
||||||
Jin10FlashTypeOptions,
|
|
||||||
Jin10SymbolsDatatypeOptions,
|
|
||||||
Jin10SymbolsTypeOptions,
|
|
||||||
Jin10TypeOptions,
|
|
||||||
} from '../../constant';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const Jin10Form = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
const jin10TypeOptions = useMemo(() => {
|
|
||||||
return Jin10TypeOptions.map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`jin10TypeOptions.${x}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
const jin10FlashTypeOptions = useMemo(() => {
|
|
||||||
return Jin10FlashTypeOptions.map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`jin10FlashTypeOptions.${x}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
const jin10CalendarTypeOptions = useMemo(() => {
|
|
||||||
return Jin10CalendarTypeOptions.map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`jin10CalendarTypeOptions.${x}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
const jin10CalendarDatashapeOptions = useMemo(() => {
|
|
||||||
return Jin10CalendarDatashapeOptions.map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`jin10CalendarDatashapeOptions.${x}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
const jin10SymbolsTypeOptions = useMemo(() => {
|
|
||||||
return Jin10SymbolsTypeOptions.map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`jin10SymbolsTypeOptions.${x}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
const jin10SymbolsDatatypeOptions = useMemo(() => {
|
|
||||||
return Jin10SymbolsDatatypeOptions.map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`jin10SymbolsDatatypeOptions.${x}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<Form.Item label={t('type')} name={'type'} initialValue={'flash'}>
|
|
||||||
<Select options={jin10TypeOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('secretKey')} name={'secret_key'}>
|
|
||||||
<Input></Input>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item noStyle dependencies={['type']}>
|
|
||||||
{({ getFieldValue }) => {
|
|
||||||
const type = getFieldValue('type');
|
|
||||||
switch (type) {
|
|
||||||
case 'flash':
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Form.Item label={t('flashType')} name={'flash_type'}>
|
|
||||||
<Select options={jin10FlashTypeOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('contain')} name={'contain'}>
|
|
||||||
<Input></Input>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('filter')} name={'filter'}>
|
|
||||||
<Input></Input>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'calendar':
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Form.Item label={t('calendarType')} name={'calendar_type'}>
|
|
||||||
<Select options={jin10CalendarTypeOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t('calendarDatashape')}
|
|
||||||
name={'calendar_datashape'}
|
|
||||||
>
|
|
||||||
<Select options={jin10CalendarDatashapeOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'symbols':
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Form.Item label={t('symbolsType')} name={'symbols_type'}>
|
|
||||||
<Select options={jin10SymbolsTypeOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t('symbolsDatatype')}
|
|
||||||
name={'symbols_datatype'}
|
|
||||||
>
|
|
||||||
<Select options={jin10SymbolsDatatypeOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
case 'news':
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Form.Item label={t('contain')} name={'contain'}>
|
|
||||||
<Input></Input>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('filter')} name={'filter'}>
|
|
||||||
<Input></Input>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Jin10Form;
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import LLMSelect from '@/components/llm-select';
|
|
||||||
import TopNItem from '@/components/top-n-item';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form } from 'antd';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const KeywordExtractForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<Form.Item
|
|
||||||
name={'llm_id'}
|
|
||||||
label={t('model', { keyPrefix: 'chat' })}
|
|
||||||
tooltip={t('modelTip', { keyPrefix: 'chat' })}
|
|
||||||
>
|
|
||||||
<LLMSelect></LLMSelect>
|
|
||||||
</Form.Item>
|
|
||||||
<TopNItem initialValue={3}></TopNItem>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default KeywordExtractForm;
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
.dynamicDeleteButton {
|
|
||||||
position: relative;
|
|
||||||
top: 4px;
|
|
||||||
margin: 0 8px;
|
|
||||||
color: #999;
|
|
||||||
font-size: 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
&:hover {
|
|
||||||
color: #777;
|
|
||||||
}
|
|
||||||
&[disabled] {
|
|
||||||
cursor: not-allowed;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Form, Input } from 'antd';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
const formItemLayout = {
|
|
||||||
labelCol: {
|
|
||||||
sm: { span: 6 },
|
|
||||||
},
|
|
||||||
wrapperCol: {
|
|
||||||
sm: { span: 18 },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const formItemLayoutWithOutLabel = {
|
|
||||||
wrapperCol: {
|
|
||||||
sm: { span: 18, offset: 6 },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const MessageForm = ({ onValuesChange, form }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
{...formItemLayoutWithOutLabel}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
>
|
|
||||||
<Form.List name="messages">
|
|
||||||
{(fields, { add, remove }, {}) => (
|
|
||||||
<>
|
|
||||||
{fields.map((field, index) => (
|
|
||||||
<Form.Item
|
|
||||||
{...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
|
|
||||||
label={index === 0 ? t('msg') : ''}
|
|
||||||
required={false}
|
|
||||||
key={field.key}
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
{...field}
|
|
||||||
validateTrigger={['onChange', 'onBlur']}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
whitespace: true,
|
|
||||||
message: t('messageMsg'),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
noStyle
|
|
||||||
>
|
|
||||||
<Input.TextArea
|
|
||||||
rows={4}
|
|
||||||
placeholder={t('messagePlaceholder')}
|
|
||||||
style={{ width: '80%' }}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
{fields.length > 1 ? (
|
|
||||||
<MinusCircleOutlined
|
|
||||||
className={styles.dynamicDeleteButton}
|
|
||||||
onClick={() => remove(field.name)}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</Form.Item>
|
|
||||||
))}
|
|
||||||
<Form.Item>
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
onClick={() => add()}
|
|
||||||
style={{ width: '80%' }}
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
>
|
|
||||||
{t('addMessage')}
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Form.List>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MessageForm;
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import TopNItem from '@/components/top-n-item';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Input } from 'antd';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const PubMedForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<TopNItem initialValue={10}></TopNItem>
|
|
||||||
<Form.Item
|
|
||||||
label={t('email')}
|
|
||||||
name={'email'}
|
|
||||||
tooltip={t('emailTip')}
|
|
||||||
rules={[{ type: 'email' }]}
|
|
||||||
>
|
|
||||||
<Input></Input>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PubMedForm;
|
|
||||||
@ -1,88 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Input, Select } from 'antd';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import {
|
|
||||||
QWeatherLangOptions,
|
|
||||||
QWeatherTimePeriodOptions,
|
|
||||||
QWeatherTypeOptions,
|
|
||||||
QWeatherUserTypeOptions,
|
|
||||||
} from '../../constant';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
|
||||||
|
|
||||||
const QWeatherForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const qWeatherLangOptions = useMemo(() => {
|
|
||||||
return QWeatherLangOptions.map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`qWeatherLangOptions.${x}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
const qWeatherTypeOptions = useMemo(() => {
|
|
||||||
return QWeatherTypeOptions.map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`qWeatherTypeOptions.${x}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
const qWeatherUserTypeOptions = useMemo(() => {
|
|
||||||
return QWeatherUserTypeOptions.map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`qWeatherUserTypeOptions.${x}`),
|
|
||||||
}));
|
|
||||||
}, [t]);
|
|
||||||
|
|
||||||
const getQWeatherTimePeriodOptions = useCallback(
|
|
||||||
(userType: string) => {
|
|
||||||
let options = QWeatherTimePeriodOptions;
|
|
||||||
if (userType === 'free') {
|
|
||||||
options = options.slice(0, 3);
|
|
||||||
}
|
|
||||||
return options.map((x) => ({
|
|
||||||
value: x,
|
|
||||||
label: t(`qWeatherTimePeriodOptions.${x}`),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
[t],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
layout={'vertical'}
|
|
||||||
>
|
|
||||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
|
||||||
<Form.Item label={t('webApiKey')} name={'web_apikey'}>
|
|
||||||
<Input></Input>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('lang')} name={'lang'}>
|
|
||||||
<Select options={qWeatherLangOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('type')} name={'type'}>
|
|
||||||
<Select options={qWeatherTypeOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('userType')} name={'user_type'}>
|
|
||||||
<Select options={qWeatherUserTypeOptions}></Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item noStyle dependencies={['type', 'user_type']}>
|
|
||||||
{({ getFieldValue }) =>
|
|
||||||
getFieldValue('type') === 'weather' && (
|
|
||||||
<Form.Item label={t('timePeriod')} name={'time_period'}>
|
|
||||||
<Select
|
|
||||||
options={getQWeatherTimePeriodOptions(
|
|
||||||
getFieldValue('user_type'),
|
|
||||||
)}
|
|
||||||
></Select>
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default QWeatherForm;
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
import pick from 'lodash/pick';
|
|
||||||
import { useCallback, useEffect } from 'react';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import useGraphStore from '../../store';
|
|
||||||
|
|
||||||
export const useBuildRelevantOptions = () => {
|
|
||||||
const nodes = useGraphStore((state) => state.nodes);
|
|
||||||
|
|
||||||
const buildRelevantOptions = useCallback(
|
|
||||||
(toList: string[]) => {
|
|
||||||
return nodes
|
|
||||||
.filter(
|
|
||||||
(x) => !toList.some((y) => y === x.id), // filter out selected values in other to fields from the current drop-down box options
|
|
||||||
)
|
|
||||||
.map((x) => ({ label: x.data.name, value: x.id }));
|
|
||||||
},
|
|
||||||
[nodes],
|
|
||||||
);
|
|
||||||
|
|
||||||
return buildRelevantOptions;
|
|
||||||
};
|
|
||||||
|
|
||||||
// const getTargetOfEdge = (edges: Edge[], sourceHandle: string) =>
|
|
||||||
// edges.find((x) => x.sourceHandle === sourceHandle)?.target;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* monitor changes in the connection and synchronize the target to the yes and no fields of the form
|
|
||||||
* similar to the categorize-form's useHandleFormValuesChange method
|
|
||||||
* @param param0
|
|
||||||
*/
|
|
||||||
export const useWatchConnectionChanges = ({ nodeId, form }: IOperatorForm) => {
|
|
||||||
// const edges = useGraphStore((state) => state.edges);
|
|
||||||
const getNode = useGraphStore((state) => state.getNode);
|
|
||||||
const node = getNode(nodeId);
|
|
||||||
|
|
||||||
const watchFormChanges = useCallback(() => {
|
|
||||||
if (node) {
|
|
||||||
form?.setFieldsValue(pick(node, ['yes', 'no']));
|
|
||||||
}
|
|
||||||
}, [node, form]);
|
|
||||||
|
|
||||||
// const watchConnectionChanges = useCallback(() => {
|
|
||||||
// const edgeList = edges.filter((x) => x.source === nodeId);
|
|
||||||
// const yes = getTargetOfEdge(edgeList, 'yes');
|
|
||||||
// const no = getTargetOfEdge(edgeList, 'no');
|
|
||||||
// form?.setFieldsValue({ yes, no });
|
|
||||||
// }, [edges, nodeId, form]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
watchFormChanges();
|
|
||||||
}, [watchFormChanges]);
|
|
||||||
};
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import LLMSelect from '@/components/llm-select';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
|
||||||
import { Form, Select } from 'antd';
|
|
||||||
import { Operator } from '../../constant';
|
|
||||||
import { useBuildFormSelectOptions } from '../../form-hooks';
|
|
||||||
import { IOperatorForm } from '../../interface';
|
|
||||||
import { useWatchConnectionChanges } from './hooks';
|
|
||||||
|
|
||||||
const RelevantForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
|
||||||
const { t } = useTranslate('flow');
|
|
||||||
const buildRelevantOptions = useBuildFormSelectOptions(
|
|
||||||
Operator.Relevant,
|
|
||||||
node?.id,
|
|
||||||
);
|
|
||||||
useWatchConnectionChanges({ nodeId: node?.id, form });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
name="basic"
|
|
||||||
labelCol={{ span: 4 }}
|
|
||||||
wrapperCol={{ span: 20 }}
|
|
||||||
onValuesChange={onValuesChange}
|
|
||||||
autoComplete="off"
|
|
||||||
form={form}
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
name={'llm_id'}
|
|
||||||
label={t('model', { keyPrefix: 'chat' })}
|
|
||||||
tooltip={t('modelTip', { keyPrefix: 'chat' })}
|
|
||||||
>
|
|
||||||
<LLMSelect></LLMSelect>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('yes')} name={'yes'}>
|
|
||||||
<Select
|
|
||||||
allowClear
|
|
||||||
options={buildRelevantOptions([form?.getFieldValue('no')])}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t('no')} name={'no'}>
|
|
||||||
<Select
|
|
||||||
allowClear
|
|
||||||
options={buildRelevantOptions([form?.getFieldValue('yes')])}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RelevantForm;
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user