Feat: In a dialog message, users can enter different types of data #3221 (#8583)

### 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)
This commit is contained in:
balibabu
2025-06-30 19:32:40 +08:00
committed by GitHub
parent cf8c063a69
commit d620432e3b
5 changed files with 110 additions and 83 deletions

View File

@ -11,8 +11,14 @@ import PdfDrawer from '@/components/pdf-drawer';
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 {
@ -25,7 +31,7 @@ const AgentChatBox = () => {
derivedMessages, derivedMessages,
reference, reference,
stopOutputMessage, stopOutputMessage,
send, sendFormMessage,
} = useSendNextMessage(); } = useSendNextMessage();
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
@ -33,6 +39,35 @@ const AgentChatBox = () => {
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 (
<> <>
@ -62,7 +97,12 @@ const AgentChatBox = () => {
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>
); );
})} })}

View File

@ -22,6 +22,7 @@ import { useParams } from 'umi';
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';
@ -176,6 +177,19 @@ export const useSendNextMessage = () => {
}); });
}, [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({
@ -200,5 +214,6 @@ export const useSendNextMessage = () => {
removeMessageById, removeMessageById,
stopOutputMessage, stopOutputMessage,
send, send,
sendFormMessage,
}; };
}; };

View File

@ -12,11 +12,8 @@ import { Input } from '@/components/ui/input';
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, { ReactNode, useCallback, useMemo } from 'react';
import React, { useCallback, useMemo, useState } 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';
@ -44,6 +41,7 @@ interface IProps {
isNext?: boolean; isNext?: boolean;
loading?: boolean; loading?: boolean;
submitButtonDisabled?: boolean; submitButtonDisabled?: boolean;
btnText?: ReactNode;
} }
const values = {}; const values = {};
@ -54,76 +52,45 @@ const DebugContent = ({
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 obj = parameters.reduce<Record<string, z.ZodType>>(
const type = cur.type; (pre, cur, idx) => {
let fieldSchema; const type = cur.type;
if (StringFields.some((x) => x === type)) { let fieldSchema;
fieldSchema = z.string(); if (StringFields.some((x) => x === type)) {
} else if (type === BeginQueryType.Boolean) { fieldSchema = z.string();
fieldSchema = z.boolean(); } else if (type === BeginQueryType.Boolean) {
} else if (type === BeginQueryType.Integer) { fieldSchema = z.boolean();
fieldSchema = z.coerce.number(); } else if (type === BeginQueryType.Integer) {
} else { fieldSchema = z.coerce.number();
fieldSchema = z.instanceof(File); } else {
} fieldSchema = z.instanceof(File);
}
if (cur.optional) { if (cur.optional) {
fieldSchema.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) => {
@ -132,14 +99,6 @@ const DebugContent = ({
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]: (
@ -183,7 +142,10 @@ const DebugContent = ({
<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>
@ -295,10 +257,10 @@ const DebugContent = ({
<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>

View File

@ -14,6 +14,7 @@ import { useGetBeginNodeDataQuery } from '../hooks/use-get-begin-query';
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,
@ -34,16 +35,7 @@ const RunSheet = ({
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>>( const nextInputs = buildBeginQueryWithObject(inputs, nextValues);
(pre, key) => {
const item = nextValues.find((x) => x.key === key);
if (item) {
pre[key] = { ...item };
}
return pre;
},
{},
);
const currentNodes = updateNodeForm(BeginId, nextInputs, ['inputs']); const currentNodes = updateNodeForm(BeginId, nextInputs, ['inputs']);
handleRun(currentNodes); handleRun(currentNodes);

View File

@ -19,7 +19,7 @@ import {
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[],
@ -537,3 +537,21 @@ export function mapEdgeMouseEvent(
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;
}