mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Feat: Supports to debug single component in Agent. #3993 Fix: The github button on the login page is displayed incorrectly #4002 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -5,6 +5,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { get } from 'lodash';
|
||||
import { FolderInput, FolderOutput } from 'lucide-react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import ReactFlow, {
|
||||
@ -24,6 +25,7 @@ import {
|
||||
useHandleExportOrImportJsonFile,
|
||||
useSelectCanvasData,
|
||||
useShowFormDrawer,
|
||||
useShowSingleDebugDrawer,
|
||||
useValidateConnection,
|
||||
useWatchNodeFormDataChange,
|
||||
} from '../hooks';
|
||||
@ -95,6 +97,11 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
showModal: showChatModal,
|
||||
hideModal: hideChatModal,
|
||||
} = useSetModalState();
|
||||
const {
|
||||
singleDebugDrawerVisible,
|
||||
showSingleDebugDrawer,
|
||||
hideSingleDebugDrawer,
|
||||
} = useShowSingleDebugDrawer();
|
||||
|
||||
const { formDrawerVisible, hideFormDrawer, showFormDrawer, clickedNode } =
|
||||
useShowFormDrawer();
|
||||
@ -116,11 +123,24 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
const onNodeClick: NodeMouseHandler = useCallback(
|
||||
(e, node) => {
|
||||
if (node.data.label !== Operator.Note) {
|
||||
hideSingleDebugDrawer();
|
||||
hideRunOrChatDrawer();
|
||||
showFormDrawer(node);
|
||||
}
|
||||
// handle single debug icon click
|
||||
if (
|
||||
get(e.target, 'dataset.play') === 'true' ||
|
||||
get(e.target, 'parentNode.dataset.play') === 'true'
|
||||
) {
|
||||
showSingleDebugDrawer();
|
||||
}
|
||||
},
|
||||
[hideRunOrChatDrawer, showFormDrawer],
|
||||
[
|
||||
hideRunOrChatDrawer,
|
||||
hideSingleDebugDrawer,
|
||||
showFormDrawer,
|
||||
showSingleDebugDrawer,
|
||||
],
|
||||
);
|
||||
|
||||
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
||||
@ -193,12 +213,6 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
onSelectionChange={onSelectionChange}
|
||||
nodeOrigin={[0.5, 0]}
|
||||
isValidConnection={isValidConnection}
|
||||
onChangeCapture={(...params) => {
|
||||
console.info('onChangeCapture:', ...params);
|
||||
}}
|
||||
onChange={(...params) => {
|
||||
console.info('params:', ...params);
|
||||
}}
|
||||
defaultEdgeOptions={{
|
||||
type: 'buttonEdge',
|
||||
markerEnd: 'logo',
|
||||
@ -214,7 +228,7 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
<ControlButton onClick={handleImportJson}>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<TooltipTrigger asChild>
|
||||
<FolderInput />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Import</TooltipContent>
|
||||
@ -224,7 +238,7 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
<ControlButton onClick={handleExportJson}>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<TooltipTrigger asChild>
|
||||
<FolderOutput />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Export</TooltipContent>
|
||||
@ -238,6 +252,9 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
node={clickedNode}
|
||||
visible={formDrawerVisible}
|
||||
hideModal={hideFormDrawer}
|
||||
singleDebugDrawerVisible={singleDebugDrawerVisible}
|
||||
hideSingleDebugDrawer={hideSingleDebugDrawer}
|
||||
showSingleDebugDrawer={showSingleDebugDrawer}
|
||||
></FormDrawer>
|
||||
)}
|
||||
{chatVisible && (
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
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 { useTranslate } from '@/hooks/common-hooks';
|
||||
import styles from './index.less';
|
||||
import { NextNodePopover } from './popover';
|
||||
|
||||
import { RunTooltip } from '../../flow-tooltip';
|
||||
import styles from './index.less';
|
||||
interface IProps {
|
||||
id: string;
|
||||
label: string;
|
||||
@ -15,12 +17,17 @@ interface IProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function RunStatus({ id, name }: Omit<IProps, 'label'>) {
|
||||
export function RunStatus({ id, name, label }: IProps) {
|
||||
const { t } = useTranslate('flow');
|
||||
return (
|
||||
<section className="flex justify-end items-center pb-1 ">
|
||||
<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="text-blue-600 cursor-pointer text-[10px]">
|
||||
<span className="cursor-pointer text-[10px]">
|
||||
{t('operationResults')}
|
||||
</span>
|
||||
</NextNodePopover>
|
||||
@ -30,8 +37,10 @@ export function RunStatus({ id, name }: Omit<IProps, 'label'>) {
|
||||
|
||||
const NodeHeader = ({ label, id, name, gap = 4, className }: IProps) => {
|
||||
return (
|
||||
<section className="haha">
|
||||
{label !== Operator.Answer && <RunStatus id={id} name={name}></RunStatus>}
|
||||
<section>
|
||||
{label !== Operator.Answer && (
|
||||
<RunStatus id={id} name={name} label={label}></RunStatus>
|
||||
)}
|
||||
<Flex
|
||||
flex={1}
|
||||
align="center"
|
||||
|
||||
@ -2931,3 +2931,13 @@ export const BeginQueryTypeIconMap = {
|
||||
[BeginQueryType.Integer]: ListOrdered,
|
||||
[BeginQueryType.Boolean]: ToggleLeft,
|
||||
};
|
||||
|
||||
export const NoDebugOperatorsList = [
|
||||
Operator.Begin,
|
||||
Operator.Answer,
|
||||
Operator.Concentrator,
|
||||
Operator.Template,
|
||||
Operator.Message,
|
||||
Operator.RewriteQuestion,
|
||||
Operator.Switch,
|
||||
];
|
||||
|
||||
5
web/src/pages/flow/debug-content/index.less
Normal file
5
web/src/pages/flow/debug-content/index.less
Normal file
@ -0,0 +1,5 @@
|
||||
.formWrapper {
|
||||
:global(.ant-form-item-label) {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
238
web/src/pages/flow/debug-content/index.tsx
Normal file
238
web/src/pages/flow/debug-content/index.tsx
Normal file
@ -0,0 +1,238 @@
|
||||
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;
|
||||
74
web/src/pages/flow/debug-content/popover-form.tsx
Normal file
74
web/src/pages/flow/debug-content/popover-form.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@ -13,3 +13,9 @@
|
||||
padding-top: 16px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.formDrawer {
|
||||
:global(.ant-drawer-content-wrapper) {
|
||||
transform: translateX(0) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import { Drawer, Flex, Form, Input } from 'antd';
|
||||
import { lowerFirst } from 'lodash';
|
||||
import { Play } from 'lucide-react';
|
||||
import { useEffect } from 'react';
|
||||
import { Node } from 'reactflow';
|
||||
import { Operator, operatorMap } from '../constant';
|
||||
@ -15,6 +18,7 @@ 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';
|
||||
@ -30,22 +34,24 @@ import RelevantForm from '../form/relevant-form';
|
||||
import RetrievalForm from '../form/retrieval-form';
|
||||
import RewriteQuestionForm from '../form/rewrite-question-form';
|
||||
import SwitchForm from '../form/switch-form';
|
||||
import TemplateForm from '../form/template-form';
|
||||
import TuShareForm from '../form/tushare-form';
|
||||
import WenCaiForm from '../form/wencai-form';
|
||||
import WikipediaForm from '../form/wikipedia-form';
|
||||
import YahooFinanceForm from '../form/yahoo-finance-form';
|
||||
import { useHandleFormValuesChange, useHandleNodeNameChange } from '../hooks';
|
||||
import OperatorIcon from '../operator-icon';
|
||||
import { getDrawerWidth, needsSingleStepDebugging } from '../utils';
|
||||
import SingleDebugDrawer from './single-debug-drawer';
|
||||
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import { lowerFirst } from 'lodash';
|
||||
import EmailForm from '../form/email-form';
|
||||
import TemplateForm from '../form/template-form';
|
||||
import { getDrawerWidth } from '../utils';
|
||||
import { RunTooltip } from '../flow-tooltip';
|
||||
import styles from './index.less';
|
||||
|
||||
interface IProps {
|
||||
node?: Node;
|
||||
singleDebugDrawerVisible: IModalProps<any>['visible'];
|
||||
hideSingleDebugDrawer: IModalProps<any>['hideModal'];
|
||||
showSingleDebugDrawer: IModalProps<any>['showModal'];
|
||||
}
|
||||
|
||||
const FormMap = {
|
||||
@ -91,6 +97,9 @@ const FormDrawer = ({
|
||||
visible,
|
||||
hideModal,
|
||||
node,
|
||||
singleDebugDrawerVisible,
|
||||
hideSingleDebugDrawer,
|
||||
showSingleDebugDrawer,
|
||||
}: IModalProps<any> & IProps) => {
|
||||
const operatorName: Operator = node?.data.label;
|
||||
const OperatorForm = FormMap[operatorName] ?? EmptyContent;
|
||||
@ -99,12 +108,14 @@ const FormDrawer = ({
|
||||
id: node?.id,
|
||||
data: node?.data,
|
||||
});
|
||||
|
||||
const { t } = useTranslate('flow');
|
||||
|
||||
const { handleValuesChange } = useHandleFormValuesChange(node?.id);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
form.resetFields();
|
||||
form.setFieldsValue(node?.data?.form);
|
||||
}
|
||||
}, [visible, form, node?.data?.form]);
|
||||
@ -128,6 +139,14 @@ const FormDrawer = ({
|
||||
onChange={handleNameChange}
|
||||
></Input>
|
||||
</Flex>
|
||||
{needsSingleStepDebugging(operatorName) && (
|
||||
<RunTooltip>
|
||||
<Play
|
||||
className="size-5 cursor-pointer"
|
||||
onClick={showSingleDebugDrawer}
|
||||
/>
|
||||
</RunTooltip>
|
||||
)}
|
||||
<CloseOutlined onClick={hideModal} />
|
||||
</Flex>
|
||||
<span className={styles.operatorDescription}>
|
||||
@ -142,6 +161,7 @@ const FormDrawer = ({
|
||||
mask={false}
|
||||
width={getDrawerWidth()}
|
||||
closeIcon={null}
|
||||
rootClassName={styles.formDrawer}
|
||||
>
|
||||
<section className={styles.formWrapper}>
|
||||
{visible && (
|
||||
@ -152,6 +172,13 @@ const FormDrawer = ({
|
||||
></OperatorForm>
|
||||
)}
|
||||
</section>
|
||||
{singleDebugDrawerVisible && (
|
||||
<SingleDebugDrawer
|
||||
visible={singleDebugDrawerVisible}
|
||||
hideModal={hideSingleDebugDrawer}
|
||||
componentId={node?.id}
|
||||
></SingleDebugDrawer>
|
||||
)}
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
81
web/src/pages/flow/flow-drawer/single-debug-drawer/index.tsx
Normal file
81
web/src/pages/flow/flow-drawer/single-debug-drawer/index.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
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;
|
||||
22
web/src/pages/flow/flow-tooltip.tsx
Normal file
22
web/src/pages/flow/flow-tooltip.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const RunTooltip = ({ children }: PropsWithChildren) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>{children}</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{t('flow.testRun')}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
@ -1,9 +1,11 @@
|
||||
import Editor from '@monaco-editor/react';
|
||||
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',
|
||||
|
||||
@ -620,7 +620,6 @@ export const useWatchNodeFormDataChange = () => {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
console.info('xxx');
|
||||
nodes.forEach((node) => {
|
||||
const currentNode = getNode(node.id);
|
||||
const form = currentNode?.data.form ?? {};
|
||||
@ -856,3 +855,21 @@ export const useHandleExportOrImportJsonFile = () => {
|
||||
onFileUploadOk,
|
||||
};
|
||||
};
|
||||
|
||||
export const useShowSingleDebugDrawer = () => {
|
||||
const { visible, showModal, hideModal } = useSetModalState();
|
||||
const { saveGraph } = useSaveGraph();
|
||||
|
||||
const showSingleDebugDrawer = useCallback(async () => {
|
||||
const saveRet = await saveGraph();
|
||||
if (saveRet?.code === 0) {
|
||||
showModal();
|
||||
}
|
||||
}, [saveGraph, showModal]);
|
||||
|
||||
return {
|
||||
singleDebugDrawerVisible: visible,
|
||||
hideSingleDebugDrawer: hideModal,
|
||||
showSingleDebugDrawer,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,26 +1,7 @@
|
||||
import { Authorization } from '@/constants/authorization';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
|
||||
import { useHandleSubmittable } from '@/hooks/login-hooks';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import api from '@/utils/api';
|
||||
import { getAuthorization } from '@/utils/authorization-util';
|
||||
import { UploadOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Drawer,
|
||||
Form,
|
||||
FormItemProps,
|
||||
Input,
|
||||
InputNumber,
|
||||
Select,
|
||||
Switch,
|
||||
Upload,
|
||||
} from 'antd';
|
||||
import { pick } from 'lodash';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { Drawer } from 'antd';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { BeginQueryType } from '../constant';
|
||||
import {
|
||||
useGetBeginNodeDataQuery,
|
||||
useSaveGraphBeforeOpeningDebugDrawer,
|
||||
@ -28,152 +9,23 @@ import {
|
||||
import { BeginQuery } from '../interface';
|
||||
import useGraphStore from '../store';
|
||||
import { getDrawerWidth } from '../utils';
|
||||
import { PopoverForm } from './popover-form';
|
||||
|
||||
import { UploadChangeParam, UploadFile } from 'antd/es/upload';
|
||||
import { Link } from 'lucide-react';
|
||||
import styles from './index.less';
|
||||
import DebugContent from '../debug-content';
|
||||
|
||||
const RunDrawer = ({
|
||||
hideModal,
|
||||
showModal: showChatModal,
|
||||
}: IModalProps<any>) => {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
||||
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 getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
||||
const query: BeginQuery[] = getBeginNodeDataQuery();
|
||||
|
||||
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 { handleRun, loading } = useSaveGraphBeforeOpeningDebugDrawer(
|
||||
showChatModal!,
|
||||
);
|
||||
|
||||
const renderWidget = useCallback(
|
||||
(q: BeginQuery, idx: number) => {
|
||||
const props: FormItemProps & { key: number } = {
|
||||
key: idx,
|
||||
label: q.name,
|
||||
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={4}></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} 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];
|
||||
},
|
||||
[form, handleShowPopover, onChange, switchVisible, t, visible],
|
||||
);
|
||||
|
||||
const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatModal!);
|
||||
|
||||
const handleRunAgent = useCallback(
|
||||
(nextValues: Record<string, any>) => {
|
||||
const currentNodes = updateNodeForm('begin', nextValues, ['query']);
|
||||
@ -183,25 +35,12 @@ const RunDrawer = ({
|
||||
[handleRun, hideModal, updateNodeForm],
|
||||
);
|
||||
|
||||
const onOk = useCallback(async () => {
|
||||
const values = await form.validateFields();
|
||||
const nextValues = Object.entries(values).map(([key, value]) => {
|
||||
const item = query[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 };
|
||||
});
|
||||
handleRunAgent(nextValues);
|
||||
}, [form, handleRunAgent, query]);
|
||||
const onOk = useCallback(
|
||||
async (nextValues: any[]) => {
|
||||
handleRunAgent(nextValues);
|
||||
},
|
||||
[handleRunAgent],
|
||||
);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
@ -213,39 +52,11 @@ const RunDrawer = ({
|
||||
width={getDrawerWidth()}
|
||||
mask={false}
|
||||
>
|
||||
<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}
|
||||
>
|
||||
{query.map((x, idx) => {
|
||||
return renderWidget(x, idx);
|
||||
})}
|
||||
</Form>
|
||||
</Form.Provider>
|
||||
</section>
|
||||
<Button
|
||||
type={'primary'}
|
||||
block
|
||||
onClick={onOk}
|
||||
disabled={!submittable || isUploading}
|
||||
>
|
||||
{t('common.next')}
|
||||
</Button>
|
||||
<DebugContent
|
||||
ok={onOk}
|
||||
parameters={query}
|
||||
loading={loading}
|
||||
></DebugContent>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -7,7 +7,12 @@ import pipe from 'lodash/fp/pipe';
|
||||
import isObject from 'lodash/isObject';
|
||||
import { Edge, Node, Position } from 'reactflow';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { CategorizeAnchorPointPositions, NodeMap, Operator } from './constant';
|
||||
import {
|
||||
CategorizeAnchorPointPositions,
|
||||
NoDebugOperatorsList,
|
||||
NodeMap,
|
||||
Operator,
|
||||
} from './constant';
|
||||
import { ICategorizeItemResult, IPosition, NodeData } from './interface';
|
||||
|
||||
const buildEdges = (
|
||||
@ -124,7 +129,7 @@ export const buildDslComponentsByGraph = (
|
||||
const components: DSLComponents = {};
|
||||
|
||||
nodes
|
||||
.filter((x) => x.data.label !== Operator.Note)
|
||||
?.filter((x) => x.data.label !== Operator.Note)
|
||||
.forEach((x) => {
|
||||
const id = x.id;
|
||||
const operatorName = x.data.label;
|
||||
@ -323,3 +328,7 @@ export const duplicateNodeForm = (nodeData?: NodeData) => {
|
||||
export const getDrawerWidth = () => {
|
||||
return window.innerWidth > 1278 ? '40%' : 470;
|
||||
};
|
||||
|
||||
export const needsSingleStepDebugging = (label: string) => {
|
||||
return !NoDebugOperatorsList.some((x) => (label as Operator) === x);
|
||||
};
|
||||
|
||||
@ -168,7 +168,7 @@ const Login = () => {
|
||||
onClick={toGoogle}
|
||||
style={{ marginTop: 15 }}
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center">
|
||||
<Icon
|
||||
icon="local:github"
|
||||
style={{ verticalAlign: 'middle', marginRight: 5 }}
|
||||
|
||||
Reference in New Issue
Block a user