diff --git a/web/src/components/next-message-item/index.tsx b/web/src/components/next-message-item/index.tsx index 664f46afb..bc01010e3 100644 --- a/web/src/components/next-message-item/index.tsx +++ b/web/src/components/next-message-item/index.tsx @@ -3,7 +3,14 @@ import { MessageType } from '@/constants/chat'; import { useSetModalState } from '@/hooks/common-hooks'; import { IReference, IReferenceChunk } from '@/interfaces/database/chat'; import classNames from 'classnames'; -import { memo, useCallback, useEffect, useMemo, useState } from 'react'; +import { + PropsWithChildren, + memo, + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; import { useFetchDocumentInfosByIds, @@ -23,7 +30,10 @@ import styles from './index.less'; const { Text } = Typography; -interface IProps extends Partial, IRegenerateMessage { +interface IProps + extends Partial, + IRegenerateMessage, + PropsWithChildren { item: IMessage; reference: IReference; loading?: boolean; @@ -52,6 +62,7 @@ const MessageItem = ({ showLikeButton = true, showLoudspeaker = true, visibleAvatar = true, + children, }: IProps) => { const { theme } = useTheme(); const isAssistant = item.role === MessageType.Assistant; @@ -152,12 +163,16 @@ const MessageItem = ({ : styles.messageUserText } > - + {item.data ? ( + children + ) : ( + + )} {isAssistant && referenceDocumentList.length > 0 && ( { if (idx !== -1) { return pre.map((x) => { if (x.id === answer.id) { - return { ...x, content: answer.answer }; + return { ...x, ...answer, content: answer.answer }; } return x; }); diff --git a/web/src/hooks/use-send-message.ts b/web/src/hooks/use-send-message.ts index 7fdf1e21b..35dd81a8e 100644 --- a/web/src/hooks/use-send-message.ts +++ b/web/src/hooks/use-send-message.ts @@ -1,4 +1,5 @@ import { Authorization } from '@/constants/authorization'; +import { BeginQuery } from '@/pages/agent/interface'; import api from '@/utils/api'; import { getAuthorization } from '@/utils/authorization-util'; import { EventSourceParserStream } from 'eventsource-parser/stream'; @@ -31,6 +32,12 @@ export interface INodeData { created_at: number; } +export interface IInputData { + content: string; + inputs: Record; + tips: string; +} + export interface IMessageData { content: string; } @@ -39,6 +46,8 @@ export type INodeEvent = IAnswerEvent; export type IMessageEvent = IAnswerEvent; +export type IInputEvent = IAnswerEvent; + export type IChatEvent = INodeEvent | IMessageEvent; export type IEventList = Array; diff --git a/web/src/interfaces/database/chat.ts b/web/src/interfaces/database/chat.ts index a22cb2f67..a5dabd6f3 100644 --- a/web/src/interfaces/database/chat.ts +++ b/web/src/interfaces/database/chat.ts @@ -73,6 +73,7 @@ export interface Message { prompt?: string; id?: string; audio_binary?: string; + data?: any; } export interface IReferenceChunk { @@ -102,6 +103,7 @@ export interface IAnswer { prompt?: string; id?: string; audio_binary?: string; + data?: any; } export interface Docagg { diff --git a/web/src/pages/agent/canvas/node/begin-node.tsx b/web/src/pages/agent/canvas/node/begin-node.tsx index 10df9eeef..d2f199145 100644 --- a/web/src/pages/agent/canvas/node/begin-node.tsx +++ b/web/src/pages/agent/canvas/node/begin-node.tsx @@ -1,6 +1,6 @@ import { IBeginNode } from '@/interfaces/database/flow'; +import { cn } from '@/lib/utils'; import { NodeProps, Position } from '@xyflow/react'; -import { Flex } from 'antd'; import get from 'lodash/get'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -20,7 +20,7 @@ import { NodeWrapper } from './node-wrapper'; // TODO: do not allow other nodes to connect to this node function InnerBeginNode({ data, id }: NodeProps) { const { t } = useTranslation(); - const query: BeginQuery[] = get(data, 'form.query', []); + const inputs: Record = get(data, 'form.inputs', {}); return ( @@ -39,24 +39,22 @@ function InnerBeginNode({ data, id }: NodeProps) { {t(`flow.begin`)} - - {query.map((x, idx) => { - const Icon = BeginQueryTypeIconMap[x.type as BeginQueryType]; +
+ {Object.entries(inputs).map(([key, val], idx) => { + const Icon = BeginQueryTypeIconMap[val.type as BeginQueryType]; return ( - - - {x.name} - {x.optional ? 'Yes' : 'No'} - + + {val.name} + {val.optional ? 'Yes' : 'No'} + ); })} - +
); } diff --git a/web/src/pages/agent/chat/box.tsx b/web/src/pages/agent/chat/box.tsx index 6daabd17e..63126ac6e 100644 --- a/web/src/pages/agent/chat/box.tsx +++ b/web/src/pages/agent/chat/box.tsx @@ -12,6 +12,7 @@ import { useClickDrawer } from '@/components/pdf-drawer/hooks'; import { useFetchAgent } from '@/hooks/use-agent-request'; import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; import { buildMessageUuidWithRole } from '@/utils/chat'; +import { InputForm } from './input-form'; const AgentChatBox = () => { const { @@ -24,6 +25,7 @@ const AgentChatBox = () => { derivedMessages, reference, stopOutputMessage, + send, } = useSendNextMessage(); const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = @@ -59,7 +61,9 @@ const AgentChatBox = () => { index={i} showLikeButton={false} sendLoading={sendLoading} - > + > + + ); })} diff --git a/web/src/pages/agent/chat/hooks.ts b/web/src/pages/agent/chat/hooks.ts index bff0f4720..4d1972f81 100644 --- a/web/src/pages/agent/chat/hooks.ts +++ b/web/src/pages/agent/chat/hooks.ts @@ -6,6 +6,7 @@ import { import { useFetchAgent } from '@/hooks/use-agent-request'; import { IEventList, + IInputEvent, IMessageEvent, MessageEventType, useSendMessageBySSE, @@ -66,6 +67,21 @@ function findMessageFromList(eventList: IEventList) { }; } +function findInputFromList(eventList: IEventList) { + const inputEvent = eventList.find( + (x) => x.event === MessageEventType.UserInputs, + ) as IInputEvent; + + if (!inputEvent) { + return {}; + } + + return { + id: inputEvent?.message_id, + data: inputEvent?.data, + }; +} + const useGetBeginNodePrologue = () => { const getNode = useGraphStore((state) => state.getNode); @@ -136,10 +152,12 @@ export const useSendNextMessage = () => { useEffect(() => { const { content, id } = findMessageFromList(answerList); + const inputAnswer = findInputFromList(answerList); if (answerList.length > 0) { addNewestOneAnswer({ answer: content, id: id, + ...inputAnswer, }); } }, [answerList, addNewestOneAnswer]); @@ -181,5 +199,6 @@ export const useSendNextMessage = () => { ref, removeMessageById, stopOutputMessage, + send, }; }; diff --git a/web/src/pages/agent/chat/input-form.tsx b/web/src/pages/agent/chat/input-form.tsx new file mode 100644 index 000000000..b82dbaf84 --- /dev/null +++ b/web/src/pages/agent/chat/input-form.tsx @@ -0,0 +1,86 @@ +'use client'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { toast } from 'sonner'; +import { z } from 'zod'; + +import { Button } from '@/components/ui/button'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Message } from '@/interfaces/database/chat'; +import { get } from 'lodash'; +import { useParams } from 'umi'; +import { useSendNextMessage } from './hooks'; + +const FormSchema = z.object({ + username: z.string().min(2, { + message: 'Username must be at least 2 characters.', + }), +}); + +type InputFormProps = Pick, 'send'> & { + message: Message; +}; + +export function InputForm({ send, message }: InputFormProps) { + const form = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues: { + username: '', + }, + }); + + const { id: canvasId } = useParams(); + + function onSubmit(data: z.infer) { + const inputs = get(message, 'data.inputs', {}); + + const nextInputs = Object.entries(inputs).reduce((pre, [key, val]) => { + pre[key] = { ...val, value: data.username }; + + return pre; + }, {}); + + send({ + inputs: nextInputs, + id: canvasId, + }); + + toast('You submitted the following values', { + description: ( +
+          {JSON.stringify(data, null, 2)}
+        
+ ), + }); + } + + return ( +
+ + ( + + Username + + + + + + )} + /> + + + + ); +} diff --git a/web/src/pages/agent/form/begin-form/use-edit-query.ts b/web/src/pages/agent/form/begin-form/use-edit-query.ts index 05b8240f2..80e060411 100644 --- a/web/src/pages/agent/form/begin-form/use-edit-query.ts +++ b/web/src/pages/agent/form/begin-form/use-edit-query.ts @@ -23,10 +23,7 @@ export const useEditQueryRecord = ({ const nextQuery: BeginQuery[] = index > -1 ? inputs.toSpliced(index, 1, record) : [...inputs, record]; - form.setValue('inputs', nextQuery, { - shouldDirty: true, - shouldTouch: true, - }); + form.setValue('inputs', nextQuery); hideModal(); }, @@ -45,11 +42,11 @@ export const useEditQueryRecord = ({ const handleDeleteRecord = useCallback( (idx: number) => { const inputs = form?.getValues('inputs') || []; - const nextQuery = inputs.filter( + const nextInputs = inputs.filter( (item: BeginQuery, index: number) => index !== idx, ); - form.setValue('inputs', nextQuery, { shouldDirty: true }); + form.setValue('inputs', nextInputs); }, [form], ); diff --git a/web/src/pages/agent/form/begin-form/use-watch-change.ts b/web/src/pages/agent/form/begin-form/use-watch-change.ts index 3dc451265..00b40003f 100644 --- a/web/src/pages/agent/form/begin-form/use-watch-change.ts +++ b/web/src/pages/agent/form/begin-form/use-watch-change.ts @@ -17,8 +17,8 @@ export function useWatchFormChange(id?: string, form?: UseFormReturn) { const updateNodeForm = useGraphStore((state) => state.updateNodeForm); useEffect(() => { - if (id && form?.formState.isDirty) { - values = form?.getValues(); + if (id) { + values = form?.getValues() || {}; const nextValues = { ...values, diff --git a/web/src/pages/agent/form/iteration-form/use-watch-form-change.ts b/web/src/pages/agent/form/iteration-form/use-watch-form-change.ts index be6f3fa0d..4a780667e 100644 --- a/web/src/pages/agent/form/iteration-form/use-watch-form-change.ts +++ b/web/src/pages/agent/form/iteration-form/use-watch-form-change.ts @@ -3,7 +3,7 @@ import { UseFormReturn, useWatch } from 'react-hook-form'; import useGraphStore from '../../store'; import { OutputArray, OutputObject } from './interface'; -function transferToObject(list: OutputArray) { +export function transferToObject(list: OutputArray) { return list.reduce((pre, cur) => { pre[cur.name] = { ref: cur.ref, type: cur.type }; return pre; diff --git a/web/src/pages/agent/form/user-fill-up-form/use-watch-change.ts b/web/src/pages/agent/form/user-fill-up-form/use-watch-change.ts index 3dc451265..f6c95d157 100644 --- a/web/src/pages/agent/form/user-fill-up-form/use-watch-change.ts +++ b/web/src/pages/agent/form/user-fill-up-form/use-watch-change.ts @@ -17,8 +17,9 @@ export function useWatchFormChange(id?: string, form?: UseFormReturn) { const updateNodeForm = useGraphStore((state) => state.updateNodeForm); useEffect(() => { - if (id && form?.formState.isDirty) { - values = form?.getValues(); + // TODO: This should only be executed when the form changes + if (id) { + values = form?.getValues() || {}; const nextValues = { ...values,