feat: 支持 Python 代码节点 main 返回输出
- 自动传入节点输入并映射 main 返回字典,兼容历史 _result - 限制脚本输出缓冲并补充运行时与编辑器测试 - 同步 Python 帮助、占位提示和独立可执行补全片段
This commit is contained in:
@@ -22,8 +22,8 @@
|
||||
let currentNode = useNodesData(currentNodeId);
|
||||
const { addParameter } = useAddParameter();
|
||||
const { updateNodeData } = useSvelteFlow();
|
||||
const codeNodeHelp = `JavaScript 如何返回结果
|
||||
- 定义 main 函数并返回对象,系统会自动执行。
|
||||
const codeNodeHelp = `代码如何返回结果
|
||||
- JavaScript 定义 main 函数并返回对象;Python 定义 main 函数并返回 dict,系统会自动执行。
|
||||
- main 接收由输入参数组成的对象。
|
||||
|
||||
输出参数如何配置
|
||||
@@ -31,11 +31,15 @@
|
||||
- 字段名与 main 返回对象的 key 保持一致。
|
||||
- 下游节点可引用:代码节点ID.输出参数名。
|
||||
|
||||
示例
|
||||
- 代码里写:
|
||||
JavaScript 示例
|
||||
function main({ input }) {
|
||||
return { answer: input, score: 95 };
|
||||
}
|
||||
|
||||
Python 示例
|
||||
def main(inputs):
|
||||
return {'answer': inputs.get('input'), 'score': 95}
|
||||
|
||||
- 输出参数配置:answer(String)、score(Number)
|
||||
- 结束节点输出参数可引用:代码节点ID.answer、代码节点ID.score
|
||||
|
||||
@@ -53,6 +57,13 @@
|
||||
const firstAvailable = engines.find((item) => item.selectable !== false);
|
||||
return firstAvailable?.value || 'js';
|
||||
});
|
||||
const codePlaceholder = $derived.by(() => {
|
||||
const engine = String(data.engine || defaultEngine).trim().toLowerCase();
|
||||
if (engine === 'python' || engine === 'py') {
|
||||
return "请输入代码,例如:def main(inputs):\n return {'output': inputs.get('input')}";
|
||||
}
|
||||
return '请输入代码,例如:function main({ input }) { return { output: input }; }';
|
||||
});
|
||||
|
||||
onMount(async () => {
|
||||
const codeEngines = await options.provider?.codeEngine?.();
|
||||
@@ -118,7 +129,7 @@
|
||||
<CodeScriptEditor
|
||||
mode="textarea"
|
||||
rows={10}
|
||||
placeholder={'请输入代码,例如:function main({ input }) { return { output: input }; }'}
|
||||
placeholder={codePlaceholder}
|
||||
style="width: 100%"
|
||||
engine={(data.engine as string) || (defaultEngine as string)}
|
||||
parameters={editorParameters}
|
||||
|
||||
@@ -71,6 +71,36 @@ describe('codeCompletion utils', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should prefer main return snippets for python output', () => {
|
||||
const completions = createBusinessCompletions('python', []);
|
||||
const mainCompletion = completions.find((item) => item.label === 'main');
|
||||
const resultCompletion = completions.find(
|
||||
(item) => item.label === 'result-object',
|
||||
);
|
||||
|
||||
expect(mainCompletion).toMatchObject({
|
||||
type: 'snippet',
|
||||
detail: '代码节点入口',
|
||||
boost: 1000,
|
||||
});
|
||||
expect(String(mainCompletion?.apply)).toContain('def main(inputs):');
|
||||
expect(String(mainCompletion?.apply)).toContain('return {');
|
||||
expect(String(resultCompletion?.apply)).toContain('def main(inputs):');
|
||||
expect(String(resultCompletion?.apply)).toContain(
|
||||
"return {'message': 'ok', 'data': inputs.get('data')}",
|
||||
);
|
||||
});
|
||||
|
||||
it('should provide standalone python snippets', () => {
|
||||
const completions = createBusinessCompletions('python', []);
|
||||
const snippets = completions.filter((item) => item.type === 'snippet');
|
||||
|
||||
expect(snippets).toHaveLength(4);
|
||||
for (const snippet of snippets) {
|
||||
expect(String(snippet.apply)).toMatch(/^def main\(inputs\):\n /);
|
||||
}
|
||||
});
|
||||
|
||||
it('should detect blocked nodes for comment/string/property contexts', () => {
|
||||
expect(shouldSkipBusinessCompletion('Comment')).toBe(true);
|
||||
expect(shouldSkipBusinessCompletion('LineComment')).toBe(true);
|
||||
|
||||
@@ -60,21 +60,29 @@ const PYTHON_SNIPPETS: Array<{
|
||||
insert: string;
|
||||
detail: string;
|
||||
}> = [
|
||||
{
|
||||
label: 'main',
|
||||
detail: '代码节点入口',
|
||||
insert:
|
||||
"def main(inputs):\n return {\n 'output': inputs.get('input'),\n }",
|
||||
},
|
||||
{
|
||||
label: 'if-else',
|
||||
detail: '条件分支',
|
||||
insert:
|
||||
"if condition:\n _result['value'] = value\nelse:\n _result['value'] = None",
|
||||
"def main(inputs):\n condition = inputs.get('condition')\n value = inputs.get('value')\n if condition:\n return {'value': value}\n return {'value': None}",
|
||||
},
|
||||
{
|
||||
label: 'for-loop',
|
||||
detail: '遍历数组',
|
||||
insert: "for item in items:\n # TODO\n pass\n_result['done'] = True",
|
||||
insert:
|
||||
"def main(inputs):\n result = []\n for item in inputs.get('items', []):\n result.append(item)\n return {'result': result}",
|
||||
},
|
||||
{
|
||||
label: 'result-object',
|
||||
detail: '返回对象',
|
||||
insert: "_result['message'] = 'ok'\n_result['data'] = data",
|
||||
insert:
|
||||
"def main(inputs):\n return {'message': 'ok', 'data': inputs.get('data')}",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user