### 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
| import { useClickDrawer } from '@/components/pdf-drawer/hooks'; | import { useClickDrawer } from '@/components/pdf-drawer/hooks'; | ||||
| import { useFetchAgent } from '@/hooks/use-agent-request'; | import { useFetchAgent } from '@/hooks/use-agent-request'; | ||||
| import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | ||||
| import { Message } from '@/interfaces/database/chat'; | |||||
| import { buildMessageUuidWithRole } from '@/utils/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 AgentChatBox = () => { | ||||
| const { | const { | ||||
| derivedMessages, | derivedMessages, | ||||
| reference, | reference, | ||||
| stopOutputMessage, | stopOutputMessage, | ||||
| send, | |||||
| sendFormMessage, | |||||
| } = useSendNextMessage(); | } = useSendNextMessage(); | ||||
| const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = | const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = | ||||
| useGetFileIcon(); | useGetFileIcon(); | ||||
| const { data: userInfo } = useFetchUserInfo(); | const { data: userInfo } = useFetchUserInfo(); | ||||
| const { data: canvasInfo } = useFetchAgent(); | 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 ( | return ( | ||||
| <> | <> | ||||
| showLikeButton={false} | showLikeButton={false} | ||||
| sendLoading={sendLoading} | sendLoading={sendLoading} | ||||
| > | > | ||||
| <InputForm send={send} message={message}></InputForm> | |||||
| <DebugContent | |||||
| parameters={buildInputList(message)} | |||||
| ok={handleOk(message)} | |||||
| isNext={false} | |||||
| btnText={'Submit'} | |||||
| ></DebugContent> | |||||
| </MessageItem> | </MessageItem> | ||||
| ); | ); | ||||
| })} | })} |
| import { v4 as uuid } from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||
| import { BeginId } from '../constant'; | import { BeginId } from '../constant'; | ||||
| import { AgentChatLogContext } from '../context'; | import { AgentChatLogContext } from '../context'; | ||||
| import { BeginQuery } from '../interface'; | |||||
| import useGraphStore from '../store'; | import useGraphStore from '../store'; | ||||
| import { receiveMessageError } from '../utils'; | import { receiveMessageError } from '../utils'; | ||||
| }); | }); | ||||
| }, [value, done, addNewestOneQuestion, setValue, handleSendMessage]); | }, [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(() => { | useEffect(() => { | ||||
| if (prologue) { | if (prologue) { | ||||
| addNewestOneAnswer({ | addNewestOneAnswer({ | ||||
| removeMessageById, | removeMessageById, | ||||
| stopOutputMessage, | stopOutputMessage, | ||||
| send, | send, | ||||
| sendFormMessage, | |||||
| }; | }; | ||||
| }; | }; |
| import { RAGFlowSelect } from '@/components/ui/select'; | import { RAGFlowSelect } from '@/components/ui/select'; | ||||
| import { Switch } from '@/components/ui/switch'; | import { Switch } from '@/components/ui/switch'; | ||||
| import { Textarea } from '@/components/ui/textarea'; | 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 { 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 { useForm } from 'react-hook-form'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { z } from 'zod'; | import { z } from 'zod'; | ||||
| isNext?: boolean; | isNext?: boolean; | ||||
| loading?: boolean; | loading?: boolean; | ||||
| submitButtonDisabled?: boolean; | submitButtonDisabled?: boolean; | ||||
| btnText?: ReactNode; | |||||
| } | } | ||||
| const values = {}; | const values = {}; | ||||
| isNext = true, | isNext = true, | ||||
| loading = false, | loading = false, | ||||
| submitButtonDisabled = false, | submitButtonDisabled = false, | ||||
| btnText, | |||||
| }: IProps) => { | }: IProps) => { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const FormSchema = useMemo(() => { | 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); | return z.object(obj); | ||||
| }, [parameters]); | }, [parameters]); | ||||
| const form = useForm({ | |||||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||||
| defaultValues: values, | defaultValues: values, | ||||
| resolver: zodResolver(FormSchema), | resolver: zodResolver(FormSchema), | ||||
| }); | }); | ||||
| const { | |||||
| visible, | |||||
| hideModal: hidePopover, | |||||
| switchVisible, | |||||
| showModal: showPopover, | |||||
| } = useSetModalState(); | |||||
| const { setRecord, currentRecord } = useSetSelectedRecord<number>(); | |||||
| // const { submittable } = useHandleSubmittable(form); | |||||
| const submittable = true; | 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( | const renderWidget = useCallback( | ||||
| (q: BeginQuery, idx: string) => { | (q: BeginQuery, idx: string) => { | ||||
| label: q.name ?? q.key, | label: q.name ?? q.key, | ||||
| name: idx, | 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 = { | const BeginQueryTypeMap = { | ||||
| [BeginQueryType.Line]: ( | [BeginQueryType.Line]: ( | ||||
| <RAGFlowSelect | <RAGFlowSelect | ||||
| allowClear | allowClear | ||||
| options={ | options={ | ||||
| q.options?.map((x) => ({ label: x, value: x })) ?? [] | |||||
| q.options?.map((x) => ({ | |||||
| label: x, | |||||
| value: x as string, | |||||
| })) ?? [] | |||||
| } | } | ||||
| {...field} | {...field} | ||||
| ></RAGFlowSelect> | ></RAGFlowSelect> | ||||
| <ButtonLoading | <ButtonLoading | ||||
| type="submit" | type="submit" | ||||
| loading={loading} | loading={loading} | ||||
| disabled={!submittable || isUploading || submitButtonDisabled} | |||||
| disabled={!submittable || submitButtonDisabled} | |||||
| className="w-full" | className="w-full" | ||||
| > | > | ||||
| {t(isNext ? 'common.next' : 'flow.run')} | |||||
| {btnText || t(isNext ? 'common.next' : 'flow.run')} | |||||
| </ButtonLoading> | </ButtonLoading> | ||||
| </form> | </form> | ||||
| </Form> | </Form> |
| import { useSaveGraphBeforeOpeningDebugDrawer } from '../hooks/use-save-graph'; | import { useSaveGraphBeforeOpeningDebugDrawer } from '../hooks/use-save-graph'; | ||||
| import { BeginQuery } from '../interface'; | import { BeginQuery } from '../interface'; | ||||
| import useGraphStore from '../store'; | import useGraphStore from '../store'; | ||||
| import { buildBeginQueryWithObject } from '../utils'; | |||||
| const RunSheet = ({ | const RunSheet = ({ | ||||
| hideModal, | hideModal, | ||||
| const beginNode = getNode(BeginId); | const beginNode = getNode(BeginId); | ||||
| const inputs: Record<string, BeginQuery> = beginNode?.data.form.inputs; | 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']); | const currentNodes = updateNodeForm(BeginId, nextInputs, ['inputs']); | ||||
| handleRun(currentNodes); | handleRun(currentNodes); |
| NodeMap, | NodeMap, | ||||
| Operator, | Operator, | ||||
| } from './constant'; | } from './constant'; | ||||
| import { IPosition } from './interface'; | |||||
| import { BeginQuery, IPosition } from './interface'; | |||||
| const buildEdges = ( | const buildEdges = ( | ||||
| operatorIds: string[], | operatorIds: string[], | ||||
| return nextEdges; | 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; | |||||
| } |