chores: 删除前端agent相关内容及无用语言文件
- 删除了多个与流程功能相关的组件和文件 - 移除了 API 服务中与流程相关的无用代码 - 更新了导航钩子和头部组件,移除了与流程相关的路由和图标 - 删除了多个语言翻译文件
This commit is contained in:
parent
aee7779237
commit
86303e358e
|
@ -155,13 +155,7 @@ export const usePreviewChat = (idKey: string) => {
|
|||
|
||||
const open = useCallback(
|
||||
(t: string) => {
|
||||
window.open(
|
||||
getUrlWithToken(
|
||||
t,
|
||||
idKey === 'canvasId' ? SharedFrom.Agent : SharedFrom.Chat,
|
||||
),
|
||||
'_blank',
|
||||
);
|
||||
window.open(getUrlWithToken(t, SharedFrom.Chat), '_blank');
|
||||
},
|
||||
[idKey],
|
||||
);
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import i18n from '@/locales/config';
|
||||
import { BeginId } from '@/pages/flow/constant';
|
||||
import { DecoratorNode, LexicalNode, NodeKey } from 'lexical';
|
||||
import { ReactNode } from 'react';
|
||||
const prefix = BeginId + '@';
|
||||
|
||||
export class VariableNode extends DecoratorNode<ReactNode> {
|
||||
__value: string;
|
||||
|
@ -37,13 +34,6 @@ export class VariableNode extends DecoratorNode<ReactNode> {
|
|||
let content: ReactNode = (
|
||||
<span className="text-blue-600">{this.__label}</span>
|
||||
);
|
||||
if (this.__value.startsWith(prefix)) {
|
||||
content = (
|
||||
<div>
|
||||
<span>{i18n.t(`flow.begin`)}</span> / {content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="bg-gray-200 dark:bg-gray-400 text-primary inline-flex items-center rounded-md px-2 py-0">
|
||||
{content}
|
||||
|
|
|
@ -29,8 +29,6 @@ import {
|
|||
} from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
import { FlowFormContext } from '@/pages/flow/context';
|
||||
import { useBuildComponentIdSelectOptions } from '@/pages/flow/hooks/use-get-begin-query';
|
||||
import { $createVariableNode } from './variable-node';
|
||||
|
||||
import { ProgrammaticTag } from './constant';
|
||||
|
|
|
@ -20,7 +20,6 @@ export const variableEnabledFieldMap = {
|
|||
};
|
||||
|
||||
export enum SharedFrom {
|
||||
Agent = 'agent',
|
||||
Chat = 'chat',
|
||||
}
|
||||
|
||||
|
|
|
@ -1,308 +0,0 @@
|
|||
import { ResponseType } from '@/interfaces/database/base';
|
||||
import { DSL, IFlow, IFlowTemplate } from '@/interfaces/database/flow';
|
||||
import { IDebugSingleRequestBody } from '@/interfaces/request/flow';
|
||||
import i18n from '@/locales/config';
|
||||
import { useGetSharedChatSearchParams } from '@/pages/chat/shared-hooks';
|
||||
import { BeginId } from '@/pages/flow/constant';
|
||||
import flowService from '@/services/flow-service';
|
||||
import { buildMessageListWithUuid } from '@/utils/chat';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { message } from 'antd';
|
||||
import { set } from 'lodash';
|
||||
import get from 'lodash/get';
|
||||
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 { data } = useQuery({
|
||||
queryKey: ['fetchFlowTemplates'],
|
||||
initialData: [],
|
||||
queryFn: async () => {
|
||||
const { data } = await flowService.listTemplates();
|
||||
if (Array.isArray(data?.data)) {
|
||||
data.data.unshift({
|
||||
id: uuid(),
|
||||
title: 'Blank',
|
||||
description: 'Create your agent from scratch',
|
||||
dsl: EmptyDsl,
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useFetchFlowList = (): { data: IFlow[]; loading: boolean } => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: ['fetchFlowList'],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await flowService.listCanvas();
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchFlow = (): {
|
||||
data: IFlow;
|
||||
loading: boolean;
|
||||
refetch: () => void;
|
||||
} => {
|
||||
const { id } = useParams();
|
||||
const { sharedId } = useGetSharedChatSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: ['flowDetail'],
|
||||
initialData: {} as IFlow,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await flowService.getCanvas({}, sharedId || id);
|
||||
|
||||
const messageList = buildMessageListWithUuid(
|
||||
get(data, 'data.dsl.messages', []),
|
||||
);
|
||||
set(data, 'data.dsl.messages', messageList);
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useFetchFlowSSE = (): {
|
||||
data: IFlow;
|
||||
loading: boolean;
|
||||
refetch: () => void;
|
||||
} => {
|
||||
const { sharedId } = useGetSharedChatSearchParams();
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: ['flowDetailSSE'],
|
||||
initialData: {} as IFlow,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
if (!sharedId) return {};
|
||||
const { data } = await flowService.getCanvasSSE({}, sharedId);
|
||||
|
||||
const messageList = buildMessageListWithUuid(
|
||||
get(data, 'data.dsl.messages', []),
|
||||
);
|
||||
set(data, 'data.dsl.messages', messageList);
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useSetFlow = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['setFlow'],
|
||||
mutationFn: async (params: {
|
||||
id?: string;
|
||||
title?: string;
|
||||
dsl?: DSL;
|
||||
avatar?: string;
|
||||
}) => {
|
||||
const { data = {} } = await flowService.setCanvas(params);
|
||||
if (data.code === 0) {
|
||||
message.success(
|
||||
i18n.t(`message.${params?.id ? 'modified' : 'created'}`),
|
||||
);
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchFlowList'] });
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, setFlow: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteFlow = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['deleteFlow'],
|
||||
mutationFn: async (canvasIds: string[]) => {
|
||||
const { data } = await flowService.removeCanvas({ canvasIds });
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['fetchFlowList'] });
|
||||
}
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteFlow: mutateAsync };
|
||||
};
|
||||
|
||||
export const useRunFlow = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['runFlow'],
|
||||
mutationFn: async (params: { id: string; dsl: DSL }) => {
|
||||
const { data } = await flowService.runCanvas(params);
|
||||
if (data.code === 0) {
|
||||
message.success(i18n.t(`message.modified`));
|
||||
}
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, runFlow: mutateAsync };
|
||||
};
|
||||
|
||||
export const useResetFlow = () => {
|
||||
const { id } = useParams();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['resetFlow'],
|
||||
mutationFn: async () => {
|
||||
const { data } = await flowService.resetCanvas({ id });
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, resetFlow: mutateAsync };
|
||||
};
|
||||
|
||||
export const useTestDbConnect = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['testDbConnect'],
|
||||
mutationFn: async (params: any) => {
|
||||
const ret = await flowService.testDbConnect(params);
|
||||
if (ret?.data?.code === 0) {
|
||||
message.success(ret?.data?.data);
|
||||
} else {
|
||||
message.error(ret?.data?.data);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, testDbConnect: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchInputElements = (componentId?: string) => {
|
||||
const { id } = useParams();
|
||||
|
||||
const { data, isPending: loading } = useQuery({
|
||||
queryKey: ['fetchInputElements', id, componentId],
|
||||
initialData: [],
|
||||
enabled: !!id && !!componentId,
|
||||
retryOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const { data } = await flowService.getInputElements({
|
||||
id,
|
||||
component_id: componentId,
|
||||
});
|
||||
return data?.data ?? [];
|
||||
} catch (error) {
|
||||
console.log('🚀 ~ queryFn: ~ error:', error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useDebugSingle = () => {
|
||||
const { id } = useParams();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['debugSingle'],
|
||||
mutationFn: async (params: IDebugSingleRequestBody) => {
|
||||
const ret = await flowService.debugSingle({ id, ...params });
|
||||
if (ret?.data?.code !== 0) {
|
||||
message.error(ret?.data?.message);
|
||||
}
|
||||
return ret?.data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, debugSingle: mutateAsync };
|
||||
};
|
|
@ -38,21 +38,6 @@ export const useNavigatePage = () => {
|
|||
navigate(Routes.Chat);
|
||||
}, [navigate]);
|
||||
|
||||
const navigateToAgentList = useCallback(() => {
|
||||
navigate(Routes.Agents);
|
||||
}, [navigate]);
|
||||
|
||||
const navigateToAgent = useCallback(
|
||||
(id: string) => () => {
|
||||
navigate(`${Routes.Agent}/${id}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToAgentTemplates = useCallback(() => {
|
||||
navigate(Routes.AgentTemplates);
|
||||
}, [navigate]);
|
||||
|
||||
const navigateToSearchList = useCallback(() => {
|
||||
navigate(Routes.Searches);
|
||||
}, [navigate]);
|
||||
|
@ -104,9 +89,7 @@ export const useNavigatePage = () => {
|
|||
navigateToChunkParsedResult,
|
||||
getQueryString,
|
||||
navigateToChunk,
|
||||
navigateToAgentList,
|
||||
navigateToAgent,
|
||||
navigateToAgentTemplates,
|
||||
|
||||
navigateToSearchList,
|
||||
navigateToSearch,
|
||||
};
|
||||
|
|
|
@ -9,7 +9,6 @@ import { cn } from '@/lib/utils';
|
|||
import { Routes } from '@/routes';
|
||||
import {
|
||||
ChevronDown,
|
||||
Cpu,
|
||||
File,
|
||||
Github,
|
||||
House,
|
||||
|
@ -33,7 +32,6 @@ export function Header() {
|
|||
{ path: Routes.Datasets, name: t('knowledgeBase'), icon: Library },
|
||||
{ path: Routes.Chats, name: t('chat'), icon: MessageSquareText },
|
||||
{ path: Routes.Searches, name: t('search'), icon: Search },
|
||||
{ path: Routes.Agents, name: t('flow'), icon: Cpu },
|
||||
{ path: Routes.Files, name: t('fileManager'), icon: File },
|
||||
],
|
||||
[t],
|
||||
|
|
|
@ -3,14 +3,8 @@ import LanguageDetector from 'i18next-browser-languagedetector';
|
|||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
import { LanguageAbbreviation } from '@/constants/common';
|
||||
import translation_de from './de';
|
||||
import translation_en from './en';
|
||||
import translation_es from './es';
|
||||
import translation_id from './id';
|
||||
import translation_ja from './ja';
|
||||
import translation_pt_br from './pt-br';
|
||||
import { createTranslationTable, flattenObject } from './until';
|
||||
import translation_vi from './vi';
|
||||
import translation_zh from './zh';
|
||||
import translation_zh_traditional from './zh-traditional';
|
||||
|
||||
|
@ -18,42 +12,16 @@ const resources = {
|
|||
[LanguageAbbreviation.En]: translation_en,
|
||||
[LanguageAbbreviation.Zh]: translation_zh,
|
||||
[LanguageAbbreviation.ZhTraditional]: translation_zh_traditional,
|
||||
[LanguageAbbreviation.Id]: translation_id,
|
||||
[LanguageAbbreviation.Ja]: translation_ja,
|
||||
[LanguageAbbreviation.Es]: translation_es,
|
||||
[LanguageAbbreviation.Vi]: translation_vi,
|
||||
[LanguageAbbreviation.PtBr]: translation_pt_br,
|
||||
[LanguageAbbreviation.De]: translation_de,
|
||||
};
|
||||
const enFlattened = flattenObject(translation_en);
|
||||
const viFlattened = flattenObject(translation_vi);
|
||||
const esFlattened = flattenObject(translation_es);
|
||||
|
||||
const zhFlattened = flattenObject(translation_zh);
|
||||
const jaFlattened = flattenObject(translation_ja);
|
||||
const pt_brFlattened = flattenObject(translation_pt_br);
|
||||
|
||||
const zh_traditionalFlattened = flattenObject(translation_zh_traditional);
|
||||
const deFlattened = flattenObject(translation_de);
|
||||
|
||||
export const translationTable = createTranslationTable(
|
||||
[
|
||||
enFlattened,
|
||||
viFlattened,
|
||||
esFlattened,
|
||||
zhFlattened,
|
||||
zh_traditionalFlattened,
|
||||
jaFlattened,
|
||||
pt_brFlattened,
|
||||
deFlattened,
|
||||
],
|
||||
[
|
||||
'English',
|
||||
'Vietnamese',
|
||||
'Spanish',
|
||||
'zh',
|
||||
'zh-TRADITIONAL',
|
||||
'ja',
|
||||
'pt-BR',
|
||||
'Deutsch',
|
||||
],
|
||||
[enFlattened, zhFlattened, zh_traditionalFlattened],
|
||||
['English', 'zh', 'zh-TRADITIONAL'],
|
||||
);
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
import { SideDown } from '@/assets/icon/Icon';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from '@/components/ui/collapsible';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarGroupLabel,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
} from '@/components/ui/sidebar';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
AgentOperatorList,
|
||||
Operator,
|
||||
componentMenuList,
|
||||
operatorMap,
|
||||
} from './constant';
|
||||
import OperatorIcon from './operator-icon';
|
||||
|
||||
type OperatorItem = {
|
||||
name: Operator;
|
||||
};
|
||||
|
||||
function OperatorCard({ name }: OperatorItem) {
|
||||
return (
|
||||
<Card className="bg-colors-background-inverse-weak border-colors-outline-neutral-standard">
|
||||
<CardContent className="p-2 flex items-center gap-2">
|
||||
<OperatorIcon
|
||||
name={name}
|
||||
color={operatorMap[name].color}
|
||||
></OperatorIcon>
|
||||
{name}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
type OperatorCollapsibleProps = { operatorList: OperatorItem[]; title: string };
|
||||
|
||||
function OperatorCollapsible({
|
||||
operatorList,
|
||||
title,
|
||||
}: OperatorCollapsibleProps) {
|
||||
return (
|
||||
<Collapsible defaultOpen className="group/collapsible">
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel asChild className="mb-1">
|
||||
<CollapsibleTrigger>
|
||||
<span className="font-bold text-base">{title}</span>
|
||||
<SideDown className="ml-auto" />
|
||||
</CollapsibleTrigger>
|
||||
</SidebarGroupLabel>
|
||||
<CollapsibleContent className="px-2">
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu className="gap-2">
|
||||
{operatorList.map((item) => (
|
||||
<OperatorCard key={item.name} name={item.name}></OperatorCard>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</CollapsibleContent>
|
||||
</SidebarGroup>
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
|
||||
export function AgentSidebar() {
|
||||
const agentOperatorList = useMemo(() => {
|
||||
return componentMenuList.filter((x) =>
|
||||
AgentOperatorList.some((y) => y === x.name),
|
||||
);
|
||||
}, []);
|
||||
|
||||
const thirdOperatorList = useMemo(() => {
|
||||
return componentMenuList.filter(
|
||||
(x) => !AgentOperatorList.some((y) => y === x.name),
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Sidebar variant={'floating'} className="top-16">
|
||||
<SidebarHeader>
|
||||
<p className="font-bold text-2xl">All nodes</p>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<OperatorCollapsible
|
||||
title="Agent operator"
|
||||
operatorList={agentOperatorList}
|
||||
></OperatorCollapsible>
|
||||
<OperatorCollapsible
|
||||
title="Third-party tools"
|
||||
operatorList={thirdOperatorList}
|
||||
></OperatorCollapsible>
|
||||
</SidebarContent>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
|
@ -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,183 +0,0 @@
|
|||
import {
|
||||
Background,
|
||||
ConnectionMode,
|
||||
NodeTypes,
|
||||
ReactFlow,
|
||||
} from '@xyflow/react';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
// import ChatDrawer from '../chat/drawer';
|
||||
import FormSheet from '../form-sheet/next';
|
||||
import {
|
||||
useHandleDrop,
|
||||
useSelectCanvasData,
|
||||
useValidateConnection,
|
||||
useWatchNodeFormDataChange,
|
||||
} from '../hooks';
|
||||
import { useBeforeDelete } from '../hooks/use-before-delete';
|
||||
import { useShowDrawer } from '../hooks/use-show-drawer';
|
||||
// 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';
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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 {
|
||||
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 />
|
||||
</ReactFlow>
|
||||
{formDrawerVisible && (
|
||||
<FormSheet
|
||||
node={clickedNode}
|
||||
visible={formDrawerVisible}
|
||||
hideModal={hideFormDrawer}
|
||||
singleDebugDrawerVisible={singleDebugDrawerVisible}
|
||||
hideSingleDebugDrawer={hideSingleDebugDrawer}
|
||||
showSingleDebugDrawer={showSingleDebugDrawer}
|
||||
></FormSheet>
|
||||
)}
|
||||
{/* {chatVisible && (
|
||||
<ChatDrawer
|
||||
visible={chatVisible}
|
||||
hideModal={hideRunOrChatDrawer}
|
||||
></ChatDrawer>
|
||||
)}
|
||||
|
||||
{runVisible && (
|
||||
<RunDrawer
|
||||
hideModal={hideRunOrChatDrawer}
|
||||
showModal={showChatModal}
|
||||
></RunDrawer>
|
||||
)} */}
|
||||
</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,121 +0,0 @@
|
|||
import { useFetchFlow } from '@/hooks/flow-hooks';
|
||||
import get from 'lodash/get';
|
||||
import React, { MouseEventHandler, useCallback, useMemo } from 'react';
|
||||
import JsonView from 'react18-json-view';
|
||||
import 'react18-json-view/src/style.css';
|
||||
import { useReplaceIdWithText } from '../../hooks';
|
||||
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
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 { 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();
|
||||
const { theme } = useTheme();
|
||||
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 { replacedOutput } = useReplaceIdWithText(output);
|
||||
const stopPropagation: MouseEventHandler = useCallback((e) => {
|
||||
e.stopPropagation();
|
||||
}, []);
|
||||
|
||||
const getLabel = useGetComponentLabelByValue(nodeId);
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger onClick={stopPropagation} asChild>
|
||||
{children}
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align={'start'}
|
||||
side={'right'}
|
||||
sideOffset={20}
|
||||
onClick={stopPropagation}
|
||||
className="w-[400px]"
|
||||
>
|
||||
<div className="mb-3 font-semibold text-[16px]">
|
||||
{name} {t('operationResults')}
|
||||
</div>
|
||||
<div className="flex w-full gap-4 flex-col">
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<span className="font-semibold text-[14px]">{t('input')}</span>
|
||||
<div
|
||||
style={
|
||||
theme === 'dark'
|
||||
? {
|
||||
backgroundColor: 'rgba(150, 150, 150, 0.2)',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
className={`bg-gray-100 p-1 rounded`}
|
||||
>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>{t('componentId')}</TableHead>
|
||||
<TableHead className="w-[60px]">{t('content')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{inputs.map((x, idx) => (
|
||||
<TableRow key={idx}>
|
||||
<TableCell>{getLabel(x.component_id)}</TableCell>
|
||||
<TableCell className="truncate">{x.content}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<span className="font-semibold text-[14px]">{t('output')}</span>
|
||||
<div
|
||||
style={
|
||||
theme === 'dark'
|
||||
? {
|
||||
backgroundColor: 'rgba(150, 150, 150, 0.2)',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
className="bg-gray-100 p-1 rounded"
|
||||
>
|
||||
<JsonView
|
||||
src={replacedOutput}
|
||||
displaySize={30}
|
||||
className="w-full max-h-[300px] break-words overflow-auto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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,88 +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, Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { useMemo } 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 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]);
|
||||
|
||||
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}>
|
||||
{knowledgeBases.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>
|
||||
);
|
||||
})}
|
||||
</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>
|
||||
);
|
||||
}
|
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,5 +0,0 @@
|
|||
.formWrapper {
|
||||
:global(.ant-form-item-label) {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
|
@ -1,238 +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';
|
||||
|
||||
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>
|
||||
),
|
||||
};
|
||||
|
||||
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 = ``;
|
||||
|
||||
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,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 @@
|
|||
.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,208 +0,0 @@
|
|||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { 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 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/next';
|
||||
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,
|
||||
needsSingleStepDebugging,
|
||||
} from '../utils';
|
||||
|
||||
import { Sheet, SheetContent, SheetHeader } from '@/components/ui/sheet';
|
||||
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]: () => <></>,
|
||||
};
|
||||
|
||||
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 (
|
||||
<Sheet onOpenChange={hideModal} open={visible}>
|
||||
<SheetContent>
|
||||
<SheetHeader>
|
||||
<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>
|
||||
</SheetHeader>
|
||||
<section className={styles.formWrapper}>
|
||||
{visible && (
|
||||
<FlowFormContext.Provider value={node}>
|
||||
<OperatorForm
|
||||
onValuesChange={handleValuesChange}
|
||||
form={form}
|
||||
node={node}
|
||||
></OperatorForm>
|
||||
</FlowFormContext.Provider>
|
||||
)}
|
||||
</section>
|
||||
</SheetContent>
|
||||
{/* {singleDebugDrawerVisible && (
|
||||
<SingleDebugDrawer
|
||||
visible={singleDebugDrawerVisible}
|
||||
hideModal={hideSingleDebugDrawer}
|
||||
componentId={node?.id}
|
||||
></SingleDebugDrawer>
|
||||
)} */}
|
||||
</Sheet>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormDrawer;
|
|
@ -1,158 +0,0 @@
|
|||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from '@/components/ui/sheet';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { get, isPlainObject, lowerFirst } from 'lodash';
|
||||
import { Play, X } from 'lucide-react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { BeginId, Operator, operatorMap } from '../constant';
|
||||
import { FlowFormContext } from '../context';
|
||||
import { RunTooltip } from '../flow-tooltip';
|
||||
import { useHandleFormValuesChange, useHandleNodeNameChange } from '../hooks';
|
||||
import OperatorIcon from '../operator-icon';
|
||||
import {
|
||||
buildCategorizeListFromObject,
|
||||
needsSingleStepDebugging,
|
||||
} from '../utils';
|
||||
import SingleDebugDrawer from './single-debug-drawer';
|
||||
import { useFormConfigMap } from './use-form-config-map';
|
||||
|
||||
interface IProps {
|
||||
node?: RAGFlowNodeType;
|
||||
singleDebugDrawerVisible: IModalProps<any>['visible'];
|
||||
hideSingleDebugDrawer: IModalProps<any>['hideModal'];
|
||||
showSingleDebugDrawer: IModalProps<any>['showModal'];
|
||||
}
|
||||
|
||||
const EmptyContent = () => <div></div>;
|
||||
|
||||
const FormSheet = ({
|
||||
visible,
|
||||
hideModal,
|
||||
node,
|
||||
singleDebugDrawerVisible,
|
||||
hideSingleDebugDrawer,
|
||||
showSingleDebugDrawer,
|
||||
}: IModalProps<any> & IProps) => {
|
||||
const operatorName: Operator = node?.data.label as Operator;
|
||||
|
||||
const FormConfigMap = useFormConfigMap();
|
||||
|
||||
const currentFormMap = FormConfigMap[operatorName];
|
||||
|
||||
const OperatorForm = currentFormMap.component ?? EmptyContent;
|
||||
|
||||
const form = useForm({
|
||||
defaultValues: currentFormMap.defaultValues,
|
||||
resolver: zodResolver(currentFormMap.schema),
|
||||
});
|
||||
|
||||
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(
|
||||
operatorName,
|
||||
node?.id,
|
||||
form,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible && !form.formState.isDirty) {
|
||||
if (node?.id !== previousId.current) {
|
||||
form.reset();
|
||||
form.clearErrors();
|
||||
}
|
||||
|
||||
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 });
|
||||
form.reset({ ...formData, items });
|
||||
}
|
||||
} else {
|
||||
// form.setFieldsValue(node?.data?.form);
|
||||
form.reset(node?.data?.form);
|
||||
}
|
||||
previousId.current = node?.id;
|
||||
}
|
||||
}, [visible, form, node?.data?.form, node?.id, node, operatorName]);
|
||||
|
||||
return (
|
||||
<Sheet open={visible} modal={false}>
|
||||
<SheetTitle className="hidden"></SheetTitle>
|
||||
<SheetContent className={cn('bg-white top-20 p-0')} closeIcon={false}>
|
||||
<SheetHeader>
|
||||
<section className="flex-col border-b py-2 px-5">
|
||||
<div className="flex items-center gap-2 pb-3">
|
||||
<OperatorIcon
|
||||
name={operatorName}
|
||||
color={operatorMap[operatorName]?.color}
|
||||
></OperatorIcon>
|
||||
<div className="flex items-center gap-1 flex-1">
|
||||
<label htmlFor="">{t('title')}</label>
|
||||
{node?.id === BeginId ? (
|
||||
<span>{t(BeginId)}</span>
|
||||
) : (
|
||||
<Input
|
||||
value={name}
|
||||
onBlur={handleNameBlur}
|
||||
onChange={handleNameChange}
|
||||
></Input>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{needsSingleStepDebugging(operatorName) && (
|
||||
<RunTooltip>
|
||||
<Play
|
||||
className="size-5 cursor-pointer"
|
||||
onClick={showSingleDebugDrawer}
|
||||
/>
|
||||
</RunTooltip>
|
||||
)}
|
||||
<X onClick={hideModal} />
|
||||
</div>
|
||||
<span>{t(`${lowerFirst(operatorName)}Description`)}</span>
|
||||
</section>
|
||||
</SheetHeader>
|
||||
<section className="pt-4">
|
||||
{visible && (
|
||||
<FlowFormContext.Provider value={node}>
|
||||
<OperatorForm
|
||||
onValuesChange={handleValuesChange}
|
||||
form={form}
|
||||
node={node}
|
||||
></OperatorForm>
|
||||
</FlowFormContext.Provider>
|
||||
)}
|
||||
</section>
|
||||
</SheetContent>
|
||||
{singleDebugDrawerVisible && (
|
||||
<SingleDebugDrawer
|
||||
visible={singleDebugDrawerVisible}
|
||||
hideModal={hideSingleDebugDrawer}
|
||||
componentId={node?.id}
|
||||
></SingleDebugDrawer>
|
||||
)}
|
||||
</Sheet>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormSheet;
|
|
@ -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,307 +0,0 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
import { Operator } 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 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 IterationForm from '../form/iteration-from';
|
||||
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/next';
|
||||
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';
|
||||
|
||||
export function useFormConfigMap() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const FormConfigMap = {
|
||||
[Operator.Begin]: {
|
||||
component: BeginForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({
|
||||
name: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: t('common.namePlaceholder'),
|
||||
})
|
||||
.trim(),
|
||||
age: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: t('common.namePlaceholder'),
|
||||
})
|
||||
.trim(),
|
||||
}),
|
||||
},
|
||||
[Operator.Retrieval]: {
|
||||
component: RetrievalForm,
|
||||
defaultValues: { query: [] },
|
||||
schema: z.object({
|
||||
name: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: t('common.namePlaceholder'),
|
||||
})
|
||||
.trim(),
|
||||
age: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: t('common.namePlaceholder'),
|
||||
})
|
||||
.trim(),
|
||||
query: z.array(
|
||||
z.object({
|
||||
type: z.string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
},
|
||||
[Operator.Generate]: {
|
||||
component: GenerateForm,
|
||||
defaultValues: {
|
||||
cite: true,
|
||||
prompt: t('flow.promptText'),
|
||||
},
|
||||
schema: z.object({
|
||||
prompt: z.string().min(1, {
|
||||
message: t('flow.promptMessage'),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
[Operator.Answer]: {
|
||||
component: AnswerForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.Categorize]: {
|
||||
component: CategorizeForm,
|
||||
defaultValues: { message_history_window_size: 1 },
|
||||
schema: z.object({
|
||||
message_history_window_size: z.number(),
|
||||
items: z.array(
|
||||
z.object({
|
||||
name: z.string().min(1, t('flow.nameMessage')).trim(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
},
|
||||
[Operator.Message]: {
|
||||
component: MessageForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.Relevant]: {
|
||||
component: RelevantForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.RewriteQuestion]: {
|
||||
component: RewriteQuestionForm,
|
||||
defaultValues: {
|
||||
message_history_window_size: 6,
|
||||
},
|
||||
schema: z.object({
|
||||
llm_id: z.string(),
|
||||
message_history_window_size: z.number(),
|
||||
language: z.string(),
|
||||
}),
|
||||
},
|
||||
[Operator.Baidu]: {
|
||||
component: BaiduForm,
|
||||
defaultValues: { top_n: 10 },
|
||||
schema: z.object({ top_n: z.number() }),
|
||||
},
|
||||
[Operator.DuckDuckGo]: {
|
||||
component: DuckDuckGoForm,
|
||||
defaultValues: {
|
||||
top_n: 10,
|
||||
channel: 'text',
|
||||
},
|
||||
schema: z.object({
|
||||
top_n: z.number(),
|
||||
}),
|
||||
},
|
||||
[Operator.KeywordExtract]: {
|
||||
component: KeywordExtractForm,
|
||||
defaultValues: { top_n: 3 },
|
||||
schema: z.object({
|
||||
llm_id: z.string(),
|
||||
top_n: z.number(),
|
||||
}),
|
||||
},
|
||||
[Operator.Wikipedia]: {
|
||||
component: WikipediaForm,
|
||||
defaultValues: {
|
||||
top_n: 10,
|
||||
},
|
||||
schema: z.object({
|
||||
language: z.string(),
|
||||
top_n: z.number(),
|
||||
}),
|
||||
},
|
||||
[Operator.PubMed]: {
|
||||
component: PubMedForm,
|
||||
defaultValues: { top_n: 10 },
|
||||
schema: z.object({
|
||||
top_n: z.number(),
|
||||
email: z.string(),
|
||||
}),
|
||||
},
|
||||
[Operator.ArXiv]: {
|
||||
component: ArXivForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.Google]: {
|
||||
component: GoogleForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.Bing]: {
|
||||
component: BingForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.GoogleScholar]: {
|
||||
component: GoogleScholarForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.DeepL]: {
|
||||
component: DeepLForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.GitHub]: {
|
||||
component: GithubForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.BaiduFanyi]: {
|
||||
component: BaiduFanyiForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.QWeather]: {
|
||||
component: QWeatherForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({
|
||||
web_apikey: z.string(),
|
||||
lang: z.string(),
|
||||
type: z.string(),
|
||||
user_type: z.string(),
|
||||
time_period: z.string().optional(),
|
||||
}),
|
||||
},
|
||||
[Operator.ExeSQL]: {
|
||||
component: ExeSQLForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.Switch]: {
|
||||
component: SwitchForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.WenCai]: {
|
||||
component: WenCaiForm,
|
||||
defaultValues: {
|
||||
top_n: 20,
|
||||
},
|
||||
schema: z.object({
|
||||
top_n: z.number(),
|
||||
query_type: z.string(),
|
||||
}),
|
||||
},
|
||||
[Operator.AkShare]: {
|
||||
component: AkShareForm,
|
||||
defaultValues: {
|
||||
top_n: 10,
|
||||
},
|
||||
schema: z.object({
|
||||
top_n: z.number(),
|
||||
}),
|
||||
},
|
||||
[Operator.YahooFinance]: {
|
||||
component: YahooFinanceForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.Jin10]: {
|
||||
component: Jin10Form,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.TuShare]: {
|
||||
component: TuShareForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.Crawler]: {
|
||||
component: CrawlerForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.Invoke]: {
|
||||
component: InvokeForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.Concentrator]: {
|
||||
component: () => <></>,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.Note]: {
|
||||
component: () => <></>,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.Template]: {
|
||||
component: TemplateForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.Email]: {
|
||||
component: EmailForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.Iteration]: {
|
||||
component: IterationForm,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
[Operator.IterationStart]: {
|
||||
component: () => <></>,
|
||||
defaultValues: {},
|
||||
schema: z.object({}),
|
||||
},
|
||||
};
|
||||
|
||||
return FormConfigMap;
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import { TopNFormField } from '@/components/top-n-item';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { DynamicInputVariable } from '../components/next-dynamic-input-variable';
|
||||
|
||||
const AkShareForm = ({ form, node }: INextOperatorForm) => {
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-6"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
||||
<TopNFormField max={99}></TopNFormField>
|
||||
</form>
|
||||
</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,22 +0,0 @@
|
|||
import { TopNFormField } from '@/components/top-n-item';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { DynamicInputVariable } from '../components/next-dynamic-input-variable';
|
||||
|
||||
const BaiduForm = ({ form, node }: INextOperatorForm) => {
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-6"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
||||
<TopNFormField></TopNFormField>
|
||||
</form>
|
||||
</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,265 +0,0 @@
|
|||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from '@/components/ui/collapsible';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { RAGFlowSelect } from '@/components/ui/select';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { useUpdateNodeInternals } from '@xyflow/react';
|
||||
import humanId from 'human-id';
|
||||
import trim from 'lodash/trim';
|
||||
import { ChevronsUpDown, X } from 'lucide-react';
|
||||
import {
|
||||
ChangeEventHandler,
|
||||
FocusEventHandler,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { UseFormReturn, useFieldArray, useFormContext } from 'react-hook-form';
|
||||
import { Operator } from '../../constant';
|
||||
import { useBuildFormSelectOptions } from '../../form-hooks';
|
||||
|
||||
interface IProps {
|
||||
nodeId?: string;
|
||||
}
|
||||
|
||||
interface INameInputProps {
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
otherNames?: string[];
|
||||
validate(error?: string): void;
|
||||
}
|
||||
|
||||
const getOtherFieldValues = (
|
||||
form: UseFormReturn,
|
||||
formListName: string = 'items',
|
||||
index: number,
|
||||
latestField: string,
|
||||
) =>
|
||||
(form.getValues(formListName) ?? [])
|
||||
.map((x: any) => x[latestField])
|
||||
.filter(
|
||||
(x: string) =>
|
||||
x !== form.getValues(`${formListName}.${index}.${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;
|
||||
setName(val);
|
||||
const trimmedVal = trim(val);
|
||||
// trigger validation
|
||||
if (otherNames?.some((x) => x === trimmedVal)) {
|
||||
validate(t('nameRepeatedMsg'));
|
||||
} else if (trimmedVal === '') {
|
||||
validate(t('nameRequiredMsg'));
|
||||
} else {
|
||||
validate('');
|
||||
}
|
||||
},
|
||||
[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, index }: IProps & { index: number }) => {
|
||||
const form = useFormContext();
|
||||
const { t } = useTranslate('flow');
|
||||
const buildCategorizeToOptions = useBuildFormSelectOptions(
|
||||
Operator.Categorize,
|
||||
nodeId,
|
||||
);
|
||||
|
||||
const buildFieldName = useCallback(
|
||||
(name: string) => {
|
||||
return `items.${index}.${name}`;
|
||||
},
|
||||
[index],
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={buildFieldName('name')}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('categoryName')}</FormLabel>
|
||||
<FormControl>
|
||||
<NameInput
|
||||
{...field}
|
||||
otherNames={getOtherFieldValues(form, 'items', index, 'name')}
|
||||
validate={(error?: string) => {
|
||||
const fieldName = buildFieldName('name');
|
||||
if (error) {
|
||||
form.setError(fieldName, { message: error });
|
||||
} else {
|
||||
form.clearErrors(fieldName);
|
||||
}
|
||||
}}
|
||||
></NameInput>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={buildFieldName('description')}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('description')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea {...field} rows={3} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={buildFieldName('examples')}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('examples')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea {...field} rows={3} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={buildFieldName('to')}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('nextStep')}</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
allowClear
|
||||
options={buildCategorizeToOptions(
|
||||
getOtherFieldValues(form, 'items', index, 'to'),
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="index"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>{t('examples')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const DynamicCategorize = ({ nodeId }: IProps) => {
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const form = useFormContext();
|
||||
const { t } = useTranslate('flow');
|
||||
const { fields, remove, append } = useFieldArray({
|
||||
name: 'items',
|
||||
control: form.control,
|
||||
});
|
||||
|
||||
const handleAdd = () => {
|
||||
append({
|
||||
name: humanId(),
|
||||
});
|
||||
if (nodeId) updateNodeInternals(nodeId);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 ">
|
||||
{fields.map((field, index) => (
|
||||
<Collapsible key={field.id}>
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<h4 className="font-bold">
|
||||
{form.getValues(`items.${index}.name`)}
|
||||
</h4>
|
||||
<CollapsibleTrigger asChild>
|
||||
<div className="flex gap-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="w-9 p-0"
|
||||
onClick={() => remove(index)}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" className="w-9 p-0">
|
||||
<ChevronsUpDown className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</Button>
|
||||
</div>
|
||||
</CollapsibleTrigger>
|
||||
</div>
|
||||
<CollapsibleContent>
|
||||
<FormSet nodeId={nodeId} index={index}></FormSet>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
))}
|
||||
|
||||
<Button type={'button'} onClick={handleAdd}>
|
||||
<PlusOutlined />
|
||||
{t('addCategory')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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,26 +0,0 @@
|
|||
import { LargeModelFormField } from '@/components/large-model-form-field';
|
||||
import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { DynamicInputVariable } from '../components/next-dynamic-input-variable';
|
||||
import DynamicCategorize from './dynamic-categorize';
|
||||
|
||||
const CategorizeForm = ({ form, node }: INextOperatorForm) => {
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-6 p-5 overflow-auto max-h-[76vh]"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
||||
<LargeModelFormField></LargeModelFormField>
|
||||
<MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField>
|
||||
<DynamicCategorize nodeId={node?.id}></DynamicCategorize>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategorizeForm;
|
|
@ -1,130 +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 {
|
||||
node?: RAGFlowNodeType;
|
||||
}
|
||||
|
||||
enum VariableType {
|
||||
Reference = 'reference',
|
||||
Input = 'input',
|
||||
}
|
||||
|
||||
const getVariableName = (type: string) =>
|
||||
type === VariableType.Reference ? 'component_id' : 'value';
|
||||
|
||||
const DynamicVariableForm = ({ node }: IProps) => {
|
||||
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(['query', name, 'component_id'], undefined);
|
||||
form.setFieldValue(['query', name, 'value'], undefined);
|
||||
}, 0);
|
||||
},
|
||||
[form],
|
||||
);
|
||||
|
||||
return (
|
||||
<Form.List name="query">
|
||||
{(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(['query', 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 = ({ node }: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<FormCollapse title={t('flow.input')}>
|
||||
<DynamicVariableForm 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,138 +0,0 @@
|
|||
'use client';
|
||||
|
||||
import { SideDown } from '@/assets/icon/Icon';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from '@/components/ui/collapsible';
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { RAGFlowSelect } from '@/components/ui/select';
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query';
|
||||
|
||||
interface IProps {
|
||||
node?: RAGFlowNodeType;
|
||||
}
|
||||
|
||||
enum VariableType {
|
||||
Reference = 'reference',
|
||||
Input = 'input',
|
||||
}
|
||||
|
||||
const getVariableName = (type: string) =>
|
||||
type === VariableType.Reference ? 'component_id' : 'value';
|
||||
|
||||
export function DynamicVariableForm({ node }: IProps) {
|
||||
const { t } = useTranslation();
|
||||
const form = useFormContext();
|
||||
const { fields, remove, append } = useFieldArray({
|
||||
name: 'query',
|
||||
control: form.control,
|
||||
});
|
||||
|
||||
const valueOptions = useBuildComponentIdSelectOptions(
|
||||
node?.id,
|
||||
node?.parentId,
|
||||
);
|
||||
|
||||
const options = [
|
||||
{ value: VariableType.Reference, label: t('flow.reference') },
|
||||
{ value: VariableType.Input, label: t('flow.text') },
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
{fields.map((field, index) => {
|
||||
const typeField = `query.${index}.type`;
|
||||
const typeValue = form.watch(typeField);
|
||||
return (
|
||||
<div key={field.id} className="flex items-center gap-1">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={typeField}
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-2/5">
|
||||
<FormDescription />
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
options={options}
|
||||
onChange={(val) => {
|
||||
field.onChange(val);
|
||||
form.resetField(`query.${index}.value`);
|
||||
form.resetField(`query.${index}.component_id`);
|
||||
}}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`query.${index}.${getVariableName(typeValue)}`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormDescription />
|
||||
<FormControl>
|
||||
{typeValue === VariableType.Reference ? (
|
||||
<RAGFlowSelect
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
{...field}
|
||||
options={valueOptions}
|
||||
></RAGFlowSelect>
|
||||
) : (
|
||||
<Input placeholder={t('common.pleaseInput')} {...field} />
|
||||
)}
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Trash2
|
||||
className="cursor-pointer mx-3 size-4 text-colors-text-functional-danger"
|
||||
onClick={() => remove(index)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<Button onClick={append} className="mt-4" variant={'outline'} size={'sm'}>
|
||||
<Plus />
|
||||
{t('flow.addVariable')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DynamicInputVariable({ node }: IProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Collapsible defaultOpen className="group/collapsible">
|
||||
<CollapsibleTrigger className="flex justify-between w-full pb-2">
|
||||
<span className="font-bold text-2xl text-colors-text-neutral-strong">
|
||||
{t('flow.input')}
|
||||
</span>
|
||||
<Button variant={'icon'} size={'icon'}>
|
||||
<SideDown />
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<DynamicVariableForm node={node}></DynamicVariableForm>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
|
@ -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,52 +0,0 @@
|
|||
import { TopNFormField } from '@/components/top-n-item';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { RAGFlowSelect } from '@/components/ui/select';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useMemo } from 'react';
|
||||
import { Channel } from '../../constant';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { DynamicInputVariable } from '../components/next-dynamic-input-variable';
|
||||
|
||||
const DuckDuckGoForm = ({ form, node }: INextOperatorForm) => {
|
||||
const { t } = useTranslate('flow');
|
||||
|
||||
const options = useMemo(() => {
|
||||
return Object.values(Channel).map((x) => ({ value: x, label: t(x) }));
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-6"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
||||
<TopNFormField></TopNFormField>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="channel"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel tooltip={t('channelTip')}>{t('channel')}</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect {...field} options={options} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</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,78 +0,0 @@
|
|||
import { NextLLMSelect } from '@/components/llm-select';
|
||||
import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
|
||||
import { PromptEditor } from '@/components/prompt-editor';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
|
||||
const GenerateForm = ({ form }: INextOperatorForm) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-6"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="llm_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel tooltip={t('chat.modelTip')}>
|
||||
{t('chat.model')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<NextLLMSelect {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="prompt"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel tooltip={t('knowledgeConfiguration.promptTip')}>
|
||||
{t('flow.systemPrompt')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<PromptEditor {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cite"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel tooltip={t('flow.citeTip')}>
|
||||
{t('flow.cite')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Switch {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField>
|
||||
</form>
|
||||
</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 { 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, RAGFlowNodeType } 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,78 +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>
|
||||
<DynamicVariablesForm node={node}></DynamicVariablesForm>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default InvokeForm;
|
|
@ -1,94 +0,0 @@
|
|||
import { CommaIcon, SemicolonIcon } from '@/assets/icon/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,48 +0,0 @@
|
|||
import { NextLLMSelect } from '@/components/llm-select';
|
||||
import { TopNFormField } from '@/components/top-n-item';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { DynamicInputVariable } from '../components/next-dynamic-input-variable';
|
||||
|
||||
const KeywordExtractForm = ({ form, node }: INextOperatorForm) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-6"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="llm_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel tooltip={t('chat.modelTip')}>
|
||||
{t('chat.model')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<NextLLMSelect {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<TopNFormField></TopNFormField>
|
||||
</form>
|
||||
</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,82 +0,0 @@
|
|||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { PlusCircle, Trash2 } from 'lucide-react';
|
||||
import { useFieldArray } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
|
||||
const MessageForm = ({ form }: INextOperatorForm) => {
|
||||
const { t } = useTranslation();
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
name: 'messages',
|
||||
control: form.control,
|
||||
});
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-6"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.msg')}</FormLabel>
|
||||
<div className="space-y-4">
|
||||
{fields.map((field, index) => (
|
||||
<div key={field.id} className="flex items-start gap-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`messages.${index}`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
placeholder={t('flow.messagePlaceholder')}
|
||||
rows={5}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{fields.length > 1 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
type="button"
|
||||
onClick={() => remove(index)}
|
||||
className="cursor-pointer text-colors-text-functional-danger"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => append(' ')} // "" will cause the inability to add, refer to: https://github.com/orgs/react-hook-form/discussions/8485#discussioncomment-2961861
|
||||
className="w-full mt-4"
|
||||
>
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
{t('flow.addMessage')}
|
||||
</Button>
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageForm;
|
|
@ -1,46 +0,0 @@
|
|||
import { TopNFormField } from '@/components/top-n-item';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { DynamicInputVariable } from '../components/next-dynamic-input-variable';
|
||||
|
||||
const PubMedForm = ({ form, node }: INextOperatorForm) => {
|
||||
const { t } = useTranslate('flow');
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-6"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
||||
<TopNFormField></TopNFormField>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel tooltip={t('emailTip')}>{t('email')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default PubMedForm;
|
|
@ -1,157 +0,0 @@
|
|||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { RAGFlowSelect } from '@/components/ui/select';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
QWeatherLangOptions,
|
||||
QWeatherTimePeriodOptions,
|
||||
QWeatherTypeOptions,
|
||||
QWeatherUserTypeOptions,
|
||||
} from '../../constant';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { DynamicInputVariable } from '../components/next-dynamic-input-variable';
|
||||
|
||||
enum FormFieldName {
|
||||
Type = 'type',
|
||||
UserType = 'user_type',
|
||||
}
|
||||
|
||||
const QWeatherForm = ({ form, node }: INextOperatorForm) => {
|
||||
const { t } = useTranslation();
|
||||
const typeValue = form.watch(FormFieldName.Type);
|
||||
|
||||
const qWeatherLangOptions = useMemo(() => {
|
||||
return QWeatherLangOptions.map((x) => ({
|
||||
value: x,
|
||||
label: t(`flow.qWeatherLangOptions.${x}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
const qWeatherTypeOptions = useMemo(() => {
|
||||
return QWeatherTypeOptions.map((x) => ({
|
||||
value: x,
|
||||
label: t(`flow.qWeatherTypeOptions.${x}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
const qWeatherUserTypeOptions = useMemo(() => {
|
||||
return QWeatherUserTypeOptions.map((x) => ({
|
||||
value: x,
|
||||
label: t(`flow.qWeatherUserTypeOptions.${x}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
const getQWeatherTimePeriodOptions = useCallback(() => {
|
||||
let options = QWeatherTimePeriodOptions;
|
||||
const userType = form.getValues(FormFieldName.UserType);
|
||||
if (userType === 'free') {
|
||||
options = options.slice(0, 3);
|
||||
}
|
||||
return options.map((x) => ({
|
||||
value: x,
|
||||
label: t(`flow.qWeatherTimePeriodOptions.${x}`),
|
||||
}));
|
||||
}, [form, t]);
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-6"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="web_apikey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.webApiKey')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="lang"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.lang')}</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
options={qWeatherLangOptions}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={FormFieldName.Type}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.type')}</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
options={qWeatherTypeOptions}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={FormFieldName.UserType}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.userType')}</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
options={qWeatherUserTypeOptions}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{typeValue === 'weather' && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={'time_period'}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.timePeriod')}</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
options={getQWeatherTimePeriodOptions()}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default QWeatherForm;
|
|
@ -1,53 +0,0 @@
|
|||
import { Edge } from '@xyflow/react';
|
||||
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;
|
|
@ -1,54 +0,0 @@
|
|||
import KnowledgeBaseItem from '@/components/knowledge-base-item';
|
||||
import Rerank from '@/components/rerank';
|
||||
import SimilaritySlider from '@/components/similarity-slider';
|
||||
import TopNItem from '@/components/top-n-item';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import type { FormProps } from 'antd';
|
||||
import { Form, Input } from 'antd';
|
||||
import { IOperatorForm } from '../../interface';
|
||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
||||
|
||||
type FieldType = {
|
||||
top_n?: number;
|
||||
};
|
||||
|
||||
const onFinish: FormProps<FieldType>['onFinish'] = (values) => {
|
||||
console.log('Success:', values);
|
||||
};
|
||||
|
||||
const onFinishFailed: FormProps<FieldType>['onFinishFailed'] = (errorInfo) => {
|
||||
console.log('Failed:', errorInfo);
|
||||
};
|
||||
|
||||
const RetrievalForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
||||
const { t } = useTranslate('flow');
|
||||
return (
|
||||
<Form
|
||||
name="basic"
|
||||
onFinish={onFinish}
|
||||
onFinishFailed={onFinishFailed}
|
||||
autoComplete="off"
|
||||
onValuesChange={onValuesChange}
|
||||
form={form}
|
||||
layout={'vertical'}
|
||||
>
|
||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
||||
<SimilaritySlider
|
||||
isTooltipShown
|
||||
vectorSimilarityWeightName="keywords_similarity_weight"
|
||||
></SimilaritySlider>
|
||||
<TopNItem></TopNItem>
|
||||
<Rerank></Rerank>
|
||||
<KnowledgeBaseItem></KnowledgeBaseItem>
|
||||
<Form.Item
|
||||
name={'empty_response'}
|
||||
label={t('emptyResponse', { keyPrefix: 'chat' })}
|
||||
tooltip={t('emptyResponseTip', { keyPrefix: 'chat' })}
|
||||
>
|
||||
<Input.TextArea placeholder="" rows={4} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default RetrievalForm;
|
|
@ -1,59 +0,0 @@
|
|||
import { KnowledgeBaseFormField } from '@/components/knowledge-base-item';
|
||||
import { RerankFormFields } from '@/components/rerank';
|
||||
import { SimilaritySliderFormField } from '@/components/similarity-slider';
|
||||
import { TopNFormField } from '@/components/top-n-item';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { DynamicInputVariable } from '../components/next-dynamic-input-variable';
|
||||
|
||||
const RetrievalForm = ({ form, node }: INextOperatorForm) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-6"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
||||
<SimilaritySliderFormField
|
||||
vectorSimilarityWeightName="keywords_similarity_weight"
|
||||
isTooltipShown
|
||||
></SimilaritySliderFormField>
|
||||
<TopNFormField></TopNFormField>
|
||||
<RerankFormFields></RerankFormFields>
|
||||
<KnowledgeBaseFormField></KnowledgeBaseFormField>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="empty_response"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('chat.emptyResponse')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t('common.namePlaceholder')}
|
||||
{...field}
|
||||
autoComplete="off"
|
||||
rows={4}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default RetrievalForm;
|
|
@ -1,68 +0,0 @@
|
|||
import { NextLLMSelect } from '@/components/llm-select';
|
||||
import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { RAGFlowSelect } from '@/components/ui/select';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { GoogleLanguageOptions } from '../../constant';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
|
||||
const RewriteQuestionForm = ({ form }: INextOperatorForm) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-6"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="llm_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel tooltip={t('chat.modelTip')}>
|
||||
{t('chat.model')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<NextLLMSelect {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="language"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel tooltip={t('chat.languageTip')}>
|
||||
{t('chat.language')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
options={GoogleLanguageOptions}
|
||||
allowClear={true}
|
||||
{...field}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default RewriteQuestionForm;
|
|
@ -1,21 +0,0 @@
|
|||
@lightBackgroundColor: rgba(150, 150, 150, 0.07);
|
||||
@darkBackgroundColor: rgba(150, 150, 150, 0.12);
|
||||
|
||||
.caseCard {
|
||||
background-color: @lightBackgroundColor;
|
||||
}
|
||||
|
||||
.conditionCard {
|
||||
background-color: @darkBackgroundColor;
|
||||
}
|
||||
|
||||
.elseCase {
|
||||
background-color: @lightBackgroundColor;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.addButton {
|
||||
color: rgb(22, 119, 255);
|
||||
font-weight: 600;
|
||||
}
|
|
@ -1,204 +0,0 @@
|
|||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import { Button, Card, Divider, Form, Input, Select } from 'antd';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Operator,
|
||||
SwitchElseTo,
|
||||
SwitchLogicOperatorOptions,
|
||||
SwitchOperatorOptions,
|
||||
} from '../../constant';
|
||||
import { useBuildFormSelectOptions } from '../../form-hooks';
|
||||
import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query';
|
||||
import { IOperatorForm } from '../../interface';
|
||||
import { getOtherFieldValues } from '../../utils';
|
||||
|
||||
import { ISwitchForm } from '@/interfaces/database/flow';
|
||||
import styles from './index.less';
|
||||
|
||||
const SwitchForm = ({ onValuesChange, node, form }: IOperatorForm) => {
|
||||
const { t } = useTranslation();
|
||||
const buildCategorizeToOptions = useBuildFormSelectOptions(
|
||||
Operator.Switch,
|
||||
node?.id,
|
||||
);
|
||||
|
||||
const getSelectedConditionTos = () => {
|
||||
const conditions: ISwitchForm['conditions'] =
|
||||
form?.getFieldValue('conditions');
|
||||
|
||||
return conditions?.filter((x) => !!x).map((x) => x?.to) ?? [];
|
||||
};
|
||||
|
||||
const switchOperatorOptions = useMemo(() => {
|
||||
return SwitchOperatorOptions.map((x) => ({
|
||||
value: x.value,
|
||||
label: t(`flow.switchOperatorOptions.${x.label}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
const switchLogicOperatorOptions = useMemo(() => {
|
||||
return SwitchLogicOperatorOptions.map((x) => ({
|
||||
value: x,
|
||||
label: t(`flow.switchLogicOperatorOptions.${x}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
const componentIdOptions = useBuildComponentIdSelectOptions(
|
||||
node?.id,
|
||||
node?.parentId,
|
||||
);
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
name="dynamic_form_complex"
|
||||
autoComplete="off"
|
||||
initialValues={{ conditions: [{}] }}
|
||||
onValuesChange={onValuesChange}
|
||||
layout={'vertical'}
|
||||
>
|
||||
<Form.List name="conditions">
|
||||
{(fields, { add, remove }) => (
|
||||
<div style={{ display: 'flex', rowGap: 16, flexDirection: 'column' }}>
|
||||
{fields.map((field) => {
|
||||
return (
|
||||
<Card
|
||||
size="small"
|
||||
title={`Case ${field.name + 1}`}
|
||||
key={field.key}
|
||||
className={styles.caseCard}
|
||||
extra={
|
||||
<CloseOutlined
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Form.Item noStyle dependencies={[field.name, 'items']}>
|
||||
{({ getFieldValue }) =>
|
||||
getFieldValue(['conditions', field.name, 'items'])
|
||||
?.length > 1 && (
|
||||
<Form.Item
|
||||
label={t('flow.logicalOperator')}
|
||||
name={[field.name, 'logical_operator']}
|
||||
>
|
||||
<Select options={switchLogicOperatorOptions} />
|
||||
</Form.Item>
|
||||
)
|
||||
}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('flow.nextStep')}
|
||||
name={[field.name, 'to']}
|
||||
>
|
||||
<Select
|
||||
allowClear
|
||||
options={buildCategorizeToOptions([
|
||||
form?.getFieldValue(SwitchElseTo),
|
||||
...getOtherFieldValues(
|
||||
form!,
|
||||
'conditions',
|
||||
field,
|
||||
'to',
|
||||
),
|
||||
])}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="Condition">
|
||||
<Form.List name={[field.name, 'items']}>
|
||||
{(subFields, subOpt) => (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
rowGap: 16,
|
||||
}}
|
||||
>
|
||||
{subFields.map((subField) => (
|
||||
<Card
|
||||
key={subField.key}
|
||||
title={null}
|
||||
size="small"
|
||||
className={styles.conditionCard}
|
||||
bordered
|
||||
extra={
|
||||
<CloseOutlined
|
||||
onClick={() => {
|
||||
subOpt.remove(subField.name);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Form.Item
|
||||
label={t('flow.componentId')}
|
||||
name={[subField.name, 'cpn_id']}
|
||||
>
|
||||
<Select
|
||||
placeholder={t('flow.componentId')}
|
||||
options={componentIdOptions}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('flow.operator')}
|
||||
name={[subField.name, 'operator']}
|
||||
>
|
||||
<Select
|
||||
placeholder={t('flow.operator')}
|
||||
options={switchOperatorOptions}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('flow.value')}
|
||||
name={[subField.name, 'value']}
|
||||
>
|
||||
<Input placeholder={t('flow.value')} />
|
||||
</Form.Item>
|
||||
</Card>
|
||||
))}
|
||||
<Button
|
||||
onClick={() => {
|
||||
form?.setFieldValue(
|
||||
['conditions', field.name, 'logical_operator'],
|
||||
SwitchLogicOperatorOptions[0],
|
||||
);
|
||||
subOpt.add({
|
||||
operator: SwitchOperatorOptions[0].value,
|
||||
});
|
||||
}}
|
||||
block
|
||||
className={styles.addButton}
|
||||
>
|
||||
+ Add Condition
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form.Item>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
|
||||
<Button onClick={() => add()} block className={styles.addButton}>
|
||||
+ Add Case
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Form.List>
|
||||
<Divider />
|
||||
<Form.Item
|
||||
label={'ELSE'}
|
||||
name={[SwitchElseTo]}
|
||||
className={styles.elseCase}
|
||||
>
|
||||
<Select
|
||||
allowClear
|
||||
options={buildCategorizeToOptions(getSelectedConditionTos())}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default SwitchForm;
|
|
@ -1,24 +0,0 @@
|
|||
import { PromptEditor } from '@/components/prompt-editor';
|
||||
import { Form } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IOperatorForm } from '../../interface';
|
||||
|
||||
const TemplateForm = ({ onValuesChange, form }: IOperatorForm) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Form
|
||||
name="basic"
|
||||
autoComplete="off"
|
||||
form={form}
|
||||
onValuesChange={onValuesChange}
|
||||
layout={'vertical'}
|
||||
>
|
||||
<Form.Item name={['content']} label={t('flow.content')}>
|
||||
<PromptEditor></PromptEditor>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default TemplateForm;
|
|
@ -1,83 +0,0 @@
|
|||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { DatePicker, DatePickerProps, Form, Input, Select } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { TuShareSrcOptions } from '../../constant';
|
||||
import { IOperatorForm } from '../../interface';
|
||||
import DynamicInputVariable from '../components/dynamic-input-variable';
|
||||
|
||||
const DateTimePicker = ({
|
||||
onChange,
|
||||
value,
|
||||
}: {
|
||||
onChange?: (val: number | undefined) => void;
|
||||
value?: number | undefined;
|
||||
}) => {
|
||||
const handleChange: DatePickerProps['onChange'] = useCallback(
|
||||
(val: any) => {
|
||||
const nextVal = val?.format('YYYY-MM-DD HH:mm:ss');
|
||||
onChange?.(nextVal ? nextVal : undefined);
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
// The value needs to be converted into a string and saved to the backend
|
||||
const nextValue = useMemo(() => {
|
||||
if (value) {
|
||||
return dayjs(value);
|
||||
}
|
||||
return undefined;
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<DatePicker
|
||||
showTime
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
onChange={handleChange}
|
||||
value={nextValue}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TuShareForm = ({ onValuesChange, form, node }: IOperatorForm) => {
|
||||
const { t } = useTranslate('flow');
|
||||
|
||||
const tuShareSrcOptions = useMemo(() => {
|
||||
return TuShareSrcOptions.map((x) => ({
|
||||
value: x,
|
||||
label: t(`tuShareSrcOptions.${x}`),
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<Form
|
||||
name="basic"
|
||||
autoComplete="off"
|
||||
form={form}
|
||||
onValuesChange={onValuesChange}
|
||||
layout={'vertical'}
|
||||
>
|
||||
<DynamicInputVariable node={node}></DynamicInputVariable>
|
||||
<Form.Item
|
||||
label={t('token')}
|
||||
name={'token'}
|
||||
tooltip={'Get from https://tushare.pro/'}
|
||||
>
|
||||
<Input></Input>
|
||||
</Form.Item>
|
||||
<Form.Item label={t('src')} name={'src'}>
|
||||
<Select options={tuShareSrcOptions}></Select>
|
||||
</Form.Item>
|
||||
<Form.Item label={t('startDate')} name={'start_date'}>
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('endDate')} name={'end_date'}>
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('keyword')} name={'keyword'}>
|
||||
<Input></Input>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default TuShareForm;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue