Merge branch 'release/v0.2.3' into develop
# Conflicts: # api/app/core/agent/langchain_agent.py # api/app/core/memory/agent/langgraph_graph/write_graph.py # api/app/repositories/neo4j/graph_saver.py # api/app/services/draft_run_service.py
This commit is contained in:
@@ -15,8 +15,6 @@ import CharacterCountPlugin from './plugin/CharacterCountPlugin'
|
||||
import InitialValuePlugin from './plugin/InitialValuePlugin';
|
||||
import CommandPlugin from './plugin/CommandPlugin';
|
||||
import Jinja2HighlightPlugin from './plugin/Jinja2HighlightPlugin';
|
||||
import Python3HighlightPlugin from './plugin/Python3HighlightPlugin';
|
||||
import JavaScriptHighlightPlugin from './plugin/JavaScriptHighlightPlugin';
|
||||
import LineNumberPlugin from './plugin/LineNumberPlugin';
|
||||
import BlurPlugin from './plugin/BlurPlugin';
|
||||
import { VariableNode } from './nodes/VariableNode'
|
||||
@@ -32,7 +30,7 @@ export interface LexicalEditorProps {
|
||||
lineHeight?: number;
|
||||
size?: 'default' | 'small';
|
||||
type?: 'input' | 'textarea',
|
||||
language?: 'string' | 'jinja2' | 'python3' | 'javascript'
|
||||
language?: 'string' | 'jinja2'
|
||||
}
|
||||
|
||||
const theme = {
|
||||
@@ -67,7 +65,7 @@ const Editor: FC<LexicalEditorProps> =({
|
||||
const [enableLineNumbers, setEnableLineNumbers] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const needsLineNumbers = language === 'jinja2' || language === 'python3' || language === 'javascript';
|
||||
const needsLineNumbers = language === 'jinja2';
|
||||
setEnableJinja2(language === 'jinja2');
|
||||
setEnableLineNumbers(needsLineNumbers);
|
||||
|
||||
@@ -237,13 +235,11 @@ const Editor: FC<LexicalEditorProps> =({
|
||||
<HistoryPlugin />
|
||||
<CommandPlugin />
|
||||
{language === 'jinja2' && <Jinja2HighlightPlugin />}
|
||||
{language === 'python3' && <Python3HighlightPlugin />}
|
||||
{language === 'javascript' && <JavaScriptHighlightPlugin />}
|
||||
{enableLineNumbers && <LineNumberPlugin />}
|
||||
<AutocompletePlugin options={options} enableJinja2={enableJinja2} />
|
||||
<CharacterCountPlugin setCount={(count) => { setCount(count) }} onChange={onChange} />
|
||||
<InitialValuePlugin key={language} value={value} options={options} enableLineNumbers={enableLineNumbers} />
|
||||
{enableLineNumbers && <BlurPlugin />}
|
||||
<InitialValuePlugin value={value} options={options} enableLineNumbers={enableLineNumbers} />
|
||||
{enableJinja2 && <BlurPlugin />}
|
||||
</div>
|
||||
</LexicalComposer>
|
||||
);
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||
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',
|
||||
'delete', 'do', 'else', 'export', 'extends', 'finally', 'for', 'function', 'if', 'import',
|
||||
'in', 'instanceof', 'let', 'new', 'return', 'super', 'switch', 'this', 'throw', 'try',
|
||||
'typeof', 'var', 'void', 'while', 'with', 'yield', 'true', 'false', 'null', 'undefined'
|
||||
]);
|
||||
|
||||
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;
|
||||
|
||||
const selection = $getSelection();
|
||||
let selectionOffset = null;
|
||||
if ($isRangeSelection(selection)) {
|
||||
const anchor = selection.anchor;
|
||||
if (anchor.getNode() === textNode) {
|
||||
selectionOffset = anchor.offset;
|
||||
}
|
||||
}
|
||||
|
||||
const tokens = tokenizeJavaScript(text);
|
||||
if (tokens.length <= 1) return;
|
||||
|
||||
const newNodes = tokens.map(token => {
|
||||
const newNode = $createTextNode(token.text);
|
||||
newNode.toggleFormat('code');
|
||||
|
||||
switch (token.type) {
|
||||
case 'keyword':
|
||||
newNode.setStyle('color: #d73a49; font-weight: 600;');
|
||||
break;
|
||||
case 'string':
|
||||
newNode.setStyle('color: #032f62;');
|
||||
break;
|
||||
case 'comment':
|
||||
newNode.setStyle('color: #6a737d; font-style: italic;');
|
||||
break;
|
||||
case 'number':
|
||||
newNode.setStyle('color: #005cc5; font-weight: 500;');
|
||||
break;
|
||||
case 'function':
|
||||
newNode.setStyle('color: #6f42c1; font-weight: 500;');
|
||||
break;
|
||||
}
|
||||
|
||||
return newNode;
|
||||
});
|
||||
|
||||
if (newNodes.length > 1) {
|
||||
textNode.replace(newNodes[0]);
|
||||
for (let i = 1; i < newNodes.length; i++) {
|
||||
newNodes[i - 1].insertAfter(newNodes[i]);
|
||||
}
|
||||
|
||||
if (selectionOffset !== null && $isRangeSelection(selection)) {
|
||||
let currentOffset = 0;
|
||||
for (const node of newNodes) {
|
||||
const nodeLength = node.getTextContent().length;
|
||||
if (currentOffset + nodeLength >= selectionOffset) {
|
||||
node.select(selectionOffset - currentOffset, selectionOffset - currentOffset);
|
||||
break;
|
||||
}
|
||||
currentOffset += nodeLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [editor]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
function needsHighlight(text: string): boolean {
|
||||
return /[a-zA-Z0-9_/"'`]/.test(text);
|
||||
}
|
||||
|
||||
function tokenizeJavaScript(text: string): Array<{text: string, type: string}> {
|
||||
const tokens: Array<{text: string, type: string}> = [];
|
||||
let i = 0;
|
||||
|
||||
while (i < text.length) {
|
||||
// Single-line comments
|
||||
if (text.slice(i, i + 2) === '//') {
|
||||
let start = i;
|
||||
while (i < text.length && text[i] !== '\n') i++;
|
||||
tokens.push({ text: text.slice(start, i), type: 'comment' });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Multi-line comments
|
||||
if (text.slice(i, i + 2) === '/*') {
|
||||
let start = i;
|
||||
i += 2;
|
||||
while (i < text.length && text.slice(i, i + 2) !== '*/') i++;
|
||||
if (i < text.length) i += 2;
|
||||
tokens.push({ text: text.slice(start, i), type: 'comment' });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Strings
|
||||
if (text[i] === '"' || text[i] === "'" || text[i] === '`') {
|
||||
const quote = text[i];
|
||||
let start = i++;
|
||||
|
||||
while (i < text.length) {
|
||||
if (text[i] === quote && text[i - 1] !== '\\') {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
tokens.push({ text: text.slice(start, i), type: 'string' });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Numbers
|
||||
if (/\d/.test(text[i])) {
|
||||
let start = i;
|
||||
while (i < text.length && /[\d.]/.test(text[i])) i++;
|
||||
tokens.push({ text: text.slice(start, i), type: 'number' });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keywords and identifiers
|
||||
if (/[a-zA-Z_$]/.test(text[i])) {
|
||||
let start = i;
|
||||
while (i < text.length && /[a-zA-Z0-9_$]/.test(text[i])) i++;
|
||||
const word = text.slice(start, i);
|
||||
|
||||
if (JS_KEYWORDS.has(word)) {
|
||||
tokens.push({ text: word, type: 'keyword' });
|
||||
} else if (i < text.length && text[i] === '(') {
|
||||
tokens.push({ text: word, type: 'function' });
|
||||
} else {
|
||||
tokens.push({ text: word, type: 'text' });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Other characters
|
||||
let start = i;
|
||||
while (i < text.length && !/[a-zA-Z0-9_$/"'`]/.test(text[i])) i++;
|
||||
if (start < i) {
|
||||
tokens.push({ text: text.slice(start, i), type: 'text' });
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
export default JavaScriptHighlightPlugin;
|
||||
@@ -1,177 +0,0 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||
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',
|
||||
'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import',
|
||||
'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while',
|
||||
'with', 'yield'
|
||||
]);
|
||||
|
||||
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();
|
||||
if (!parent) return;
|
||||
|
||||
const selection = $getSelection();
|
||||
let selectionOffset = null;
|
||||
if ($isRangeSelection(selection)) {
|
||||
const anchor = selection.anchor;
|
||||
if (anchor.getNode() === textNode) {
|
||||
selectionOffset = anchor.offset;
|
||||
}
|
||||
}
|
||||
|
||||
const tokens = tokenizePython(text);
|
||||
if (tokens.length <= 1) return;
|
||||
|
||||
const newNodes = tokens.map(token => {
|
||||
const newNode = $createTextNode(token.text);
|
||||
newNode.toggleFormat('code');
|
||||
|
||||
switch (token.type) {
|
||||
case 'keyword':
|
||||
newNode.setStyle('color: #d73a49; font-weight: 600;');
|
||||
break;
|
||||
case 'string':
|
||||
newNode.setStyle('color: #032f62;');
|
||||
break;
|
||||
case 'comment':
|
||||
newNode.setStyle('color: #6a737d; font-style: italic;');
|
||||
break;
|
||||
case 'number':
|
||||
newNode.setStyle('color: #005cc5; font-weight: 500;');
|
||||
break;
|
||||
case 'function':
|
||||
newNode.setStyle('color: #6f42c1; font-weight: 500;');
|
||||
break;
|
||||
}
|
||||
|
||||
return newNode;
|
||||
});
|
||||
|
||||
if (newNodes.length > 1) {
|
||||
textNode.replace(newNodes[0]);
|
||||
for (let i = 1; i < newNodes.length; i++) {
|
||||
newNodes[i - 1].insertAfter(newNodes[i]);
|
||||
}
|
||||
|
||||
if (selectionOffset !== null && $isRangeSelection(selection)) {
|
||||
let currentOffset = 0;
|
||||
for (const node of newNodes) {
|
||||
const nodeLength = node.getTextContent().length;
|
||||
if (currentOffset + nodeLength >= selectionOffset) {
|
||||
node.select(selectionOffset - currentOffset, selectionOffset - currentOffset);
|
||||
break;
|
||||
}
|
||||
currentOffset += nodeLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [editor]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
function needsHighlight(text: string): boolean {
|
||||
return /[a-zA-Z0-9_#"']/.test(text);
|
||||
}
|
||||
|
||||
function tokenizePython(text: string): Array<{text: string, type: string}> {
|
||||
const tokens: Array<{text: string, type: string}> = [];
|
||||
let i = 0;
|
||||
|
||||
while (i < text.length) {
|
||||
// Comments
|
||||
if (text[i] === '#') {
|
||||
let start = i;
|
||||
while (i < text.length && text[i] !== '\n') i++;
|
||||
tokens.push({ text: text.slice(start, i), type: 'comment' });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Strings
|
||||
if (text[i] === '"' || text[i] === "'") {
|
||||
const quote = text[i];
|
||||
let start = i++;
|
||||
const isTriple = text.slice(start, start + 3) === quote.repeat(3);
|
||||
if (isTriple) i += 2;
|
||||
|
||||
while (i < text.length) {
|
||||
if (isTriple && text.slice(i, i + 3) === quote.repeat(3)) {
|
||||
i += 3;
|
||||
break;
|
||||
} else if (!isTriple && text[i] === quote && text[i - 1] !== '\\') {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
tokens.push({ text: text.slice(start, i), type: 'string' });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Numbers
|
||||
if (/\d/.test(text[i])) {
|
||||
let start = i;
|
||||
while (i < text.length && /[\d.]/.test(text[i])) i++;
|
||||
tokens.push({ text: text.slice(start, i), type: 'number' });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keywords and identifiers
|
||||
if (/[a-zA-Z_]/.test(text[i])) {
|
||||
let start = i;
|
||||
while (i < text.length && /[a-zA-Z0-9_]/.test(text[i])) i++;
|
||||
const word = text.slice(start, i);
|
||||
|
||||
if (PYTHON_KEYWORDS.has(word)) {
|
||||
tokens.push({ text: word, type: 'keyword' });
|
||||
} else if (i < text.length && text[i] === '(') {
|
||||
tokens.push({ text: word, type: 'function' });
|
||||
} else {
|
||||
tokens.push({ text: word, type: 'text' });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Other characters
|
||||
let start = i;
|
||||
while (i < text.length && !/[a-zA-Z0-9_#"']/.test(text[i])) i++;
|
||||
if (start < i) {
|
||||
tokens.push({ text: text.slice(start, i), type: 'text' });
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
export default Python3HighlightPlugin;
|
||||
@@ -5,8 +5,8 @@ import { Node } from '@antv/x6'
|
||||
|
||||
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
|
||||
import MappingList from '../MappingList'
|
||||
import Editor from '../../Editor'
|
||||
import OutputList from './OutputList'
|
||||
import CodeMirrorEditor from '@/components/CodeMirrorEditor';
|
||||
|
||||
interface MappingItem {
|
||||
name?: string
|
||||
@@ -110,7 +110,10 @@ const CodeExecution: FC<CodeExecutionProps> = ({ options }) => {
|
||||
<Form.Item noStyle shouldUpdate={(prev, curr) => prev.language !== curr.language}>
|
||||
{() => (
|
||||
<Form.Item name="code" noStyle>
|
||||
<Editor size="small" language={form.getFieldValue('language')} />
|
||||
<CodeMirrorEditor
|
||||
language={form.getFieldValue('language')}
|
||||
size="small"
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
@@ -126,7 +126,7 @@ const Knowledge: FC<{value?: KnowledgeConfig; onChange?: (config: KnowledgeConfi
|
||||
<div
|
||||
className="rb:size-3.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/workflow/recall.svg')] rb:group-hover:bg-[url('@/assets/images/workflow/recall_hover.svg')]"
|
||||
></div>
|
||||
{t('workflow.config.knowledge-retrieval.recallConfig')}
|
||||
{t('application.globalConfig')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user