### What problem does this PR solve? Feat: Convert the prompt field of the agent operator to an array #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.19.1
| @@ -14,10 +14,13 @@ import { Message } from '@/interfaces/database/chat'; | |||
| import i18n from '@/locales/config'; | |||
| import api from '@/utils/api'; | |||
| import { message } from 'antd'; | |||
| import { get } from 'lodash'; | |||
| import trim from 'lodash/trim'; | |||
| import { useCallback, useEffect } from 'react'; | |||
| import { useCallback, useEffect, useMemo } from 'react'; | |||
| import { useParams } from 'umi'; | |||
| import { v4 as uuid } from 'uuid'; | |||
| import { BeginId } from '../constant'; | |||
| import useGraphStore from '../store'; | |||
| import { receiveMessageError } from '../utils'; | |||
| const antMessage = message; | |||
| @@ -56,6 +59,17 @@ function findMessageFromList(eventList: IEventList) { | |||
| return event?.data?.content; | |||
| } | |||
| const useGetBeginNodePrologue = () => { | |||
| const getNode = useGraphStore((state) => state.getNode); | |||
| return useMemo(() => { | |||
| const formData = get(getNode(BeginId), 'data.form', {}); | |||
| if (formData?.enablePrologue) { | |||
| return formData?.prologue; | |||
| } | |||
| }, [getNode]); | |||
| }; | |||
| export const useSendNextMessage = () => { | |||
| const { | |||
| reference, | |||
| @@ -75,6 +89,8 @@ export const useSendNextMessage = () => { | |||
| api.runCanvas, | |||
| ); | |||
| const prologue = useGetBeginNodePrologue(); | |||
| const sendMessage = useCallback( | |||
| async ({ message }: { message: Message; messages?: Message[] }) => { | |||
| const params: Record<string, unknown> = { | |||
| @@ -138,19 +154,18 @@ export const useSendNextMessage = () => { | |||
| }); | |||
| }, [addNewestQuestion, handleSendMessage, done, setValue, value]); | |||
| const fetchPrologue = useCallback(async () => { | |||
| // fetch prologue | |||
| const sendRet = await send({ id: agentId }); | |||
| if (receiveMessageError(sendRet)) { | |||
| message.error(sendRet?.data?.message); | |||
| } else { | |||
| refetch(); | |||
| } | |||
| }, [agentId, refetch, send]); | |||
| useEffect(() => { | |||
| fetchPrologue(); | |||
| }, [fetchPrologue]); | |||
| if (prologue) { | |||
| addNewestAnswer({ | |||
| answer: prologue, | |||
| reference: { | |||
| chunks: [], | |||
| doc_aggs: [], | |||
| total: 0, | |||
| }, | |||
| }); | |||
| } | |||
| }, [addNewestAnswer, prologue]); | |||
| return { | |||
| handlePressEnter, | |||
| @@ -0,0 +1,4 @@ | |||
| export enum PromptRole { | |||
| User = 'user', | |||
| Assistant = 'assistant', | |||
| } | |||
| @@ -12,11 +12,7 @@ import { memo } from 'react'; | |||
| import { useFieldArray, useFormContext } from 'react-hook-form'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { PromptEditor } from '../components/prompt-editor'; | |||
| export enum PromptRole { | |||
| User = 'user', | |||
| Assistant = 'assistant', | |||
| } | |||
| import { PromptRole } from './constant'; | |||
| const options = [ | |||
| { label: 'User', value: PromptRole.User }, | |||
| @@ -2,6 +2,7 @@ import { FormContainer } from '@/components/form-container'; | |||
| import { LargeModelFormField } from '@/components/large-model-form-field'; | |||
| import { LlmSettingSchema } from '@/components/llm-setting-items/next'; | |||
| import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item'; | |||
| import { BlockButton } from '@/components/ui/button'; | |||
| import { | |||
| Form, | |||
| FormControl, | |||
| @@ -9,30 +10,29 @@ import { | |||
| FormItem, | |||
| FormLabel, | |||
| } from '@/components/ui/form'; | |||
| import { useFetchModelId } from '@/hooks/logic-hooks'; | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import { useMemo } from 'react'; | |||
| import { useForm } from 'react-hook-form'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { z } from 'zod'; | |||
| import { initialAgentValues } from '../../constant'; | |||
| import { useFormValues } from '../../hooks/use-form-values'; | |||
| import { useWatchFormChange } from '../../hooks/use-watch-form-change'; | |||
| import { INextOperatorForm } from '../../interface'; | |||
| import { Output } from '../components/output'; | |||
| import { PromptEditor } from '../components/prompt-editor'; | |||
| import DynamicPrompt from './dynamic-prompt'; | |||
| import { useValues } from './use-values'; | |||
| import { useWatchFormChange } from './use-watch-change'; | |||
| const FormSchema = z.object({ | |||
| sys_prompt: z.string(), | |||
| prompts: z | |||
| .array( | |||
| z.object({ | |||
| role: z.string(), | |||
| content: z.string(), | |||
| }), | |||
| ) | |||
| .optional(), | |||
| prompts: z.string().optional(), | |||
| // prompts: z | |||
| // .array( | |||
| // z.object({ | |||
| // role: z.string(), | |||
| // content: z.string(), | |||
| // }), | |||
| // ) | |||
| // .optional(), | |||
| message_history_window_size: z.coerce.number(), | |||
| tools: z | |||
| .array( | |||
| @@ -46,11 +46,8 @@ const FormSchema = z.object({ | |||
| const AgentForm = ({ node }: INextOperatorForm) => { | |||
| const { t } = useTranslation(); | |||
| const llmId = useFetchModelId(); | |||
| const defaultValues = useFormValues( | |||
| { ...initialAgentValues, llm_id: llmId }, | |||
| node, | |||
| ); | |||
| const defaultValues = useValues(node); | |||
| const outputList = useMemo(() => { | |||
| return [ | |||
| @@ -94,8 +91,22 @@ const AgentForm = ({ node }: INextOperatorForm) => { | |||
| <MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField> | |||
| </FormContainer> | |||
| <FormContainer> | |||
| <DynamicPrompt></DynamicPrompt> | |||
| {/* <DynamicPrompt></DynamicPrompt> */} | |||
| <FormField | |||
| control={form.control} | |||
| name={`prompts`} | |||
| render={({ field }) => ( | |||
| <FormItem className="flex-1"> | |||
| <FormControl> | |||
| <section> | |||
| <PromptEditor {...field} showToolbar={false}></PromptEditor> | |||
| </section> | |||
| </FormControl> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| </FormContainer> | |||
| <BlockButton>Add Agent</BlockButton> | |||
| <Output list={outputList}></Output> | |||
| </form> | |||
| </Form> | |||
| @@ -0,0 +1,30 @@ | |||
| import { useFetchModelId } from '@/hooks/logic-hooks'; | |||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||
| import { get, isEmpty } from 'lodash'; | |||
| import { useMemo } from 'react'; | |||
| import { initialAgentValues } from '../../constant'; | |||
| export function useValues(node?: RAGFlowNodeType) { | |||
| const llmId = useFetchModelId(); | |||
| const defaultValues = useMemo( | |||
| () => ({ | |||
| ...initialAgentValues, | |||
| llm_id: llmId, | |||
| prompts: '', | |||
| }), | |||
| [llmId], | |||
| ); | |||
| const values = useMemo(() => { | |||
| const formData = node?.data?.form; | |||
| if (isEmpty(formData)) { | |||
| return defaultValues; | |||
| } | |||
| return { ...formData, prompts: get(formData, 'prompts.0.content', '') }; | |||
| }, [defaultValues, node?.data?.form]); | |||
| return values; | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| import { useEffect } from 'react'; | |||
| import { UseFormReturn, useWatch } from 'react-hook-form'; | |||
| import useGraphStore from '../../store'; | |||
| import { PromptRole } from './constant'; | |||
| export function useWatchFormChange(id?: string, form?: UseFormReturn) { | |||
| let values = useWatch({ control: form?.control }); | |||
| const updateNodeForm = useGraphStore((state) => state.updateNodeForm); | |||
| useEffect(() => { | |||
| // Manually triggered form updates are synchronized to the canvas | |||
| if (id && form?.formState.isDirty) { | |||
| values = form?.getValues(); | |||
| let nextValues: any = { | |||
| ...values, | |||
| prompts: [{ role: PromptRole.User, content: values.prompts }], | |||
| }; | |||
| updateNodeForm(id, nextValues); | |||
| } | |||
| }, [form?.formState.isDirty, id, updateNodeForm, values]); | |||
| } | |||
| @@ -56,10 +56,13 @@ function filterAllUpstreamNodeIds(edges: Edge[], nodeIds: string[]) { | |||
| }, []); | |||
| } | |||
| function buildOutputOptions(outputs: Record<string, any> = {}) { | |||
| function buildOutputOptions( | |||
| outputs: Record<string, any> = {}, | |||
| nodeId?: string, | |||
| ) { | |||
| return Object.keys(outputs).map((x) => ({ | |||
| label: x, | |||
| value: x, | |||
| value: `${nodeId}@${x}`, | |||
| })); | |||
| } | |||
| @@ -84,7 +87,7 @@ export function useBuildNodeOutputOptions(nodeId?: string) { | |||
| label: x.data.name, | |||
| value: x.id, | |||
| title: x.data.name, | |||
| options: buildOutputOptions(x.data.form.outputs), | |||
| options: buildOutputOptions(x.data.form.outputs, x.id), | |||
| })); | |||
| }, [edges, nodeId, nodes]); | |||