### What problem does this PR solve? Feat: In a dialog message, users can enter different types of data #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.0
| @@ -11,8 +11,14 @@ import PdfDrawer from '@/components/pdf-drawer'; | |||
| import { useClickDrawer } from '@/components/pdf-drawer/hooks'; | |||
| import { useFetchAgent } from '@/hooks/use-agent-request'; | |||
| import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | |||
| import { Message } from '@/interfaces/database/chat'; | |||
| import { buildMessageUuidWithRole } from '@/utils/chat'; | |||
| import { InputForm } from './input-form'; | |||
| import { get } from 'lodash'; | |||
| import { useCallback } from 'react'; | |||
| import { useParams } from 'umi'; | |||
| import DebugContent from '../debug-content'; | |||
| import { BeginQuery } from '../interface'; | |||
| import { buildBeginQueryWithObject } from '../utils'; | |||
| const AgentChatBox = () => { | |||
| const { | |||
| @@ -25,7 +31,7 @@ const AgentChatBox = () => { | |||
| derivedMessages, | |||
| reference, | |||
| stopOutputMessage, | |||
| send, | |||
| sendFormMessage, | |||
| } = useSendNextMessage(); | |||
| const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = | |||
| @@ -33,6 +39,35 @@ const AgentChatBox = () => { | |||
| useGetFileIcon(); | |||
| const { data: userInfo } = useFetchUserInfo(); | |||
| const { data: canvasInfo } = useFetchAgent(); | |||
| const { id: canvasId } = useParams(); | |||
| const getInputs = useCallback((message: Message) => { | |||
| return get(message, 'data.inputs', {}) as Record<string, BeginQuery>; | |||
| }, []); | |||
| const buildInputList = useCallback( | |||
| (message: Message) => { | |||
| return Object.entries(getInputs(message)).map(([key, val]) => { | |||
| return { | |||
| ...val, | |||
| key, | |||
| }; | |||
| }); | |||
| }, | |||
| [getInputs], | |||
| ); | |||
| const handleOk = useCallback( | |||
| (message: Message) => (values: BeginQuery[]) => { | |||
| const inputs = getInputs(message); | |||
| const nextInputs = buildBeginQueryWithObject(inputs, values); | |||
| sendFormMessage({ | |||
| inputs: nextInputs, | |||
| id: canvasId, | |||
| }); | |||
| }, | |||
| [canvasId, getInputs, sendFormMessage], | |||
| ); | |||
| return ( | |||
| <> | |||
| @@ -62,7 +97,12 @@ const AgentChatBox = () => { | |||
| showLikeButton={false} | |||
| sendLoading={sendLoading} | |||
| > | |||
| <InputForm send={send} message={message}></InputForm> | |||
| <DebugContent | |||
| parameters={buildInputList(message)} | |||
| ok={handleOk(message)} | |||
| isNext={false} | |||
| btnText={'Submit'} | |||
| ></DebugContent> | |||
| </MessageItem> | |||
| ); | |||
| })} | |||
| @@ -22,6 +22,7 @@ import { useParams } from 'umi'; | |||
| import { v4 as uuid } from 'uuid'; | |||
| import { BeginId } from '../constant'; | |||
| import { AgentChatLogContext } from '../context'; | |||
| import { BeginQuery } from '../interface'; | |||
| import useGraphStore from '../store'; | |||
| import { receiveMessageError } from '../utils'; | |||
| @@ -176,6 +177,19 @@ export const useSendNextMessage = () => { | |||
| }); | |||
| }, [value, done, addNewestOneQuestion, setValue, handleSendMessage]); | |||
| const sendFormMessage = useCallback( | |||
| (body: { id?: string; inputs: Record<string, BeginQuery> }) => { | |||
| send(body); | |||
| addNewestOneQuestion({ | |||
| content: Object.entries(body.inputs) | |||
| .map(([key, val]) => `${key}: ${val.value}`) | |||
| .join('<br/>'), | |||
| role: MessageType.User, | |||
| }); | |||
| }, | |||
| [addNewestOneQuestion, send], | |||
| ); | |||
| useEffect(() => { | |||
| if (prologue) { | |||
| addNewestOneAnswer({ | |||
| @@ -200,5 +214,6 @@ export const useSendNextMessage = () => { | |||
| removeMessageById, | |||
| stopOutputMessage, | |||
| send, | |||
| sendFormMessage, | |||
| }; | |||
| }; | |||
| @@ -12,11 +12,8 @@ import { Input } from '@/components/ui/input'; | |||
| import { RAGFlowSelect } from '@/components/ui/select'; | |||
| import { Switch } from '@/components/ui/switch'; | |||
| import { Textarea } from '@/components/ui/textarea'; | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useSetSelectedRecord } from '@/hooks/logic-hooks'; | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import { UploadChangeParam, UploadFile } from 'antd/es/upload'; | |||
| import React, { useCallback, useMemo, useState } from 'react'; | |||
| import React, { ReactNode, useCallback, useMemo } from 'react'; | |||
| import { useForm } from 'react-hook-form'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { z } from 'zod'; | |||
| @@ -44,6 +41,7 @@ interface IProps { | |||
| isNext?: boolean; | |||
| loading?: boolean; | |||
| submitButtonDisabled?: boolean; | |||
| btnText?: ReactNode; | |||
| } | |||
| const values = {}; | |||
| @@ -54,76 +52,45 @@ const DebugContent = ({ | |||
| isNext = true, | |||
| loading = false, | |||
| submitButtonDisabled = false, | |||
| btnText, | |||
| }: IProps) => { | |||
| const { t } = useTranslation(); | |||
| const FormSchema = useMemo(() => { | |||
| const obj = parameters.reduce((pre, cur, idx) => { | |||
| const type = cur.type; | |||
| let fieldSchema; | |||
| if (StringFields.some((x) => x === type)) { | |||
| fieldSchema = z.string(); | |||
| } else if (type === BeginQueryType.Boolean) { | |||
| fieldSchema = z.boolean(); | |||
| } else if (type === BeginQueryType.Integer) { | |||
| fieldSchema = z.coerce.number(); | |||
| } else { | |||
| fieldSchema = z.instanceof(File); | |||
| } | |||
| const obj = parameters.reduce<Record<string, z.ZodType>>( | |||
| (pre, cur, idx) => { | |||
| const type = cur.type; | |||
| let fieldSchema; | |||
| if (StringFields.some((x) => x === type)) { | |||
| fieldSchema = z.string(); | |||
| } else if (type === BeginQueryType.Boolean) { | |||
| fieldSchema = z.boolean(); | |||
| } else if (type === BeginQueryType.Integer) { | |||
| fieldSchema = z.coerce.number(); | |||
| } else { | |||
| fieldSchema = z.instanceof(File); | |||
| } | |||
| if (cur.optional) { | |||
| fieldSchema.optional(); | |||
| } | |||
| if (cur.optional) { | |||
| fieldSchema.optional(); | |||
| } | |||
| pre[idx.toString()] = fieldSchema; | |||
| pre[idx.toString()] = fieldSchema; | |||
| return pre; | |||
| }, {}); | |||
| return pre; | |||
| }, | |||
| {}, | |||
| ); | |||
| return z.object(obj); | |||
| }, [parameters]); | |||
| const form = useForm({ | |||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||
| defaultValues: values, | |||
| resolver: zodResolver(FormSchema), | |||
| }); | |||
| const { | |||
| visible, | |||
| hideModal: hidePopover, | |||
| switchVisible, | |||
| showModal: showPopover, | |||
| } = useSetModalState(); | |||
| const { setRecord, currentRecord } = useSetSelectedRecord<number>(); | |||
| // const { submittable } = useHandleSubmittable(form); | |||
| const submittable = true; | |||
| 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: string) => { | |||
| @@ -132,14 +99,6 @@ const DebugContent = ({ | |||
| 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 urlList: { url: string; result: string }[] = []; | |||
| const BeginQueryTypeMap = { | |||
| [BeginQueryType.Line]: ( | |||
| @@ -183,7 +142,10 @@ const DebugContent = ({ | |||
| <RAGFlowSelect | |||
| allowClear | |||
| options={ | |||
| q.options?.map((x) => ({ label: x, value: x })) ?? [] | |||
| q.options?.map((x) => ({ | |||
| label: x, | |||
| value: x as string, | |||
| })) ?? [] | |||
| } | |||
| {...field} | |||
| ></RAGFlowSelect> | |||
| @@ -295,10 +257,10 @@ const DebugContent = ({ | |||
| <ButtonLoading | |||
| type="submit" | |||
| loading={loading} | |||
| disabled={!submittable || isUploading || submitButtonDisabled} | |||
| disabled={!submittable || submitButtonDisabled} | |||
| className="w-full" | |||
| > | |||
| {t(isNext ? 'common.next' : 'flow.run')} | |||
| {btnText || t(isNext ? 'common.next' : 'flow.run')} | |||
| </ButtonLoading> | |||
| </form> | |||
| </Form> | |||
| @@ -14,6 +14,7 @@ import { useGetBeginNodeDataQuery } from '../hooks/use-get-begin-query'; | |||
| import { useSaveGraphBeforeOpeningDebugDrawer } from '../hooks/use-save-graph'; | |||
| import { BeginQuery } from '../interface'; | |||
| import useGraphStore from '../store'; | |||
| import { buildBeginQueryWithObject } from '../utils'; | |||
| const RunSheet = ({ | |||
| hideModal, | |||
| @@ -34,16 +35,7 @@ const RunSheet = ({ | |||
| const beginNode = getNode(BeginId); | |||
| const inputs: Record<string, BeginQuery> = beginNode?.data.form.inputs; | |||
| const nextInputs = Object.keys(inputs).reduce<Record<string, BeginQuery>>( | |||
| (pre, key) => { | |||
| const item = nextValues.find((x) => x.key === key); | |||
| if (item) { | |||
| pre[key] = { ...item }; | |||
| } | |||
| return pre; | |||
| }, | |||
| {}, | |||
| ); | |||
| const nextInputs = buildBeginQueryWithObject(inputs, nextValues); | |||
| const currentNodes = updateNodeForm(BeginId, nextInputs, ['inputs']); | |||
| handleRun(currentNodes); | |||
| @@ -19,7 +19,7 @@ import { | |||
| NodeMap, | |||
| Operator, | |||
| } from './constant'; | |||
| import { IPosition } from './interface'; | |||
| import { BeginQuery, IPosition } from './interface'; | |||
| const buildEdges = ( | |||
| operatorIds: string[], | |||
| @@ -537,3 +537,21 @@ export function mapEdgeMouseEvent( | |||
| return nextEdges; | |||
| } | |||
| export function buildBeginQueryWithObject( | |||
| inputs: Record<string, BeginQuery>, | |||
| values: BeginQuery[], | |||
| ) { | |||
| const nextInputs = Object.keys(inputs).reduce<Record<string, BeginQuery>>( | |||
| (pre, key) => { | |||
| const item = values.find((x) => x.key === key); | |||
| if (item) { | |||
| pre[key] = { ...item }; | |||
| } | |||
| return pre; | |||
| }, | |||
| {}, | |||
| ); | |||
| return nextInputs; | |||
| } | |||