Merge branch 'release/v0.2.3' into develop

This commit is contained in:
Mark
2026-02-04 13:52:45 +08:00
24 changed files with 527 additions and 62 deletions

View File

@@ -142,7 +142,7 @@ const PageScrollList = forwardRef(<T, Q = Record<string, unknown>>({
dataLength={data.length}
next={loadMoreData}
hasMore={hasMore}
loader={needLoading ? <PageLoading /> : undefined}
loader={loading && needLoading ? <PageLoading /> : false}
// endMessage={<Divider plain>It is all, nothing more 🤐</Divider>}
scrollableTarget="scrollableDiv"
className='rb:h-full!'

View File

@@ -180,7 +180,4 @@ body {
.x6-node foreignObject > body {
min-height: 100%;
max-height: 100%;
}
#scrollableDiv .infinite-scroll-component__outerdiv {
height: 100%;
}

View File

@@ -21,6 +21,7 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
import InitialValuePlugin from './plugin/InitialValuePlugin'
import LineBreakPlugin from './plugin/LineBreakPlugin';
import InsertTextPlugin from './plugin/InsertTextPlugin';
import EditablePlugin from './plugin/EditablePlugin';
/**
* Editor ref methods exposed to parent components
@@ -50,6 +51,7 @@ interface LexicalEditorProps {
onChange?: (value: string) => void;
/** Editor height in pixels */
height?: number;
disabled?: boolean;
}
/**
@@ -71,6 +73,7 @@ const EditorContent = forwardRef<EditorRef, LexicalEditorProps>(({
value,
placeholder = "Please enter content...",
onChange,
disabled
}, ref) => {
const [editor] = useLexicalComposerContext();
@@ -132,7 +135,11 @@ const EditorContent = forwardRef<EditorRef, LexicalEditorProps>(({
<RichTextPlugin
contentEditable={
<ContentEditable
className={clsx("rb:outline-none rb:resize-none rb:text-[14px] rb:leading-5 rb:px-4 rb:py-5 rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:overflow-auto", className)}
className={clsx(
"rb:outline-none rb:resize-none rb:text-[14px] rb:leading-5 rb:px-4 rb:py-5 rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:overflow-auto",
disabled && "rb:cursor-not-allowed rb:bg-[#F6F8FC] rb:text-[#5B6167]",
className
)}
/>
}
placeholder={
@@ -145,6 +152,7 @@ const EditorContent = forwardRef<EditorRef, LexicalEditorProps>(({
<LineBreakPlugin onChange={onChange} />
<InitialValuePlugin value={value} />
<InsertTextPlugin />
<EditablePlugin disabled={disabled} />
</div>
);
});
@@ -158,6 +166,7 @@ const Editor = forwardRef<EditorRef, LexicalEditorProps>((props, ref) => {
namespace: 'Editor',
theme,
nodes: [],
editable: !props.disabled,
onError: (error: Error) => {
console.error(error);
},

View File

@@ -0,0 +1,48 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-04 11:20:49
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-04 11:20:49
*/
import { useEffect } from 'react';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
/**
* Props for the EditablePlugin component
*/
interface EditablePluginProps {
/** Whether the editor should be disabled (read-only mode) */
disabled?: boolean;
}
/**
* EditablePlugin - A Lexical editor plugin that controls the editable state of the editor
*
* This plugin allows you to dynamically toggle between editable and read-only modes.
* When disabled is true, the editor becomes read-only and users cannot modify content.
* When disabled is false or undefined, the editor is fully editable.
*
* @param {EditablePluginProps} props - Component props
* @param {boolean} [props.disabled] - Controls whether the editor is in read-only mode
* @returns {null} This plugin doesn't render any UI elements
*
* @example
* ```tsx
* <LexicalComposer>
* <EditablePlugin disabled={isReadOnly} />
* </LexicalComposer>
* ```
*/
export default function EditablePlugin({ disabled }: EditablePluginProps) {
// Get the editor instance from Lexical composer context
const [editor] = useLexicalComposerContext();
// Update editor's editable state whenever the disabled prop changes
useEffect(() => {
// Set editor to editable when disabled is false, read-only when disabled is true
editor.setEditable(!disabled);
}, [editor, disabled]);
// This plugin doesn't render any UI, it only manages editor state
return null;
}

View File

@@ -156,9 +156,9 @@ const Prompt: FC<{ editVo: HistoryItem | null; refresh: () => void; }> = ({ edit
currentPromptValueRef.current = undefined;
setChatList([])
refresh()
updateSession()
}
console.log(values)
return (
<>
<Form form={form}>
@@ -217,12 +217,13 @@ const Prompt: FC<{ editVo: HistoryItem | null; refresh: () => void; }> = ({ edit
ref={editorRef}
placeholder={t('prompt.promptPlaceholder')}
className="rb:h-[calc(100vh-260px)]"
disabled={loading}
// onChange={(value) => form.setFieldValue('current_prompt', value)}
/>
</Form.Item>
<div className="rb:grid rb:grid-cols-2 rb:gap-4 rb:mt-6">
<Button type="primary" block disabled={!values?.current_prompt} onClick={handleSave}>{t('common.save')}</Button>
<Button block disabled={!values?.current_prompt} onClick={handleCopy}>{t('common.copy')}</Button>
<Button type="primary" block disabled={!values?.current_prompt || loading} onClick={handleSave}>{t('common.save')}</Button>
<Button block disabled={!values?.current_prompt || loading} onClick={handleCopy}>{t('common.copy')}</Button>
</div>
</div>
</div>

View File

@@ -103,6 +103,8 @@ const SpaceModal = forwardRef<SpaceModalRef, SpaceModalProps>(({
}).catch(() => {
handleUpdate(formData)
})
} else {
handleUpdate(formData)
}
}
})
@@ -158,6 +160,7 @@ const SpaceModal = forwardRef<SpaceModalRef, SpaceModalProps>(({
label={t('space.spaceIcon')}
valuePropName="fileList"
hidden={currentStep === 1}
rules={[{ required: true, message: t('common.selectPlaceholder', { title: t('space.spaceIcon') }) }]}
>
<UploadImages />
</Form.Item>

View File

@@ -242,7 +242,7 @@ const Editor: FC<LexicalEditorProps> =({
{enableLineNumbers && <LineNumberPlugin />}
<AutocompletePlugin options={options} enableJinja2={enableJinja2} />
<CharacterCountPlugin setCount={(count) => { setCount(count) }} onChange={onChange} />
<InitialValuePlugin value={value} options={options} enableJinja2={enableJinja2} />
<InitialValuePlugin key={language} value={value} options={options} enableLineNumbers={enableLineNumbers} />
{enableLineNumbers && <BlurPlugin />}
</div>
</LexicalComposer>

View File

@@ -16,6 +16,12 @@ export default function BlurPlugin() {
return;
}
// 检查是否是粘贴操作导致的焦点变化
const relatedTarget = e.relatedTarget as HTMLElement;
if (!relatedTarget || relatedTarget === document.body) {
return;
}
editor.update(() => {
$setSelection(null);
});

View File

@@ -8,12 +8,13 @@ import { type Suggestion } from '../plugin/AutocompletePlugin'
interface InitialValuePluginProps {
value: string;
options?: Suggestion[];
enableJinja2?: boolean;
enableLineNumbers?: boolean;
}
const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options = [], enableJinja2 = false }) => {
const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options = [], enableLineNumbers = false }) => {
const [editor] = useLexicalComposerContext();
const prevValueRef = useRef<string>('');
const prevEnableLineNumbersRef = useRef<boolean>(enableLineNumbers);
const isUserInputRef = useRef(false);
useEffect(() => {
@@ -32,7 +33,7 @@ const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options
}, [editor]);
useEffect(() => {
if (value !== prevValueRef.current && !isUserInputRef.current) {
if ((value !== prevValueRef.current || enableLineNumbers !== prevEnableLineNumbersRef.current) && !isUserInputRef.current) {
queueMicrotask(() => {
editor.update(() => {
const root = $getRoot();
@@ -40,7 +41,7 @@ const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options
const parts = value.split(/(\{\{[^}]+\}\})/);
if (enableJinja2) {
if (enableLineNumbers) {
// Handle newlines properly in Jinja2 mode
const lines = value.split('\n');
lines.forEach((line) => {
@@ -104,8 +105,9 @@ const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options
}
prevValueRef.current = value;
prevEnableLineNumbersRef.current = enableLineNumbers;
isUserInputRef.current = false;
}, [value, options, editor, enableJinja2]);
}, [value, options, editor, enableLineNumbers]);
return null;
};

View File

@@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { useEffect, useRef } from 'react';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { TextNode, $createTextNode, $getSelection, $isRangeSelection } from 'lexical';
import { TextNode, $createTextNode, $getSelection, $isRangeSelection, COMMAND_PRIORITY_LOW, PASTE_COMMAND } from 'lexical';
const JS_KEYWORDS = new Set([
'async', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default',
@@ -11,13 +11,31 @@ const JS_KEYWORDS = new Set([
const JavaScriptHighlightPlugin = () => {
const [editor] = useLexicalComposerContext();
const isPastingRef = useRef(false);
useEffect(() => {
return editor.registerCommand(
PASTE_COMMAND,
() => {
isPastingRef.current = true;
setTimeout(() => {
isPastingRef.current = false;
}, 100);
return false;
},
COMMAND_PRIORITY_LOW
);
}, [editor]);
useEffect(() => {
return editor.registerNodeTransform(TextNode, (textNode: TextNode) => {
if (isPastingRef.current) return;
const text = textNode.getTextContent();
if (textNode.hasFormat('code')) return;
if (!needsHighlight(text)) return;
if (textNode.getStyle()) return;
const parent = textNode.getParent();
if (!parent) return;

View File

@@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { useEffect, useRef } from 'react';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { TextNode, $createTextNode, $getSelection, $isRangeSelection } from 'lexical';
import { TextNode, $createTextNode, $getSelection, $isRangeSelection, COMMAND_PRIORITY_LOW, PASTE_COMMAND } from 'lexical';
const PYTHON_KEYWORDS = new Set([
'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue',
@@ -11,12 +11,30 @@ const PYTHON_KEYWORDS = new Set([
const Python3HighlightPlugin = () => {
const [editor] = useLexicalComposerContext();
const isPastingRef = useRef(false);
useEffect(() => {
return editor.registerCommand(
PASTE_COMMAND,
() => {
isPastingRef.current = true;
setTimeout(() => {
isPastingRef.current = false;
}, 100);
return false;
},
COMMAND_PRIORITY_LOW
);
}, [editor]);
useEffect(() => {
return editor.registerNodeTransform(TextNode, (textNode: TextNode) => {
if (isPastingRef.current) return;
const text = textNode.getTextContent();
if (textNode.hasFormat('code')) return;
if (textNode.getStyle()) return;
if (!needsHighlight(text)) return;
const parent = textNode.getParent();

View File

@@ -33,7 +33,6 @@ const codeTemplate = {
const CodeExecution: FC<CodeExecutionProps> = ({ options }) => {
const { t } = useTranslation()
const form = Form.useFormInstance()
const values = Form.useWatch([], form) || {}
const handleRefresh = () => {
const code = form.getFieldValue('code') || ''
@@ -66,7 +65,6 @@ const CodeExecution: FC<CodeExecutionProps> = ({ options }) => {
form.setFieldValue('code', newTemplate)
}
const handleChangeLanguage = (value: string) => {
form.setFieldValue('code', codeTemplate[value as keyof typeof codeTemplate])
form.setFieldsValue({
input_variables: [{ name: 'arg1' }, { name: 'arg2' }],
code: codeTemplate[value as keyof typeof codeTemplate]
@@ -109,8 +107,12 @@ const CodeExecution: FC<CodeExecutionProps> = ({ options }) => {
</Form.Item>
</Col>
</Row>
<Form.Item name="code" noStyle>
<Editor size="small" language={values.language} />
<Form.Item noStyle shouldUpdate={(prev, curr) => prev.language !== curr.language}>
{() => (
<Form.Item name="code" noStyle>
<Editor size="small" language={form.getFieldValue('language')} />
</Form.Item>
)}
</Form.Item>
</Space>

View File

@@ -159,7 +159,7 @@ export const useWorkflowGraph = ({
nodeLibraryConfig.config[key].defaultValue = Object.entries(config[key]).map(([name, value]) => ({ name, value }))
} else if (type === 'code' && key === 'code' && config[key] && nodeLibraryConfig.config && nodeLibraryConfig.config[key]) {
try {
nodeLibraryConfig.config[key].defaultValue = atob(config[key] as string)
nodeLibraryConfig.config[key].defaultValue = decodeURIComponent(atob(config[key] as string))
} catch {
nodeLibraryConfig.config[key].defaultValue = config[key]
}
@@ -943,7 +943,7 @@ export const useWorkflowGraph = ({
const code = data.config[key].defaultValue || ''
itemConfig = {
...itemConfig,
code: btoa(code || '')
code: btoa(encodeURIComponent(code || ''))
}
} else if (key === 'memory' && data.config[key] && 'defaultValue' in data.config[key]) {
const { messages, ...rest } = data.config[key].defaultValue