### What problem does this PR solve? Feat: Translate operator names and allow mailboxes to reference operator names #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.0
| @@ -38,6 +38,7 @@ export default { | |||
| previousPage: 'Previous', | |||
| nextPage: 'Next', | |||
| add: 'Add', | |||
| promptPlaceholder: `Please input or use / to quickly insert variables.`, | |||
| }, | |||
| login: { | |||
| login: 'Sign in', | |||
| @@ -1156,7 +1157,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s | |||
| note: 'Note', | |||
| noteDescription: 'Note', | |||
| notePlaceholder: 'Please enter a note', | |||
| invoke: 'Invoke', | |||
| invoke: 'HTTP Request', | |||
| invokeDescription: `A component capable of calling remote services, using other components' outputs or constants as inputs.`, | |||
| url: 'Url', | |||
| method: 'Method', | |||
| @@ -1207,10 +1208,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s | |||
| jsonUploadTypeErrorMessage: 'Please upload json file', | |||
| jsonUploadContentErrorMessage: 'json file error', | |||
| iteration: 'Iteration', | |||
| iterationDescription: `This component firstly split the input into array by "delimiter". | |||
| Perform the same operation steps on the elements in the array in sequence until all results are output, which can be understood as a task batch processor. | |||
| For example, within the long text translation iteration node, if all content is input to the LLM node, the single conversation limit may be reached. The upstream node can first split the long text into multiple fragments, and cooperate with the iterative node to perform batch translation on each fragment to avoid reaching the LLM message limit for a single conversation.`, | |||
| iterationDescription: `A looping component that iterates over an input array and executes a defined logic for each item.`, | |||
| delimiterTip: ` | |||
| This delimiter is used to split the input text into several text pieces echo of which will be performed as input item of each iteration.`, | |||
| delimiterOptions: { | |||
| @@ -1297,8 +1295,8 @@ This delimiter is used to split the input text into several text pieces echo of | |||
| 'Builds agent components equipped with reasoning, tool usage, and multi-agent collaboration. ', | |||
| maxRecords: 'Max records', | |||
| createAgent: 'Create Agent', | |||
| stringTransform: 'String transform', | |||
| userFillUp: 'Input', | |||
| stringTransform: 'Text Processing', | |||
| userFillUp: 'Await Response', | |||
| codeExec: 'Code', | |||
| tavilySearch: 'Tavily Search', | |||
| tavilySearchDescription: 'Search results via Tavily service.', | |||
| @@ -1309,6 +1307,7 @@ This delimiter is used to split the input text into several text pieces echo of | |||
| import: 'Import', | |||
| export: 'Export', | |||
| seconds: 'Seconds', | |||
| subject: 'Subject', | |||
| }, | |||
| llmTools: { | |||
| bad_calculator: { | |||
| @@ -1150,7 +1150,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 | |||
| note: '注释', | |||
| noteDescription: '注释', | |||
| notePlaceholder: '请输入注释', | |||
| invoke: 'Invoke', | |||
| invoke: 'HTTP 请求', | |||
| invokeDescription: | |||
| '该组件可以调用远程端点调用。将其他组件的输出作为参数或设置常量参数来调用远程函数。', | |||
| url: 'Url', | |||
| @@ -1200,7 +1200,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 | |||
| jsonUploadTypeErrorMessage: '请上传json文件', | |||
| jsonUploadContentErrorMessage: 'json 文件错误', | |||
| iteration: '循环', | |||
| iterationDescription: `该组件首先将输入以“分隔符”分割成数组,然后依次对数组中的元素执行相同的操作步骤,直到输出所有结果,可以理解为一个任务批处理器。例如在长文本翻译迭代节点中,如果所有内容都输入到LLM节点,可能会达到单次对话的限制,上游节点可以先将长文本分割成多个片段,配合迭代节点对每个片段进行批量翻译,避免达到单次对话的LLM消息限制。`, | |||
| iterationDescription: `该组件负责迭代生成新的内容,对列表对象执行多次步骤直至输出所有结果。`, | |||
| delimiterTip: `该分隔符用于将输入文本分割成几个文本片段,每个文本片段的回显将作为每次迭代的输入项。`, | |||
| delimiterOptions: { | |||
| comma: '逗号', | |||
| @@ -1260,6 +1260,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 | |||
| management: '管理', | |||
| import: '导入', | |||
| export: '导出', | |||
| subject: '主题', | |||
| }, | |||
| footer: { | |||
| profile: 'All rights reserved @ React', | |||
| @@ -15,7 +15,9 @@ import { IModalProps } from '@/interfaces/common'; | |||
| import { Operator } from '@/pages/agent/constant'; | |||
| import { AgentInstanceContext, HandleContext } from '@/pages/agent/context'; | |||
| import OperatorIcon from '@/pages/agent/operator-icon'; | |||
| import { lowerFirst } from 'lodash'; | |||
| import { PropsWithChildren, createContext, useContext } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| type OperatorItemProps = { operators: Operator[] }; | |||
| @@ -25,6 +27,7 @@ function OperatorItemList({ operators }: OperatorItemProps) { | |||
| const { addCanvasNode } = useContext(AgentInstanceContext); | |||
| const { nodeId, id, position } = useContext(HandleContext); | |||
| const hideModal = useContext(HideModalContext); | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <ul className="space-y-2"> | |||
| @@ -41,7 +44,7 @@ function OperatorItemList({ operators }: OperatorItemProps) { | |||
| onSelect={() => hideModal?.()} | |||
| > | |||
| <OperatorIcon name={x}></OperatorIcon> | |||
| {x} | |||
| {t(`flow.${lowerFirst(x)}`)} | |||
| </DropdownMenuItem> | |||
| ); | |||
| })} | |||
| @@ -573,6 +573,12 @@ export const initialInvokeValues = { | |||
| proxy: '', | |||
| clean_html: false, | |||
| variables: [], | |||
| outputs: { | |||
| result: { | |||
| value: '', | |||
| type: 'string', | |||
| }, | |||
| }, | |||
| }; | |||
| export const initialTemplateValues = { | |||
| @@ -1,21 +0,0 @@ | |||
| .title { | |||
| flex-basis: 60px; | |||
| } | |||
| .formWrapper { | |||
| :global(.ant-form-item-label) { | |||
| font-weight: 600; | |||
| } | |||
| } | |||
| .operatorDescription { | |||
| font-size: 14px; | |||
| padding-top: 16px; | |||
| font-weight: normal; | |||
| } | |||
| .formDrawer { | |||
| :global(.ant-drawer-content-wrapper) { | |||
| transform: translateX(0) !important; | |||
| } | |||
| } | |||
| @@ -103,7 +103,11 @@ const FormSheet = ({ | |||
| )} | |||
| <X onClick={hideModal} /> | |||
| </div> | |||
| <span>{t(`${lowerFirst(operatorName)}Description`)}</span> | |||
| <span> | |||
| {t( | |||
| `${lowerFirst(operatorName === Operator.Tool ? clickedToolId : operatorName)}Description`, | |||
| )} | |||
| </span> | |||
| </section> | |||
| </SheetHeader> | |||
| <section className="pt-4 overflow-auto flex-1"> | |||
| @@ -46,7 +46,7 @@ const Nodes: Array<Klass<LexicalNode>> = [ | |||
| VariableNode, | |||
| ]; | |||
| type PromptContentProps = { showToolbar?: boolean }; | |||
| type PromptContentProps = { showToolbar?: boolean; multiLine?: boolean }; | |||
| type IProps = { | |||
| value?: string; | |||
| @@ -54,7 +54,10 @@ type IProps = { | |||
| placeholder?: ReactNode; | |||
| } & PromptContentProps; | |||
| function PromptContent({ showToolbar = true }: PromptContentProps) { | |||
| function PromptContent({ | |||
| showToolbar = true, | |||
| multiLine = true, | |||
| }: PromptContentProps) { | |||
| const [editor] = useLexicalComposerContext(); | |||
| const [isBlur, setIsBlur] = useState(false); | |||
| const { t } = useTranslation(); | |||
| @@ -100,7 +103,9 @@ function PromptContent({ showToolbar = true }: PromptContentProps) { | |||
| </div> | |||
| )} | |||
| <ContentEditable | |||
| className="min-h-40 relative px-2 py-1 focus-visible:outline-none" | |||
| className={cn('relative px-2 py-1 focus-visible:outline-none', { | |||
| 'min-h-40': multiLine, | |||
| })} | |||
| onBlur={handleBlur} | |||
| onFocus={handleFocus} | |||
| /> | |||
| @@ -113,6 +118,7 @@ export function PromptEditor({ | |||
| onChange, | |||
| placeholder, | |||
| showToolbar, | |||
| multiLine = true, | |||
| }: IProps) { | |||
| const { t } = useTranslation(); | |||
| const initialConfig: InitialConfigType = { | |||
| @@ -142,14 +148,22 @@ export function PromptEditor({ | |||
| <LexicalComposer initialConfig={initialConfig}> | |||
| <RichTextPlugin | |||
| contentEditable={ | |||
| <PromptContent showToolbar={showToolbar}></PromptContent> | |||
| <PromptContent | |||
| showToolbar={showToolbar} | |||
| multiLine={multiLine} | |||
| ></PromptContent> | |||
| } | |||
| placeholder={ | |||
| <div | |||
| className="absolute top-10 left-2 text-text-sub-title" | |||
| className={cn( | |||
| 'absolute top-1 left-2 text-text-sub-title pointer-events-none', | |||
| { | |||
| 'truncate w-[90%]': !multiLine, | |||
| }, | |||
| )} | |||
| data-xxx | |||
| > | |||
| {placeholder || t('common.pleaseInput')} | |||
| {placeholder || t('common.promptPlaceholder')} | |||
| </div> | |||
| } | |||
| ErrorBoundary={LexicalErrorBoundary} | |||
| @@ -20,6 +20,7 @@ import { INextOperatorForm } from '../../interface'; | |||
| import { buildOutputList } from '../../utils/build-output-list'; | |||
| import { FormWrapper } from '../components/form-wrapper'; | |||
| import { Output } from '../components/output'; | |||
| import { PromptEditor } from '../components/prompt-editor'; | |||
| interface InputFormFieldProps { | |||
| name: string; | |||
| @@ -47,6 +48,29 @@ function InputFormField({ name, label, type }: InputFormFieldProps) { | |||
| ); | |||
| } | |||
| function PromptFormField({ name, label }: InputFormFieldProps) { | |||
| const form = useFormContext(); | |||
| return ( | |||
| <FormField | |||
| control={form.control} | |||
| name={name} | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{label}</FormLabel> | |||
| <FormControl> | |||
| <PromptEditor | |||
| {...field} | |||
| showToolbar={false} | |||
| multiLine={false} | |||
| ></PromptEditor> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| ); | |||
| } | |||
| export function EmailFormWidgets() { | |||
| const { t } = useTranslate('flow'); | |||
| @@ -108,10 +132,22 @@ const EmailForm = ({ node }: INextOperatorForm) => { | |||
| <Form {...form}> | |||
| <FormWrapper> | |||
| <FormContainer> | |||
| <InputFormField name="to_email" label={t('toEmail')}></InputFormField> | |||
| <InputFormField name="cc_email" label={t('ccEmail')}></InputFormField> | |||
| <InputFormField name="content" label={t('content')}></InputFormField> | |||
| <InputFormField name="subject" label={t('subject')}></InputFormField> | |||
| <PromptFormField | |||
| name="to_email" | |||
| label={t('toEmail')} | |||
| ></PromptFormField> | |||
| <PromptFormField | |||
| name="cc_email" | |||
| label={t('ccEmail')} | |||
| ></PromptFormField> | |||
| <PromptFormField | |||
| name="content" | |||
| label={t('content')} | |||
| ></PromptFormField> | |||
| <PromptFormField | |||
| name="subject" | |||
| label={t('subject')} | |||
| ></PromptFormField> | |||
| <EmailFormWidgets></EmailFormWidgets> | |||
| </FormContainer> | |||
| </FormWrapper> | |||
| @@ -23,7 +23,9 @@ import { initialInvokeValues } from '../../constant'; | |||
| import { useFormValues } from '../../hooks/use-form-values'; | |||
| import { useWatchFormChange } from '../../hooks/use-watch-form-change'; | |||
| import { INextOperatorForm } from '../../interface'; | |||
| import { buildOutputList } from '../../utils/build-output-list'; | |||
| import { FormWrapper } from '../components/form-wrapper'; | |||
| import { Output } from '../components/output'; | |||
| import { FormSchema, FormSchemaType } from './schema'; | |||
| import { useEditVariableRecord } from './use-edit-variable'; | |||
| import { VariableDialog } from './variable-dialog'; | |||
| @@ -56,6 +58,8 @@ const TimeoutInput = ({ value, onChange }: TimeoutInputProps) => { | |||
| ); | |||
| }; | |||
| const outputList = buildOutputList(initialInvokeValues.outputs); | |||
| function InvokeForm({ node }: INextOperatorForm) { | |||
| const { t } = useTranslation(); | |||
| const defaultValues = useFormValues(initialInvokeValues, node); | |||
| @@ -212,6 +216,9 @@ function InvokeForm({ node }: INextOperatorForm) { | |||
| ></VariableDialog> | |||
| )} | |||
| </FormWrapper> | |||
| <div className="p-5"> | |||
| <Output list={outputList}></Output> | |||
| </div> | |||
| </Form> | |||
| ); | |||
| } | |||