Feat: Translate operator names and allow mailboxes to reference operator names #3221 (#9118)

### 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)
This commit is contained in:
balibabu
2025-07-30 16:16:47 +08:00
committed by GitHub
parent ffff5c2e8c
commit 840abd5239
9 changed files with 91 additions and 42 deletions

View File

@ -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: {

View File

@ -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',

View File

@ -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>
);
})}

View File

@ -573,6 +573,12 @@ export const initialInvokeValues = {
proxy: '',
clean_html: false,
variables: [],
outputs: {
result: {
value: '',
type: 'string',
},
},
};
export const initialTemplateValues = {

View File

@ -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;
}
}

View File

@ -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">

View File

@ -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}

View File

@ -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>

View File

@ -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>
);
}