balibabu commited on
Commit
b9c5b85
·
1 Parent(s): de65b37

Feat: Supports to debug single component in Agent. #3993 (#4007)

Browse files

### 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)

web/src/hooks/flow-hooks.ts CHANGED
@@ -1,5 +1,6 @@
1
  import { ResponseType } from '@/interfaces/database/base';
2
  import { DSL, IFlow, IFlowTemplate } from '@/interfaces/database/flow';
 
3
  import i18n from '@/locales/config';
4
  import flowService from '@/services/flow-service';
5
  import { buildMessageListWithUuid } from '@/utils/chat';
@@ -220,3 +221,50 @@ export const useTestDbConnect = () => {
220
 
221
  return { data, loading, testDbConnect: mutateAsync };
222
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import { ResponseType } from '@/interfaces/database/base';
2
  import { DSL, IFlow, IFlowTemplate } from '@/interfaces/database/flow';
3
+ import { IDebugSingleRequestBody } from '@/interfaces/request/flow';
4
  import i18n from '@/locales/config';
5
  import flowService from '@/services/flow-service';
6
  import { buildMessageListWithUuid } from '@/utils/chat';
 
221
 
222
  return { data, loading, testDbConnect: mutateAsync };
223
  };
224
+
225
+ export const useFetchInputElements = (componentId?: string) => {
226
+ const { id } = useParams();
227
+
228
+ const { data, isPending: loading } = useQuery({
229
+ queryKey: ['fetchInputElements', id, componentId],
230
+ initialData: [],
231
+ enabled: !!id && !!componentId,
232
+ retryOnMount: false,
233
+ refetchOnWindowFocus: false,
234
+ refetchOnReconnect: false,
235
+ gcTime: 0,
236
+ queryFn: async () => {
237
+ try {
238
+ const { data } = await flowService.getInputElements({
239
+ id,
240
+ component_id: componentId,
241
+ });
242
+ return data?.data ?? [];
243
+ } catch (error) {
244
+ console.log('🚀 ~ queryFn: ~ error:', error);
245
+ }
246
+ },
247
+ });
248
+
249
+ return { data, loading };
250
+ };
251
+
252
+ export const useDebugSingle = () => {
253
+ const { id } = useParams();
254
+ const {
255
+ data,
256
+ isPending: loading,
257
+ mutateAsync,
258
+ } = useMutation({
259
+ mutationKey: ['debugSingle'],
260
+ mutationFn: async (params: IDebugSingleRequestBody) => {
261
+ const ret = await flowService.debugSingle({ id, ...params });
262
+ if (ret?.data?.code !== 0) {
263
+ message.error(ret?.data?.message);
264
+ }
265
+ return ret?.data?.data;
266
+ },
267
+ });
268
+
269
+ return { data, loading, debugSingle: mutateAsync };
270
+ };
web/src/interfaces/request/flow.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ export interface IDebugSingleRequestBody {
2
+ component_id: string;
3
+ params: any[];
4
+ }
web/src/locales/en.ts CHANGED
@@ -381,8 +381,7 @@ The above is the content you need to summarize.`,
381
  freedomTip: `Set the freedom level to 'Precise' to strictly confine the LLM's response to your selected knowledge base(s). Choose 'Improvise' to grant the LLM greater freedom in its responses, which may lead to hallucinations. 'Balance' is an intermediate level; choose 'Balance' for more balanced responses.`,
382
  temperature: 'Temperature',
383
  temperatureMessage: 'Temperature is required',
384
- temperatureTip:
385
- `This parameter controls the randomness of the model's predictions. A lower temperature results in more conservative responses, while a higher temperature yields more creative and diverse responses.`,
386
  topP: 'Top P',
387
  topPMessage: 'Top P is required',
388
  topPTip:
@@ -397,8 +396,7 @@ The above is the content you need to summarize.`,
397
  'Similar to the presence penalty, this reduces the model’s tendency to repeat the same words frequently.',
398
  maxTokens: 'Max tokens',
399
  maxTokensMessage: 'Max tokens is required',
400
- maxTokensTip:
401
- `This sets the maximum length of the model's output, measured in the number of tokens (words or pieces of words). If disabled, you lift the maximum token limit, allowing the model to determine the number of tokens in its responses. Defaults to 512.`,
402
  maxTokensInvalidMessage: 'Please enter a valid number for Max Tokens.',
403
  maxTokensMinMessage: 'Max Tokens cannot be less than 0.',
404
  quote: 'Show quote',
@@ -430,7 +428,7 @@ The above is the content you need to summarize.`,
430
  partialTitle: 'Partial Embed',
431
  extensionTitle: 'Chrome Extension',
432
  tokenError: 'Please create API Token first!',
433
- betaError: 'The beta field of the API Token cannot be empty!',
434
  searching: 'Searching...',
435
  parsing: 'Parsing',
436
  uploading: 'Uploading',
@@ -453,8 +451,7 @@ The above is the content you need to summarize.`,
453
  profileDescription: 'Update your photo and personal details here.',
454
  maxTokens: 'Max Tokens',
455
  maxTokensMessage: 'Max Tokens is required',
456
- maxTokensTip:
457
- `This sets the maximum length of the model's output, measured in the number of tokens (words or pieces of words). If disabled, you lift the maximum token limit, allowing the model to determine the number of tokens in its responses. Defaults to 512.`,
458
  maxTokensInvalidMessage: 'Please enter a valid number for Max Tokens.',
459
  maxTokensMinMessage: 'Max Tokens cannot be less than 0.',
460
  password: 'Password',
@@ -774,7 +771,7 @@ The above is the content you need to summarize.`,
774
  sourceLang: 'Source language',
775
  targetLang: 'Target language',
776
  gitHub: 'GitHub',
777
- githubDescription:
778
  'This component is used to search the repository from https://github.com/. Top N specifies the number of search results to be adjusted.',
779
  baiduFanyi: 'BaiduFanyi',
780
  baiduFanyiDescription:
 
381
  freedomTip: `Set the freedom level to 'Precise' to strictly confine the LLM's response to your selected knowledge base(s). Choose 'Improvise' to grant the LLM greater freedom in its responses, which may lead to hallucinations. 'Balance' is an intermediate level; choose 'Balance' for more balanced responses.`,
382
  temperature: 'Temperature',
383
  temperatureMessage: 'Temperature is required',
384
+ temperatureTip: `This parameter controls the randomness of the model's predictions. A lower temperature results in more conservative responses, while a higher temperature yields more creative and diverse responses.`,
 
385
  topP: 'Top P',
386
  topPMessage: 'Top P is required',
387
  topPTip:
 
396
  'Similar to the presence penalty, this reduces the model’s tendency to repeat the same words frequently.',
397
  maxTokens: 'Max tokens',
398
  maxTokensMessage: 'Max tokens is required',
399
+ maxTokensTip: `This sets the maximum length of the model's output, measured in the number of tokens (words or pieces of words). If disabled, you lift the maximum token limit, allowing the model to determine the number of tokens in its responses. Defaults to 512.`,
 
400
  maxTokensInvalidMessage: 'Please enter a valid number for Max Tokens.',
401
  maxTokensMinMessage: 'Max Tokens cannot be less than 0.',
402
  quote: 'Show quote',
 
428
  partialTitle: 'Partial Embed',
429
  extensionTitle: 'Chrome Extension',
430
  tokenError: 'Please create API Token first!',
431
+ betaError: 'Please apply an API key in system setting firstly.',
432
  searching: 'Searching...',
433
  parsing: 'Parsing',
434
  uploading: 'Uploading',
 
451
  profileDescription: 'Update your photo and personal details here.',
452
  maxTokens: 'Max Tokens',
453
  maxTokensMessage: 'Max Tokens is required',
454
+ maxTokensTip: `This sets the maximum length of the model's output, measured in the number of tokens (words or pieces of words). If disabled, you lift the maximum token limit, allowing the model to determine the number of tokens in its responses. Defaults to 512.`,
 
455
  maxTokensInvalidMessage: 'Please enter a valid number for Max Tokens.',
456
  maxTokensMinMessage: 'Max Tokens cannot be less than 0.',
457
  password: 'Password',
 
771
  sourceLang: 'Source language',
772
  targetLang: 'Target language',
773
  gitHub: 'GitHub',
774
+ gitHubDescription:
775
  'This component is used to search the repository from https://github.com/. Top N specifies the number of search results to be adjusted.',
776
  baiduFanyi: 'BaiduFanyi',
777
  baiduFanyiDescription:
web/src/locales/zh-traditional.ts CHANGED
@@ -414,7 +414,7 @@ export default {
414
  partialTitle: '部分嵌入',
415
  extensionTitle: 'Chrome 插件',
416
  tokenError: '請先創建 API Token!',
417
- betaError: 'API Token的beta欄位不可以為空!',
418
  searching: '搜索中',
419
  parsing: '解析中',
420
  uploading: '上傳中',
 
414
  partialTitle: '部分嵌入',
415
  extensionTitle: 'Chrome 插件',
416
  tokenError: '請先創建 API Token!',
417
+ betaError: '請先在系統設定中申請API密鑰。',
418
  searching: '搜索中',
419
  parsing: '解析中',
420
  uploading: '上傳中',
web/src/locales/zh.ts CHANGED
@@ -431,7 +431,7 @@ export default {
431
  partialTitle: '部分嵌入',
432
  extensionTitle: 'Chrome 插件',
433
  tokenError: '请先创建 API Token!',
434
- betaError: 'API Token的beta字段不可以为空!',
435
  searching: '搜索中',
436
  parsing: '解析中',
437
  uploading: '上传中',
@@ -760,7 +760,7 @@ export default {
760
  sourceLang: '源语言',
761
  targetLang: '目标语言',
762
  gitHub: 'GitHub',
763
- githubDescription:
764
  '该组件用于从 https://github.com/ 搜索仓库。Top N 指定需要调整的搜索结果数量。',
765
  baiduFanyi: '百度翻译',
766
  baiduFanyiDescription:
 
431
  partialTitle: '部分嵌入',
432
  extensionTitle: 'Chrome 插件',
433
  tokenError: '请先创建 API Token!',
434
+ betaError: '请先在系统设置中申请API密钥。',
435
  searching: '搜索中',
436
  parsing: '解析中',
437
  uploading: '上传中',
 
760
  sourceLang: '源语言',
761
  targetLang: '目标语言',
762
  gitHub: 'GitHub',
763
+ gitHubDescription:
764
  '该组件用于从 https://github.com/ 搜索仓库。Top N 指定需要调整的搜索结果数量。',
765
  baiduFanyi: '百度翻译',
766
  baiduFanyiDescription:
web/src/pages/flow/canvas/index.tsx CHANGED
@@ -5,6 +5,7 @@ import {
5
  TooltipTrigger,
6
  } from '@/components/ui/tooltip';
7
  import { useSetModalState } from '@/hooks/common-hooks';
 
8
  import { FolderInput, FolderOutput } from 'lucide-react';
9
  import { useCallback, useEffect } from 'react';
10
  import ReactFlow, {
@@ -24,6 +25,7 @@ import {
24
  useHandleExportOrImportJsonFile,
25
  useSelectCanvasData,
26
  useShowFormDrawer,
 
27
  useValidateConnection,
28
  useWatchNodeFormDataChange,
29
  } from '../hooks';
@@ -95,6 +97,11 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
95
  showModal: showChatModal,
96
  hideModal: hideChatModal,
97
  } = useSetModalState();
 
 
 
 
 
98
 
99
  const { formDrawerVisible, hideFormDrawer, showFormDrawer, clickedNode } =
100
  useShowFormDrawer();
@@ -116,11 +123,24 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
116
  const onNodeClick: NodeMouseHandler = useCallback(
117
  (e, node) => {
118
  if (node.data.label !== Operator.Note) {
 
119
  hideRunOrChatDrawer();
120
  showFormDrawer(node);
121
  }
 
 
 
 
 
 
 
122
  },
123
- [hideRunOrChatDrawer, showFormDrawer],
 
 
 
 
 
124
  );
125
 
126
  const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
@@ -193,12 +213,6 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
193
  onSelectionChange={onSelectionChange}
194
  nodeOrigin={[0.5, 0]}
195
  isValidConnection={isValidConnection}
196
- onChangeCapture={(...params) => {
197
- console.info('onChangeCapture:', ...params);
198
- }}
199
- onChange={(...params) => {
200
- console.info('params:', ...params);
201
- }}
202
  defaultEdgeOptions={{
203
  type: 'buttonEdge',
204
  markerEnd: 'logo',
@@ -214,7 +228,7 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
214
  <ControlButton onClick={handleImportJson}>
215
  <TooltipProvider>
216
  <Tooltip>
217
- <TooltipTrigger>
218
  <FolderInput />
219
  </TooltipTrigger>
220
  <TooltipContent>Import</TooltipContent>
@@ -224,7 +238,7 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
224
  <ControlButton onClick={handleExportJson}>
225
  <TooltipProvider>
226
  <Tooltip>
227
- <TooltipTrigger>
228
  <FolderOutput />
229
  </TooltipTrigger>
230
  <TooltipContent>Export</TooltipContent>
@@ -238,6 +252,9 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
238
  node={clickedNode}
239
  visible={formDrawerVisible}
240
  hideModal={hideFormDrawer}
 
 
 
241
  ></FormDrawer>
242
  )}
243
  {chatVisible && (
 
5
  TooltipTrigger,
6
  } from '@/components/ui/tooltip';
7
  import { useSetModalState } from '@/hooks/common-hooks';
8
+ import { get } from 'lodash';
9
  import { FolderInput, FolderOutput } from 'lucide-react';
10
  import { useCallback, useEffect } from 'react';
11
  import ReactFlow, {
 
25
  useHandleExportOrImportJsonFile,
26
  useSelectCanvasData,
27
  useShowFormDrawer,
28
+ useShowSingleDebugDrawer,
29
  useValidateConnection,
30
  useWatchNodeFormDataChange,
31
  } from '../hooks';
 
97
  showModal: showChatModal,
98
  hideModal: hideChatModal,
99
  } = useSetModalState();
100
+ const {
101
+ singleDebugDrawerVisible,
102
+ showSingleDebugDrawer,
103
+ hideSingleDebugDrawer,
104
+ } = useShowSingleDebugDrawer();
105
 
106
  const { formDrawerVisible, hideFormDrawer, showFormDrawer, clickedNode } =
107
  useShowFormDrawer();
 
123
  const onNodeClick: NodeMouseHandler = useCallback(
124
  (e, node) => {
125
  if (node.data.label !== Operator.Note) {
126
+ hideSingleDebugDrawer();
127
  hideRunOrChatDrawer();
128
  showFormDrawer(node);
129
  }
130
+ // handle single debug icon click
131
+ if (
132
+ get(e.target, 'dataset.play') === 'true' ||
133
+ get(e.target, 'parentNode.dataset.play') === 'true'
134
+ ) {
135
+ showSingleDebugDrawer();
136
+ }
137
  },
138
+ [
139
+ hideRunOrChatDrawer,
140
+ hideSingleDebugDrawer,
141
+ showFormDrawer,
142
+ showSingleDebugDrawer,
143
+ ],
144
  );
145
 
146
  const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
 
213
  onSelectionChange={onSelectionChange}
214
  nodeOrigin={[0.5, 0]}
215
  isValidConnection={isValidConnection}
 
 
 
 
 
 
216
  defaultEdgeOptions={{
217
  type: 'buttonEdge',
218
  markerEnd: 'logo',
 
228
  <ControlButton onClick={handleImportJson}>
229
  <TooltipProvider>
230
  <Tooltip>
231
+ <TooltipTrigger asChild>
232
  <FolderInput />
233
  </TooltipTrigger>
234
  <TooltipContent>Import</TooltipContent>
 
238
  <ControlButton onClick={handleExportJson}>
239
  <TooltipProvider>
240
  <Tooltip>
241
+ <TooltipTrigger asChild>
242
  <FolderOutput />
243
  </TooltipTrigger>
244
  <TooltipContent>Export</TooltipContent>
 
252
  node={clickedNode}
253
  visible={formDrawerVisible}
254
  hideModal={hideFormDrawer}
255
+ singleDebugDrawerVisible={singleDebugDrawerVisible}
256
+ hideSingleDebugDrawer={hideSingleDebugDrawer}
257
+ showSingleDebugDrawer={showSingleDebugDrawer}
258
  ></FormDrawer>
259
  )}
260
  {chatVisible && (
web/src/pages/flow/canvas/node/node-header.tsx CHANGED
@@ -1,12 +1,14 @@
 
1
  import { Flex } from 'antd';
 
2
  import { Operator, operatorMap } from '../../constant';
3
  import OperatorIcon from '../../operator-icon';
 
4
  import NodeDropdown from './dropdown';
5
-
6
- import { useTranslate } from '@/hooks/common-hooks';
7
- import styles from './index.less';
8
  import { NextNodePopover } from './popover';
9
 
 
 
10
  interface IProps {
11
  id: string;
12
  label: string;
@@ -15,12 +17,17 @@ interface IProps {
15
  className?: string;
16
  }
17
 
18
- export function RunStatus({ id, name }: Omit<IProps, 'label'>) {
19
  const { t } = useTranslate('flow');
20
  return (
21
- <section className="flex justify-end items-center pb-1 ">
 
 
 
 
 
22
  <NextNodePopover nodeId={id} name={name}>
23
- <span className="text-blue-600 cursor-pointer text-[10px]">
24
  {t('operationResults')}
25
  </span>
26
  </NextNodePopover>
@@ -30,8 +37,10 @@ export function RunStatus({ id, name }: Omit<IProps, 'label'>) {
30
 
31
  const NodeHeader = ({ label, id, name, gap = 4, className }: IProps) => {
32
  return (
33
- <section className="haha">
34
- {label !== Operator.Answer && <RunStatus id={id} name={name}></RunStatus>}
 
 
35
  <Flex
36
  flex={1}
37
  align="center"
 
1
+ import { useTranslate } from '@/hooks/common-hooks';
2
  import { Flex } from 'antd';
3
+ import { Play } from 'lucide-react';
4
  import { Operator, operatorMap } from '../../constant';
5
  import OperatorIcon from '../../operator-icon';
6
+ import { needsSingleStepDebugging } from '../../utils';
7
  import NodeDropdown from './dropdown';
 
 
 
8
  import { NextNodePopover } from './popover';
9
 
10
+ import { RunTooltip } from '../../flow-tooltip';
11
+ import styles from './index.less';
12
  interface IProps {
13
  id: string;
14
  label: string;
 
17
  className?: string;
18
  }
19
 
20
+ export function RunStatus({ id, name, label }: IProps) {
21
  const { t } = useTranslate('flow');
22
  return (
23
+ <section className="flex justify-end items-center pb-1 gap-2 text-blue-600">
24
+ {needsSingleStepDebugging(label) && (
25
+ <RunTooltip>
26
+ <Play className="size-3 cursor-pointer" data-play />
27
+ </RunTooltip> // data-play is used to trigger single step debugging
28
+ )}
29
  <NextNodePopover nodeId={id} name={name}>
30
+ <span className="cursor-pointer text-[10px]">
31
  {t('operationResults')}
32
  </span>
33
  </NextNodePopover>
 
37
 
38
  const NodeHeader = ({ label, id, name, gap = 4, className }: IProps) => {
39
  return (
40
+ <section>
41
+ {label !== Operator.Answer && (
42
+ <RunStatus id={id} name={name} label={label}></RunStatus>
43
+ )}
44
  <Flex
45
  flex={1}
46
  align="center"
web/src/pages/flow/constant.tsx CHANGED
@@ -2931,3 +2931,13 @@ export const BeginQueryTypeIconMap = {
2931
  [BeginQueryType.Integer]: ListOrdered,
2932
  [BeginQueryType.Boolean]: ToggleLeft,
2933
  };
 
 
 
 
 
 
 
 
 
 
 
2931
  [BeginQueryType.Integer]: ListOrdered,
2932
  [BeginQueryType.Boolean]: ToggleLeft,
2933
  };
2934
+
2935
+ export const NoDebugOperatorsList = [
2936
+ Operator.Begin,
2937
+ Operator.Answer,
2938
+ Operator.Concentrator,
2939
+ Operator.Template,
2940
+ Operator.Message,
2941
+ Operator.RewriteQuestion,
2942
+ Operator.Switch,
2943
+ ];
web/src/pages/flow/debug-content/index.less ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ .formWrapper {
2
+ :global(.ant-form-item-label) {
3
+ font-weight: 600 !important;
4
+ }
5
+ }
web/src/pages/flow/debug-content/index.tsx ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Authorization } from '@/constants/authorization';
2
+ import { useSetModalState } from '@/hooks/common-hooks';
3
+ import { useSetSelectedRecord } from '@/hooks/logic-hooks';
4
+ import { useHandleSubmittable } from '@/hooks/login-hooks';
5
+ import api from '@/utils/api';
6
+ import { getAuthorization } from '@/utils/authorization-util';
7
+ import { UploadOutlined } from '@ant-design/icons';
8
+ import {
9
+ Button,
10
+ Form,
11
+ FormItemProps,
12
+ Input,
13
+ InputNumber,
14
+ Select,
15
+ Switch,
16
+ Upload,
17
+ } from 'antd';
18
+ import { UploadChangeParam, UploadFile } from 'antd/es/upload';
19
+ import { pick } from 'lodash';
20
+ import { Link } from 'lucide-react';
21
+ import React, { useCallback, useState } from 'react';
22
+ import { useTranslation } from 'react-i18next';
23
+ import { BeginQueryType } from '../constant';
24
+ import { BeginQuery } from '../interface';
25
+ import { PopoverForm } from './popover-form';
26
+
27
+ import styles from './index.less';
28
+
29
+ interface IProps {
30
+ parameters: BeginQuery[];
31
+ ok(parameters: any[]): void;
32
+ isNext?: boolean;
33
+ loading?: boolean;
34
+ submitButtonDisabled?: boolean;
35
+ }
36
+
37
+ const DebugContent = ({
38
+ parameters,
39
+ ok,
40
+ isNext = true,
41
+ loading = false,
42
+ submitButtonDisabled = false,
43
+ }: IProps) => {
44
+ const { t } = useTranslation();
45
+ const [form] = Form.useForm();
46
+ const {
47
+ visible,
48
+ hideModal: hidePopover,
49
+ switchVisible,
50
+ showModal: showPopover,
51
+ } = useSetModalState();
52
+ const { setRecord, currentRecord } = useSetSelectedRecord<number>();
53
+ const { submittable } = useHandleSubmittable(form);
54
+ const [isUploading, setIsUploading] = useState(false);
55
+
56
+ const handleShowPopover = useCallback(
57
+ (idx: number) => () => {
58
+ setRecord(idx);
59
+ showPopover();
60
+ },
61
+ [setRecord, showPopover],
62
+ );
63
+
64
+ const normFile = (e: any) => {
65
+ if (Array.isArray(e)) {
66
+ return e;
67
+ }
68
+ return e?.fileList;
69
+ };
70
+
71
+ const onChange = useCallback(
72
+ (optional: boolean) =>
73
+ ({ fileList }: UploadChangeParam<UploadFile>) => {
74
+ if (!optional) {
75
+ setIsUploading(fileList.some((x) => x.status === 'uploading'));
76
+ }
77
+ },
78
+ [],
79
+ );
80
+
81
+ const renderWidget = useCallback(
82
+ (q: BeginQuery, idx: number) => {
83
+ const props: FormItemProps & { key: number } = {
84
+ key: idx,
85
+ label: q.name ?? q.key,
86
+ name: idx,
87
+ };
88
+ if (q.optional === false) {
89
+ props.rules = [{ required: true }];
90
+ }
91
+
92
+ const urlList: { url: string; result: string }[] =
93
+ form.getFieldValue(idx) || [];
94
+
95
+ const BeginQueryTypeMap = {
96
+ [BeginQueryType.Line]: (
97
+ <Form.Item {...props}>
98
+ <Input></Input>
99
+ </Form.Item>
100
+ ),
101
+ [BeginQueryType.Paragraph]: (
102
+ <Form.Item {...props}>
103
+ <Input.TextArea rows={1}></Input.TextArea>
104
+ </Form.Item>
105
+ ),
106
+ [BeginQueryType.Options]: (
107
+ <Form.Item {...props}>
108
+ <Select
109
+ allowClear
110
+ options={q.options?.map((x) => ({ label: x, value: x })) ?? []}
111
+ ></Select>
112
+ </Form.Item>
113
+ ),
114
+ [BeginQueryType.File]: (
115
+ <React.Fragment key={idx}>
116
+ <Form.Item label={q.name ?? q.key} required={!q.optional}>
117
+ <div className="relative">
118
+ <Form.Item
119
+ {...props}
120
+ valuePropName="fileList"
121
+ getValueFromEvent={normFile}
122
+ noStyle
123
+ >
124
+ <Upload
125
+ name="file"
126
+ action={api.parse}
127
+ multiple
128
+ headers={{ [Authorization]: getAuthorization() }}
129
+ onChange={onChange(q.optional)}
130
+ >
131
+ <Button icon={<UploadOutlined />}>
132
+ {t('common.upload')}
133
+ </Button>
134
+ </Upload>
135
+ </Form.Item>
136
+ <Form.Item
137
+ {...pick(props, ['key', 'label', 'rules'])}
138
+ required={!q.optional}
139
+ className={urlList.length > 0 ? 'mb-1' : ''}
140
+ noStyle
141
+ >
142
+ <PopoverForm visible={visible} switchVisible={switchVisible}>
143
+ <Button
144
+ onClick={handleShowPopover(idx)}
145
+ className="absolute left-1/2 top-0"
146
+ icon={<Link className="size-3" />}
147
+ >
148
+ {t('flow.pasteFileLink')}
149
+ </Button>
150
+ </PopoverForm>
151
+ </Form.Item>
152
+ </div>
153
+ </Form.Item>
154
+ <Form.Item name={idx} noStyle {...pick(props, ['rules'])} />
155
+ </React.Fragment>
156
+ ),
157
+ [BeginQueryType.Integer]: (
158
+ <Form.Item {...props}>
159
+ <InputNumber></InputNumber>
160
+ </Form.Item>
161
+ ),
162
+ [BeginQueryType.Boolean]: (
163
+ <Form.Item valuePropName={'checked'} {...props}>
164
+ <Switch></Switch>
165
+ </Form.Item>
166
+ ),
167
+ };
168
+
169
+ return (
170
+ BeginQueryTypeMap[q.type as BeginQueryType] ??
171
+ BeginQueryTypeMap[BeginQueryType.Paragraph]
172
+ );
173
+ },
174
+ [form, handleShowPopover, onChange, switchVisible, t, visible],
175
+ );
176
+
177
+ const onOk = useCallback(async () => {
178
+ const values = await form.validateFields();
179
+ const nextValues = Object.entries(values).map(([key, value]) => {
180
+ const item = parameters[Number(key)];
181
+ let nextValue = value;
182
+ if (Array.isArray(value)) {
183
+ nextValue = ``;
184
+
185
+ value.forEach((x) => {
186
+ nextValue +=
187
+ x?.originFileObj instanceof File
188
+ ? `${x.name}\n${x.response?.data}\n----\n`
189
+ : `${x.url}\n${x.result}\n----\n`;
190
+ });
191
+ }
192
+ return { ...item, value: nextValue };
193
+ });
194
+
195
+ ok(nextValues);
196
+ }, [form, ok, parameters]);
197
+
198
+ return (
199
+ <>
200
+ <section className={styles.formWrapper}>
201
+ <Form.Provider
202
+ onFormFinish={(name, { values, forms }) => {
203
+ if (name === 'urlForm') {
204
+ const { basicForm } = forms;
205
+ const urlInfo = basicForm.getFieldValue(currentRecord) || [];
206
+ basicForm.setFieldsValue({
207
+ [currentRecord]: [...urlInfo, { ...values, name: values.url }],
208
+ });
209
+ hidePopover();
210
+ }
211
+ }}
212
+ >
213
+ <Form
214
+ name="basicForm"
215
+ autoComplete="off"
216
+ layout={'vertical'}
217
+ form={form}
218
+ >
219
+ {parameters.map((x, idx) => {
220
+ return renderWidget(x, idx);
221
+ })}
222
+ </Form>
223
+ </Form.Provider>
224
+ </section>
225
+ <Button
226
+ type={'primary'}
227
+ block
228
+ onClick={onOk}
229
+ loading={loading}
230
+ disabled={!submittable || isUploading || submitButtonDisabled}
231
+ >
232
+ {t(isNext ? 'common.next' : 'flow.run')}
233
+ </Button>
234
+ </>
235
+ );
236
+ };
237
+
238
+ export default DebugContent;
web/src/pages/flow/debug-content/popover-form.tsx ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useParseDocument } from '@/hooks/document-hooks';
2
+ import { useResetFormOnCloseModal } from '@/hooks/logic-hooks';
3
+ import { IModalProps } from '@/interfaces/common';
4
+ import { Button, Form, Input, Popover } from 'antd';
5
+ import { PropsWithChildren } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ const reg =
9
+ /^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/;
10
+
11
+ export const PopoverForm = ({
12
+ children,
13
+ visible,
14
+ switchVisible,
15
+ }: PropsWithChildren<IModalProps<any>>) => {
16
+ const [form] = Form.useForm();
17
+ const { parseDocument, loading } = useParseDocument();
18
+ const { t } = useTranslation();
19
+
20
+ useResetFormOnCloseModal({
21
+ form,
22
+ visible,
23
+ });
24
+
25
+ const onOk = async () => {
26
+ const values = await form.validateFields();
27
+ const val = values.url;
28
+
29
+ if (reg.test(val)) {
30
+ const ret = await parseDocument(val);
31
+ if (ret?.data?.code === 0) {
32
+ form.setFieldValue('result', ret?.data?.data);
33
+ form.submit();
34
+ }
35
+ }
36
+ };
37
+
38
+ const content = (
39
+ <Form form={form} name="urlForm">
40
+ <Form.Item
41
+ name="url"
42
+ rules={[{ required: true, type: 'url' }]}
43
+ className="m-0"
44
+ >
45
+ <Input
46
+ onPressEnter={(e) => e.preventDefault()}
47
+ placeholder={t('flow.pasteFileLink')}
48
+ suffix={
49
+ <Button
50
+ type="primary"
51
+ onClick={onOk}
52
+ size={'small'}
53
+ loading={loading}
54
+ >
55
+ {t('common.submit')}
56
+ </Button>
57
+ }
58
+ />
59
+ </Form.Item>
60
+ <Form.Item name={'result'} noStyle />
61
+ </Form>
62
+ );
63
+
64
+ return (
65
+ <Popover
66
+ content={content}
67
+ open={visible}
68
+ trigger={'click'}
69
+ onOpenChange={switchVisible}
70
+ >
71
+ {children}
72
+ </Popover>
73
+ );
74
+ };
web/src/pages/flow/flow-drawer/index.less CHANGED
@@ -13,3 +13,9 @@
13
  padding-top: 16px;
14
  font-weight: normal;
15
  }
 
 
 
 
 
 
 
13
  padding-top: 16px;
14
  font-weight: normal;
15
  }
16
+
17
+ .formDrawer {
18
+ :global(.ant-drawer-content-wrapper) {
19
+ transform: translateX(0) !important;
20
+ }
21
+ }
web/src/pages/flow/flow-drawer/index.tsx CHANGED
@@ -1,6 +1,9 @@
1
  import { useTranslate } from '@/hooks/common-hooks';
2
  import { IModalProps } from '@/interfaces/common';
 
3
  import { Drawer, Flex, Form, Input } from 'antd';
 
 
4
  import { useEffect } from 'react';
5
  import { Node } from 'reactflow';
6
  import { Operator, operatorMap } from '../constant';
@@ -15,6 +18,7 @@ import CategorizeForm from '../form/categorize-form';
15
  import CrawlerForm from '../form/crawler-form';
16
  import DeepLForm from '../form/deepl-form';
17
  import DuckDuckGoForm from '../form/duckduckgo-form';
 
18
  import ExeSQLForm from '../form/exesql-form';
19
  import GenerateForm from '../form/generate-form';
20
  import GithubForm from '../form/github-form';
@@ -30,22 +34,24 @@ import RelevantForm from '../form/relevant-form';
30
  import RetrievalForm from '../form/retrieval-form';
31
  import RewriteQuestionForm from '../form/rewrite-question-form';
32
  import SwitchForm from '../form/switch-form';
 
33
  import TuShareForm from '../form/tushare-form';
34
  import WenCaiForm from '../form/wencai-form';
35
  import WikipediaForm from '../form/wikipedia-form';
36
  import YahooFinanceForm from '../form/yahoo-finance-form';
37
  import { useHandleFormValuesChange, useHandleNodeNameChange } from '../hooks';
38
  import OperatorIcon from '../operator-icon';
 
 
39
 
40
- import { CloseOutlined } from '@ant-design/icons';
41
- import { lowerFirst } from 'lodash';
42
- import EmailForm from '../form/email-form';
43
- import TemplateForm from '../form/template-form';
44
- import { getDrawerWidth } from '../utils';
45
  import styles from './index.less';
46
 
47
  interface IProps {
48
  node?: Node;
 
 
 
49
  }
50
 
51
  const FormMap = {
@@ -91,6 +97,9 @@ const FormDrawer = ({
91
  visible,
92
  hideModal,
93
  node,
 
 
 
94
  }: IModalProps<any> & IProps) => {
95
  const operatorName: Operator = node?.data.label;
96
  const OperatorForm = FormMap[operatorName] ?? EmptyContent;
@@ -99,12 +108,14 @@ const FormDrawer = ({
99
  id: node?.id,
100
  data: node?.data,
101
  });
 
102
  const { t } = useTranslate('flow');
103
 
104
  const { handleValuesChange } = useHandleFormValuesChange(node?.id);
105
 
106
  useEffect(() => {
107
  if (visible) {
 
108
  form.setFieldsValue(node?.data?.form);
109
  }
110
  }, [visible, form, node?.data?.form]);
@@ -128,6 +139,14 @@ const FormDrawer = ({
128
  onChange={handleNameChange}
129
  ></Input>
130
  </Flex>
 
 
 
 
 
 
 
 
131
  <CloseOutlined onClick={hideModal} />
132
  </Flex>
133
  <span className={styles.operatorDescription}>
@@ -142,6 +161,7 @@ const FormDrawer = ({
142
  mask={false}
143
  width={getDrawerWidth()}
144
  closeIcon={null}
 
145
  >
146
  <section className={styles.formWrapper}>
147
  {visible && (
@@ -152,6 +172,13 @@ const FormDrawer = ({
152
  ></OperatorForm>
153
  )}
154
  </section>
 
 
 
 
 
 
 
155
  </Drawer>
156
  );
157
  };
 
1
  import { useTranslate } from '@/hooks/common-hooks';
2
  import { IModalProps } from '@/interfaces/common';
3
+ import { CloseOutlined } from '@ant-design/icons';
4
  import { Drawer, Flex, Form, Input } from 'antd';
5
+ import { lowerFirst } from 'lodash';
6
+ import { Play } from 'lucide-react';
7
  import { useEffect } from 'react';
8
  import { Node } from 'reactflow';
9
  import { Operator, operatorMap } from '../constant';
 
18
  import CrawlerForm from '../form/crawler-form';
19
  import DeepLForm from '../form/deepl-form';
20
  import DuckDuckGoForm from '../form/duckduckgo-form';
21
+ import EmailForm from '../form/email-form';
22
  import ExeSQLForm from '../form/exesql-form';
23
  import GenerateForm from '../form/generate-form';
24
  import GithubForm from '../form/github-form';
 
34
  import RetrievalForm from '../form/retrieval-form';
35
  import RewriteQuestionForm from '../form/rewrite-question-form';
36
  import SwitchForm from '../form/switch-form';
37
+ import TemplateForm from '../form/template-form';
38
  import TuShareForm from '../form/tushare-form';
39
  import WenCaiForm from '../form/wencai-form';
40
  import WikipediaForm from '../form/wikipedia-form';
41
  import YahooFinanceForm from '../form/yahoo-finance-form';
42
  import { useHandleFormValuesChange, useHandleNodeNameChange } from '../hooks';
43
  import OperatorIcon from '../operator-icon';
44
+ import { getDrawerWidth, needsSingleStepDebugging } from '../utils';
45
+ import SingleDebugDrawer from './single-debug-drawer';
46
 
47
+ import { RunTooltip } from '../flow-tooltip';
 
 
 
 
48
  import styles from './index.less';
49
 
50
  interface IProps {
51
  node?: Node;
52
+ singleDebugDrawerVisible: IModalProps<any>['visible'];
53
+ hideSingleDebugDrawer: IModalProps<any>['hideModal'];
54
+ showSingleDebugDrawer: IModalProps<any>['showModal'];
55
  }
56
 
57
  const FormMap = {
 
97
  visible,
98
  hideModal,
99
  node,
100
+ singleDebugDrawerVisible,
101
+ hideSingleDebugDrawer,
102
+ showSingleDebugDrawer,
103
  }: IModalProps<any> & IProps) => {
104
  const operatorName: Operator = node?.data.label;
105
  const OperatorForm = FormMap[operatorName] ?? EmptyContent;
 
108
  id: node?.id,
109
  data: node?.data,
110
  });
111
+
112
  const { t } = useTranslate('flow');
113
 
114
  const { handleValuesChange } = useHandleFormValuesChange(node?.id);
115
 
116
  useEffect(() => {
117
  if (visible) {
118
+ form.resetFields();
119
  form.setFieldsValue(node?.data?.form);
120
  }
121
  }, [visible, form, node?.data?.form]);
 
139
  onChange={handleNameChange}
140
  ></Input>
141
  </Flex>
142
+ {needsSingleStepDebugging(operatorName) && (
143
+ <RunTooltip>
144
+ <Play
145
+ className="size-5 cursor-pointer"
146
+ onClick={showSingleDebugDrawer}
147
+ />
148
+ </RunTooltip>
149
+ )}
150
  <CloseOutlined onClick={hideModal} />
151
  </Flex>
152
  <span className={styles.operatorDescription}>
 
161
  mask={false}
162
  width={getDrawerWidth()}
163
  closeIcon={null}
164
+ rootClassName={styles.formDrawer}
165
  >
166
  <section className={styles.formWrapper}>
167
  {visible && (
 
172
  ></OperatorForm>
173
  )}
174
  </section>
175
+ {singleDebugDrawerVisible && (
176
+ <SingleDebugDrawer
177
+ visible={singleDebugDrawerVisible}
178
+ hideModal={hideSingleDebugDrawer}
179
+ componentId={node?.id}
180
+ ></SingleDebugDrawer>
181
+ )}
182
  </Drawer>
183
  );
184
  };
web/src/pages/flow/flow-drawer/single-debug-drawer/index.tsx ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import CopyToClipboard from '@/components/copy-to-clipboard';
2
+ import { useDebugSingle, useFetchInputElements } from '@/hooks/flow-hooks';
3
+ import { IModalProps } from '@/interfaces/common';
4
+ import { CloseOutlined } from '@ant-design/icons';
5
+ import { Drawer } from 'antd';
6
+ import { isEmpty } from 'lodash';
7
+ import { useCallback } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+ import JsonView from 'react18-json-view';
10
+ import 'react18-json-view/src/style.css';
11
+ import DebugContent from '../../debug-content';
12
+
13
+ interface IProps {
14
+ componentId?: string;
15
+ }
16
+
17
+ const SingleDebugDrawer = ({
18
+ componentId,
19
+ visible,
20
+ hideModal,
21
+ }: IModalProps<any> & IProps) => {
22
+ const { t } = useTranslation();
23
+ const { data: list } = useFetchInputElements(componentId);
24
+ const { debugSingle, data, loading } = useDebugSingle();
25
+
26
+ const onOk = useCallback(
27
+ (nextValues: any[]) => {
28
+ if (componentId) {
29
+ debugSingle({ component_id: componentId, params: nextValues });
30
+ }
31
+ },
32
+ [componentId, debugSingle],
33
+ );
34
+
35
+ const content = JSON.stringify(data, null, 2);
36
+
37
+ return (
38
+ <Drawer
39
+ title={
40
+ <div className="flex justify-between">
41
+ {t('flow.testRun')}
42
+ <CloseOutlined onClick={hideModal} />
43
+ </div>
44
+ }
45
+ width={'100%'}
46
+ onClose={hideModal}
47
+ open={visible}
48
+ getContainer={false}
49
+ mask={false}
50
+ placement={'bottom'}
51
+ height={'95%'}
52
+ closeIcon={null}
53
+ >
54
+ <section className="overflow-y-auto">
55
+ <DebugContent
56
+ parameters={list}
57
+ ok={onOk}
58
+ isNext={false}
59
+ loading={loading}
60
+ submitButtonDisabled={list.length === 0}
61
+ ></DebugContent>
62
+ {!isEmpty(data) ? (
63
+ <div className="mt-4 rounded-md bg-slate-200 border border-neutral-200">
64
+ <div className="flex justify-between p-2">
65
+ <span>JSON</span>
66
+ <CopyToClipboard text={content}></CopyToClipboard>
67
+ </div>
68
+ <JsonView
69
+ src={data}
70
+ displaySize
71
+ collapseStringsAfterLength={100000000000}
72
+ className="w-full h-[800px] break-words overflow-auto p-2 bg-slate-100"
73
+ />
74
+ </div>
75
+ ) : null}
76
+ </section>
77
+ </Drawer>
78
+ );
79
+ };
80
+
81
+ export default SingleDebugDrawer;
web/src/pages/flow/flow-tooltip.tsx ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Tooltip,
3
+ TooltipContent,
4
+ TooltipProvider,
5
+ TooltipTrigger,
6
+ } from '@/components/ui/tooltip';
7
+ import { PropsWithChildren } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+
10
+ export const RunTooltip = ({ children }: PropsWithChildren) => {
11
+ const { t } = useTranslation();
12
+ return (
13
+ <TooltipProvider>
14
+ <Tooltip>
15
+ <TooltipTrigger>{children}</TooltipTrigger>
16
+ <TooltipContent>
17
+ <p>{t('flow.testRun')}</p>
18
+ </TooltipContent>
19
+ </Tooltip>
20
+ </TooltipProvider>
21
+ );
22
+ };
web/src/pages/flow/form/invoke-form/index.tsx CHANGED
@@ -1,9 +1,11 @@
1
- import Editor from '@monaco-editor/react';
2
  import { Form, Input, InputNumber, Select, Space, Switch } from 'antd';
3
  import { useTranslation } from 'react-i18next';
4
  import { IOperatorForm } from '../../interface';
5
  import DynamicVariablesForm from './dynamic-variables';
6
 
 
 
7
  enum Method {
8
  GET = 'GET',
9
  POST = 'POST',
 
1
+ import Editor, { loader } from '@monaco-editor/react';
2
  import { Form, Input, InputNumber, Select, Space, Switch } from 'antd';
3
  import { useTranslation } from 'react-i18next';
4
  import { IOperatorForm } from '../../interface';
5
  import DynamicVariablesForm from './dynamic-variables';
6
 
7
+ loader.config({ paths: { vs: '/vs' } });
8
+
9
  enum Method {
10
  GET = 'GET',
11
  POST = 'POST',
web/src/pages/flow/hooks.tsx CHANGED
@@ -620,7 +620,6 @@ export const useWatchNodeFormDataChange = () => {
620
  );
621
 
622
  useEffect(() => {
623
- console.info('xxx');
624
  nodes.forEach((node) => {
625
  const currentNode = getNode(node.id);
626
  const form = currentNode?.data.form ?? {};
@@ -856,3 +855,21 @@ export const useHandleExportOrImportJsonFile = () => {
856
  onFileUploadOk,
857
  };
858
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
620
  );
621
 
622
  useEffect(() => {
 
623
  nodes.forEach((node) => {
624
  const currentNode = getNode(node.id);
625
  const form = currentNode?.data.form ?? {};
 
855
  onFileUploadOk,
856
  };
857
  };
858
+
859
+ export const useShowSingleDebugDrawer = () => {
860
+ const { visible, showModal, hideModal } = useSetModalState();
861
+ const { saveGraph } = useSaveGraph();
862
+
863
+ const showSingleDebugDrawer = useCallback(async () => {
864
+ const saveRet = await saveGraph();
865
+ if (saveRet?.code === 0) {
866
+ showModal();
867
+ }
868
+ }, [saveGraph, showModal]);
869
+
870
+ return {
871
+ singleDebugDrawerVisible: visible,
872
+ hideSingleDebugDrawer: hideModal,
873
+ showSingleDebugDrawer,
874
+ };
875
+ };
web/src/pages/flow/run-drawer/index.tsx CHANGED
@@ -1,26 +1,7 @@
1
- import { Authorization } from '@/constants/authorization';
2
- import { useSetModalState } from '@/hooks/common-hooks';
3
- import { useSetSelectedRecord } from '@/hooks/logic-hooks';
4
- import { useHandleSubmittable } from '@/hooks/login-hooks';
5
  import { IModalProps } from '@/interfaces/common';
6
- import api from '@/utils/api';
7
- import { getAuthorization } from '@/utils/authorization-util';
8
- import { UploadOutlined } from '@ant-design/icons';
9
- import {
10
- Button,
11
- Drawer,
12
- Form,
13
- FormItemProps,
14
- Input,
15
- InputNumber,
16
- Select,
17
- Switch,
18
- Upload,
19
- } from 'antd';
20
- import { pick } from 'lodash';
21
- import React, { useCallback, useState } from 'react';
22
  import { useTranslation } from 'react-i18next';
23
- import { BeginQueryType } from '../constant';
24
  import {
25
  useGetBeginNodeDataQuery,
26
  useSaveGraphBeforeOpeningDebugDrawer,
@@ -28,152 +9,23 @@ import {
28
  import { BeginQuery } from '../interface';
29
  import useGraphStore from '../store';
30
  import { getDrawerWidth } from '../utils';
31
- import { PopoverForm } from './popover-form';
32
 
33
- import { UploadChangeParam, UploadFile } from 'antd/es/upload';
34
- import { Link } from 'lucide-react';
35
- import styles from './index.less';
36
 
37
  const RunDrawer = ({
38
  hideModal,
39
  showModal: showChatModal,
40
  }: IModalProps<any>) => {
41
  const { t } = useTranslation();
42
- const [form] = Form.useForm();
43
  const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
44
- const {
45
- visible,
46
- hideModal: hidePopover,
47
- switchVisible,
48
- showModal: showPopover,
49
- } = useSetModalState();
50
- const { setRecord, currentRecord } = useSetSelectedRecord<number>();
51
- const { submittable } = useHandleSubmittable(form);
52
- const [isUploading, setIsUploading] = useState(false);
53
-
54
- const handleShowPopover = useCallback(
55
- (idx: number) => () => {
56
- setRecord(idx);
57
- showPopover();
58
- },
59
- [setRecord, showPopover],
60
- );
61
 
62
  const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
63
  const query: BeginQuery[] = getBeginNodeDataQuery();
64
 
65
- const normFile = (e: any) => {
66
- if (Array.isArray(e)) {
67
- return e;
68
- }
69
- return e?.fileList;
70
- };
71
-
72
- const onChange = useCallback(
73
- (optional: boolean) =>
74
- ({ fileList }: UploadChangeParam<UploadFile>) => {
75
- if (!optional) {
76
- setIsUploading(fileList.some((x) => x.status === 'uploading'));
77
- }
78
- },
79
- [],
80
  );
81
 
82
- const renderWidget = useCallback(
83
- (q: BeginQuery, idx: number) => {
84
- const props: FormItemProps & { key: number } = {
85
- key: idx,
86
- label: q.name,
87
- name: idx,
88
- };
89
- if (q.optional === false) {
90
- props.rules = [{ required: true }];
91
- }
92
-
93
- const urlList: { url: string; result: string }[] =
94
- form.getFieldValue(idx) || [];
95
-
96
- const BeginQueryTypeMap = {
97
- [BeginQueryType.Line]: (
98
- <Form.Item {...props}>
99
- <Input></Input>
100
- </Form.Item>
101
- ),
102
- [BeginQueryType.Paragraph]: (
103
- <Form.Item {...props}>
104
- <Input.TextArea rows={4}></Input.TextArea>
105
- </Form.Item>
106
- ),
107
- [BeginQueryType.Options]: (
108
- <Form.Item {...props}>
109
- <Select
110
- allowClear
111
- options={q.options?.map((x) => ({ label: x, value: x })) ?? []}
112
- ></Select>
113
- </Form.Item>
114
- ),
115
- [BeginQueryType.File]: (
116
- <React.Fragment key={idx}>
117
- <Form.Item label={q.name} required={!q.optional}>
118
- <div className="relative">
119
- <Form.Item
120
- {...props}
121
- valuePropName="fileList"
122
- getValueFromEvent={normFile}
123
- noStyle
124
- >
125
- <Upload
126
- name="file"
127
- action={api.parse}
128
- multiple
129
- headers={{ [Authorization]: getAuthorization() }}
130
- onChange={onChange(q.optional)}
131
- >
132
- <Button icon={<UploadOutlined />}>
133
- {t('common.upload')}
134
- </Button>
135
- </Upload>
136
- </Form.Item>
137
- <Form.Item
138
- {...pick(props, ['key', 'label', 'rules'])}
139
- required={!q.optional}
140
- className={urlList.length > 0 ? 'mb-1' : ''}
141
- noStyle
142
- >
143
- <PopoverForm visible={visible} switchVisible={switchVisible}>
144
- <Button
145
- onClick={handleShowPopover(idx)}
146
- className="absolute left-1/2 top-0"
147
- icon={<Link className="size-3" />}
148
- >
149
- {t('flow.pasteFileLink')}
150
- </Button>
151
- </PopoverForm>
152
- </Form.Item>
153
- </div>
154
- </Form.Item>
155
- <Form.Item name={idx} noStyle {...pick(props, ['rules'])} />
156
- </React.Fragment>
157
- ),
158
- [BeginQueryType.Integer]: (
159
- <Form.Item {...props}>
160
- <InputNumber></InputNumber>
161
- </Form.Item>
162
- ),
163
- [BeginQueryType.Boolean]: (
164
- <Form.Item valuePropName={'checked'} {...props}>
165
- <Switch></Switch>
166
- </Form.Item>
167
- ),
168
- };
169
-
170
- return BeginQueryTypeMap[q.type as BeginQueryType];
171
- },
172
- [form, handleShowPopover, onChange, switchVisible, t, visible],
173
- );
174
-
175
- const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatModal!);
176
-
177
  const handleRunAgent = useCallback(
178
  (nextValues: Record<string, any>) => {
179
  const currentNodes = updateNodeForm('begin', nextValues, ['query']);
@@ -183,25 +35,12 @@ const RunDrawer = ({
183
  [handleRun, hideModal, updateNodeForm],
184
  );
185
 
186
- const onOk = useCallback(async () => {
187
- const values = await form.validateFields();
188
- const nextValues = Object.entries(values).map(([key, value]) => {
189
- const item = query[Number(key)];
190
- let nextValue = value;
191
- if (Array.isArray(value)) {
192
- nextValue = ``;
193
-
194
- value.forEach((x) => {
195
- nextValue +=
196
- x?.originFileObj instanceof File
197
- ? `${x.name}\n${x.response?.data}\n----\n`
198
- : `${x.url}\n${x.result}\n----\n`;
199
- });
200
- }
201
- return { ...item, value: nextValue };
202
- });
203
- handleRunAgent(nextValues);
204
- }, [form, handleRunAgent, query]);
205
 
206
  return (
207
  <Drawer
@@ -213,39 +52,11 @@ const RunDrawer = ({
213
  width={getDrawerWidth()}
214
  mask={false}
215
  >
216
- <section className={styles.formWrapper}>
217
- <Form.Provider
218
- onFormFinish={(name, { values, forms }) => {
219
- if (name === 'urlForm') {
220
- const { basicForm } = forms;
221
- const urlInfo = basicForm.getFieldValue(currentRecord) || [];
222
- basicForm.setFieldsValue({
223
- [currentRecord]: [...urlInfo, { ...values, name: values.url }],
224
- });
225
- hidePopover();
226
- }
227
- }}
228
- >
229
- <Form
230
- name="basicForm"
231
- autoComplete="off"
232
- layout={'vertical'}
233
- form={form}
234
- >
235
- {query.map((x, idx) => {
236
- return renderWidget(x, idx);
237
- })}
238
- </Form>
239
- </Form.Provider>
240
- </section>
241
- <Button
242
- type={'primary'}
243
- block
244
- onClick={onOk}
245
- disabled={!submittable || isUploading}
246
- >
247
- {t('common.next')}
248
- </Button>
249
  </Drawer>
250
  );
251
  };
 
 
 
 
 
1
  import { IModalProps } from '@/interfaces/common';
2
+ import { Drawer } from 'antd';
3
+ import { useCallback } from 'react';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import { useTranslation } from 'react-i18next';
 
5
  import {
6
  useGetBeginNodeDataQuery,
7
  useSaveGraphBeforeOpeningDebugDrawer,
 
9
  import { BeginQuery } from '../interface';
10
  import useGraphStore from '../store';
11
  import { getDrawerWidth } from '../utils';
 
12
 
13
+ import DebugContent from '../debug-content';
 
 
14
 
15
  const RunDrawer = ({
16
  hideModal,
17
  showModal: showChatModal,
18
  }: IModalProps<any>) => {
19
  const { t } = useTranslation();
 
20
  const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
23
  const query: BeginQuery[] = getBeginNodeDataQuery();
24
 
25
+ const { handleRun, loading } = useSaveGraphBeforeOpeningDebugDrawer(
26
+ showChatModal!,
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  );
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  const handleRunAgent = useCallback(
30
  (nextValues: Record<string, any>) => {
31
  const currentNodes = updateNodeForm('begin', nextValues, ['query']);
 
35
  [handleRun, hideModal, updateNodeForm],
36
  );
37
 
38
+ const onOk = useCallback(
39
+ async (nextValues: any[]) => {
40
+ handleRunAgent(nextValues);
41
+ },
42
+ [handleRunAgent],
43
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  return (
46
  <Drawer
 
52
  width={getDrawerWidth()}
53
  mask={false}
54
  >
55
+ <DebugContent
56
+ ok={onOk}
57
+ parameters={query}
58
+ loading={loading}
59
+ ></DebugContent>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  </Drawer>
61
  );
62
  };
web/src/pages/flow/utils.ts CHANGED
@@ -7,7 +7,12 @@ import pipe from 'lodash/fp/pipe';
7
  import isObject from 'lodash/isObject';
8
  import { Edge, Node, Position } from 'reactflow';
9
  import { v4 as uuidv4 } from 'uuid';
10
- import { CategorizeAnchorPointPositions, NodeMap, Operator } from './constant';
 
 
 
 
 
11
  import { ICategorizeItemResult, IPosition, NodeData } from './interface';
12
 
13
  const buildEdges = (
@@ -124,7 +129,7 @@ export const buildDslComponentsByGraph = (
124
  const components: DSLComponents = {};
125
 
126
  nodes
127
- .filter((x) => x.data.label !== Operator.Note)
128
  .forEach((x) => {
129
  const id = x.id;
130
  const operatorName = x.data.label;
@@ -323,3 +328,7 @@ export const duplicateNodeForm = (nodeData?: NodeData) => {
323
  export const getDrawerWidth = () => {
324
  return window.innerWidth > 1278 ? '40%' : 470;
325
  };
 
 
 
 
 
7
  import isObject from 'lodash/isObject';
8
  import { Edge, Node, Position } from 'reactflow';
9
  import { v4 as uuidv4 } from 'uuid';
10
+ import {
11
+ CategorizeAnchorPointPositions,
12
+ NoDebugOperatorsList,
13
+ NodeMap,
14
+ Operator,
15
+ } from './constant';
16
  import { ICategorizeItemResult, IPosition, NodeData } from './interface';
17
 
18
  const buildEdges = (
 
129
  const components: DSLComponents = {};
130
 
131
  nodes
132
+ ?.filter((x) => x.data.label !== Operator.Note)
133
  .forEach((x) => {
134
  const id = x.id;
135
  const operatorName = x.data.label;
 
328
  export const getDrawerWidth = () => {
329
  return window.innerWidth > 1278 ? '40%' : 470;
330
  };
331
+
332
+ export const needsSingleStepDebugging = (label: string) => {
333
+ return !NoDebugOperatorsList.some((x) => (label as Operator) === x);
334
+ };
web/src/pages/login/index.tsx CHANGED
@@ -168,7 +168,7 @@ const Login = () => {
168
  onClick={toGoogle}
169
  style={{ marginTop: 15 }}
170
  >
171
- <div>
172
  <Icon
173
  icon="local:github"
174
  style={{ verticalAlign: 'middle', marginRight: 5 }}
 
168
  onClick={toGoogle}
169
  style={{ marginTop: 15 }}
170
  >
171
+ <div className="flex items-center">
172
  <Icon
173
  icon="local:github"
174
  style={{ verticalAlign: 'middle', marginRight: 5 }}
web/src/services/flow-service.ts CHANGED
@@ -11,6 +11,8 @@ const {
11
  runCanvas,
12
  listTemplates,
13
  testDbConnect,
 
 
14
  } = api;
15
 
16
  const methods = {
@@ -46,6 +48,14 @@ const methods = {
46
  url: testDbConnect,
47
  method: 'post',
48
  },
 
 
 
 
 
 
 
 
49
  } as const;
50
 
51
  const chatService = registerServer<keyof typeof methods>(methods, request);
 
11
  runCanvas,
12
  listTemplates,
13
  testDbConnect,
14
+ getInputElements,
15
+ debug,
16
  } = api;
17
 
18
  const methods = {
 
48
  url: testDbConnect,
49
  method: 'post',
50
  },
51
+ getInputElements: {
52
+ url: getInputElements,
53
+ method: 'get',
54
+ },
55
+ debugSingle: {
56
+ url: debug,
57
+ method: 'post',
58
+ },
59
  } as const;
60
 
61
  const chatService = registerServer<keyof typeof methods>(methods, request);
web/src/utils/api.ts CHANGED
@@ -118,4 +118,6 @@ export default {
118
  resetCanvas: `${api_host}/canvas/reset`,
119
  runCanvas: `${api_host}/canvas/completion`,
120
  testDbConnect: `${api_host}/canvas/test_db_connect`,
 
 
121
  };
 
118
  resetCanvas: `${api_host}/canvas/reset`,
119
  runCanvas: `${api_host}/canvas/completion`,
120
  testDbConnect: `${api_host}/canvas/test_db_connect`,
121
+ getInputElements: `${api_host}/canvas/input_elements`,
122
+ debug: `${api_host}/canvas/debug`,
123
  };
web/src/utils/request.ts CHANGED
@@ -107,31 +107,25 @@ request.interceptors.response.use(async (response: any, options) => {
107
  return response;
108
  }
109
 
110
- const data: ResponseType = await response.clone().json();
111
-
112
- if (data.code === 401 || data.code === 401) {
 
113
  notification.error({
114
- message: data.message,
115
- description: data.message,
116
  duration: 3,
117
  });
118
  authorizationUtil.removeAll();
119
  history.push('/login'); // Will not jump to the login page
120
- } else if (data.code !== 0) {
121
- if (data.code === 100) {
122
- message.error(data.message);
123
- } else {
124
- notification.error({
125
- message: `${i18n.t('message.hint')} : ${data.code}`,
126
- description: data.message,
127
- duration: 3,
128
- });
129
- }
130
-
131
- return response;
132
- } else {
133
- return response;
134
  }
 
135
  });
136
 
137
  export default request;
 
107
  return response;
108
  }
109
 
110
+ const data: ResponseType = await response?.clone()?.json();
111
+ if (data?.code === 100) {
112
+ message.error(data?.message);
113
+ } else if (data?.code === 401) {
114
  notification.error({
115
+ message: data?.message,
116
+ description: data?.message,
117
  duration: 3,
118
  });
119
  authorizationUtil.removeAll();
120
  history.push('/login'); // Will not jump to the login page
121
+ } else if (data?.code !== 0) {
122
+ notification.error({
123
+ message: `${i18n.t('message.hint')} : ${data?.code}`,
124
+ description: data?.message,
125
+ duration: 3,
126
+ });
 
 
 
 
 
 
 
 
127
  }
128
+ return response;
129
  });
130
 
131
  export default request;