Feat: Edit the code of the code operator from a broad perspective. (#14116)

### What problem does this PR solve?

Feat: Edit the code of the code operator from a broad perspective.
### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2026-04-15 11:51:17 +08:00
committed by GitHub
parent 2d291cd841
commit f08d13287a
4 changed files with 193 additions and 105 deletions

View File

@@ -0,0 +1,47 @@
import { Button } from '@/components/ui/button';
import Editor from '@monaco-editor/react';
import { Minimize2 } from 'lucide-react';
import { CodeEditorOptions } from './monaco-config';
interface ExpandedEditorProps {
visible: boolean;
onClose: () => void;
theme: string;
language: string;
value: string;
onChange: (value: string) => void;
}
export function ExpandedEditor({
visible,
onClose,
theme,
language,
value,
onChange,
}: ExpandedEditorProps) {
if (!visible) return null;
return (
<div className="absolute inset-0 z-10 flex flex-col bg-bg-base">
<div className="flex items-center justify-between border-b px-5 py-3">
<span className="font-medium">Code</span>
<Button variant="ghost" onClick={onClose} type="button">
<Minimize2 className="size-4" />
</Button>
</div>
<div className="min-h-0 flex-1 p-4">
<Editor
height="100%"
theme={theme}
language={language}
options={CodeEditorOptions}
value={value}
onChange={(val) => {
onChange(val ?? '');
}}
/>
</div>
</div>
);
}

View File

@@ -3,6 +3,7 @@ import { INextOperatorForm } from '../../interface';
import { FormContainer } from '@/components/form-container';
import { useIsDarkTheme } from '@/components/theme-provider';
import { Button } from '@/components/ui/button';
import {
Form,
FormControl,
@@ -16,13 +17,15 @@ import { RAGFlowSelect } from '@/components/ui/select';
import { ProgrammingLanguage } from '@/constants/agent';
import { ICodeForm } from '@/interfaces/database/agent';
import { zodResolver } from '@hookform/resolvers/zod';
import { AlertTriangle } from 'lucide-react';
import { memo } from 'react';
import { AlertTriangle, Maximize2 } from 'lucide-react';
import { memo, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { buildOutputList } from '../../utils/build-output-list';
import { FormWrapper } from '../components/form-wrapper';
import { Output } from '../components/output';
import { ExpandedEditor } from './expanded-editor';
import { CodeEditorOptions, RAGFlowMonacoTheme } from './monaco-config';
import {
DynamicInputVariable,
TypeOptions,
@@ -47,6 +50,8 @@ const options = [
ProgrammingLanguage.Javascript,
].map((x) => ({ value: x, label: x }));
const ScriptFieldName = 'script';
function CodeForm({ node }: INextOperatorForm) {
const formData = node?.data.form as ICodeForm;
const { t } = useTranslation();
@@ -61,6 +66,7 @@ function CodeForm({ node }: INextOperatorForm) {
useWatchFormChange(node?.id, form);
const handleLanguageChange = useHandleLanguageChange(node?.id, form);
const [isExpanded, setIsExpanded] = useState(false);
const lang = form.watch('lang');
const currentOutput = form.watch('output');
const outputFieldDirty = !!form.formState.dirtyFields?.output;
@@ -69,114 +75,136 @@ function CodeForm({ node }: INextOperatorForm) {
? getBusinessOutputs(formData?.outputs)
: serializeCodeOutputContract(currentOutput);
const theme = isDarkTheme
? RAGFlowMonacoTheme.Dark
: RAGFlowMonacoTheme.Light;
return (
<Form {...form}>
<FormWrapper>
<DynamicInputVariable
node={node}
title={t('flow.input')}
isOutputs={false}
></DynamicInputVariable>
<FormField
control={form.control}
name="script"
render={({ field }) => (
<FormItem>
<FormLabel className="flex items-center justify-between">
Code
<FormField
control={form.control}
name="lang"
render={({ field }) => (
<FormItem>
<FormControl>
<RAGFlowSelect
{...field}
onChange={(val) => {
field.onChange(val);
handleLanguageChange(val);
}}
options={options}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</FormLabel>
<FormControl>
<Editor
height={300}
theme={isDarkTheme ? 'vs-dark' : 'vs'}
<div className="relative min-h-full">
<FormWrapper>
<DynamicInputVariable
node={node}
title={t('flow.input')}
isOutputs={false}
></DynamicInputVariable>
<FormField
control={form.control}
name={ScriptFieldName}
render={({ field }) => (
<FormItem>
<FormLabel>
<section className="flex items-center justify-between">
Code
<div className="flex items-center gap-4">
<FormField
control={form.control}
name="lang"
render={({ field }) => (
<FormItem>
<FormControl>
<RAGFlowSelect
{...field}
onChange={(val) => {
field.onChange(val);
handleLanguageChange(val);
}}
options={options}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
variant={'ghost'}
onClick={() => setIsExpanded(true)}
type="button"
>
<Maximize2 className="size-4" />
</Button>
</div>
</section>
</FormLabel>
<FormControl>
<Editor
height={300}
theme={theme}
language={lang}
options={CodeEditorOptions}
{...field}
/>
</FormControl>
<FormMessage />
<ExpandedEditor
visible={isExpanded}
onClose={() => setIsExpanded(false)}
theme={theme}
language={lang}
options={{
minimap: { enabled: false },
automaticLayout: true,
}}
{...field}
value={field.value}
onChange={field.onChange}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</FormItem>
)}
/>
<div className="space-y-3">
<VariableTitle title={'Return Value'}></VariableTitle>
{legacyOutputs.length > 0 && (
<div className="flex items-start gap-2 rounded-md border border-state-error/40 bg-state-error/10 px-3 py-2 text-sm text-text-primary">
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-state-error" />
<p>
This CodeExec node uses the deprecated multi-output schema:{' '}
{legacyOutputs.join(', ')}. Keep one business output here and
move field extraction to downstream nodes.
</p>
</div>
)}
<FormContainer className="space-y-5">
<FormField
control={form.control}
name="output.name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input
{...field}
placeholder={t('common.pleaseInput')}
></Input>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="output.type"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Type</FormLabel>
<FormControl>
<RAGFlowSelect
placeholder={t('common.pleaseSelect')}
options={TypeOptions}
{...field}
></RAGFlowSelect>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</FormContainer>
<div className="space-y-3">
<VariableTitle title={'Return Value'}></VariableTitle>
{legacyOutputs.length > 0 && (
<div className="flex items-start gap-2 rounded-md border border-state-error/40 bg-state-error/10 px-3 py-2 text-sm text-text-primary">
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-state-error" />
<p>
This CodeExec node uses the deprecated multi-output schema:{' '}
{legacyOutputs.join(', ')}. Keep one business output here and
move field extraction to downstream nodes.
</p>
</div>
)}
<FormContainer className="space-y-5">
<FormField
control={form.control}
name="output.name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input
{...field}
placeholder={t('common.pleaseInput')}
></Input>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="output.type"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Type</FormLabel>
<FormControl>
<RAGFlowSelect
placeholder={t('common.pleaseSelect')}
options={TypeOptions}
{...field}
></RAGFlowSelect>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</FormContainer>
</div>
</FormWrapper>
<div className="space-y-4 p-5">
<Output list={buildOutputList(displayedBusinessOutputs)}>
Business
</Output>
<Output list={buildOutputList(CodeExecPanelSystemOutputs)}>
System
</Output>
</div>
</FormWrapper>
<div className="space-y-4 p-5">
<Output list={buildOutputList(displayedBusinessOutputs)}>
Business
</Output>
<Output list={buildOutputList(CodeExecPanelSystemOutputs)}>
System
</Output>
</div>
</Form>
);

View File

@@ -0,0 +1,13 @@
export const CodeEditorOptions = {
minimap: { enabled: false },
automaticLayout: true,
scrollbar: {
verticalScrollbarSize: 10,
horizontalScrollbarSize: 10,
},
};
export const RAGFlowMonacoTheme = {
Light: 'vs',
Dark: 'vs-dark',
} as const;

View File

@@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
import './index.less';
import { RAGFlowLogo } from './ragflow-log';
export default function SearchPage({
export default function SearchHome({
isSearching,
setIsSearching,
searchText,