diff --git a/backend/src/flow/engine.rs b/backend/src/flow/engine.rs index 58f1a44..b5b2ef1 100644 --- a/backend/src/flow/engine.rs +++ b/backend/src/flow/engine.rs @@ -99,7 +99,7 @@ fn eval_rhai_expr_bool(expr: &str, ctx: &serde_json::Value) -> bool { // 构造作用域并注入 ctx let mut scope = rhai::Scope::new(); let dyn_ctx = match rhai::serde::to_dynamic(ctx.clone()) { Ok(d) => d, Err(_) => rhai::Dynamic::UNIT }; - scope.push("ctx", dyn_ctx); + scope.push_dynamic("ctx", dyn_ctx); // 先从缓存读取 AST;未命中则编译并写入缓存,然后执行 let cached = AST_CACHE.with(|c| c.borrow().get(expr).cloned()); @@ -107,7 +107,7 @@ fn eval_rhai_expr_bool(expr: &str, ctx: &serde_json::Value) -> bool { return RHIA_ENGINE.with(|eng| eng.borrow().eval_ast_with_scope::(&mut scope, &ast).unwrap_or(false)); } - let compiled = RHIA_ENGINE.with(|eng| eng.borrow().compile(expr)); + let compiled = RHIA_ENGINE.with(|eng| eng.borrow().compile_with_scope(&mut scope, expr)); match compiled { Ok(ast) => { // 简单容量控制:超过 1024 条时清空,避免无限增长 @@ -123,20 +123,20 @@ fn eval_rhai_expr_bool(expr: &str, ctx: &serde_json::Value) -> bool { } // 通用:评估 Rhai 表达式并转换为 serde_json::Value,失败返回 None -pub(crate) fn eval_rhai_expr_json(expr: &str, ctx: &serde_json::Value) -> Option { +pub(crate) fn eval_rhai_expr_json(expr: &str, ctx: &serde_json::Value) -> Result { // 构造作用域并注入 ctx let mut scope = rhai::Scope::new(); let dyn_ctx = match rhai::serde::to_dynamic(ctx.clone()) { Ok(d) => d, Err(_) => rhai::Dynamic::UNIT }; - scope.push("ctx", dyn_ctx); + scope.push_dynamic("ctx", dyn_ctx); // 先从缓存读取 AST;未命中则编译并写入缓存,然后执行 let cached = AST_CACHE.with(|c| c.borrow().get(expr).cloned()); - let eval = |ast: &AST, scope: &mut rhai::Scope| -> Option { + let eval = |ast: &AST, scope: &mut rhai::Scope| -> Result { RHIA_ENGINE.with(|eng| { eng.borrow() .eval_ast_with_scope::(scope, ast) - .ok() - .and_then(|d| rhai::serde::from_dynamic(&d).ok()) + .map_err(|e| e.to_string()) + .and_then(|d| rhai::serde::from_dynamic(&d).map_err(|e| e.to_string())) }) }; @@ -144,7 +144,7 @@ pub(crate) fn eval_rhai_expr_json(expr: &str, ctx: &serde_json::Value) -> Option return eval(&ast, &mut scope); } - let compiled = RHIA_ENGINE.with(|eng| eng.borrow().compile(expr)); + let compiled = RHIA_ENGINE.with(|eng| eng.borrow().compile_with_scope(&mut scope, expr)); match compiled { Ok(ast) => { AST_CACHE.with(|c| { @@ -154,7 +154,7 @@ pub(crate) fn eval_rhai_expr_json(expr: &str, ctx: &serde_json::Value) -> Option }); eval(&ast, &mut scope) } - Err(_) => None, + Err(e) => Err(e.to_string()), } } diff --git a/backend/src/flow/executors/condition.rs b/backend/src/flow/executors/condition.rs index dfe34fa..26b3848 100644 --- a/backend/src/flow/executors/condition.rs +++ b/backend/src/flow/executors/condition.rs @@ -166,7 +166,7 @@ pub(crate) fn resolve_value(ctx: &serde_json::Value, v: &serde_json::Value) -> R "expression" => { let expr = v.get("content").and_then(|x| x.as_str()).unwrap_or(""); if expr.trim().is_empty() { return Ok(V::Null); } - Ok(crate::flow::engine::eval_rhai_expr_json(expr, ctx).unwrap_or(V::Null)) + Ok(crate::flow::engine::eval_rhai_expr_json(expr, ctx).unwrap_or_else(|_| V::Null)) } _ => Ok(V::Null), } diff --git a/backend/src/flow/executors/script_rhai.rs b/backend/src/flow/executors/script_rhai.rs index 108b2ff..bc3099b 100644 --- a/backend/src/flow/executors/script_rhai.rs +++ b/backend/src/flow/executors/script_rhai.rs @@ -62,7 +62,7 @@ pub fn exec_rhai_file(node_id: &NodeId, path: &str, ctx: &mut Value) -> anyhow:: let res = eval_rhai_expr_json(&wrapped, ctx); let dur_ms = start.elapsed().as_millis(); match res { - Some(new_ctx) => { + Ok(new_ctx) => { let (added, removed, modified) = shallow_diff(&before_ctx, &new_ctx); *ctx = new_ctx; info!(target = "udmin.flow", node=%node_id.0, ms=%dur_ms, added=%added.len(), removed=%removed.len(), modified=%modified.len(), "script task: Rhai file executed and ctx updated"); @@ -70,8 +70,8 @@ pub fn exec_rhai_file(node_id: &NodeId, path: &str, ctx: &mut Value) -> anyhow:: debug!(target = "udmin.flow", node=%node_id.0, ?added, ?removed, ?modified, "script task: ctx shallow diff"); } } - None => { - info!(target = "udmin.flow", node=%node_id.0, ms=%dur_ms, preview=%preview, "script task: Rhai file execution failed, ctx unchanged"); + Err(err) => { + info!(target = "udmin.flow", node=%node_id.0, ms=%dur_ms, preview=%preview, err=%err, "script task: Rhai file execution failed, ctx unchanged"); } } Ok(()) @@ -114,10 +114,10 @@ impl Executor for ScriptRhaiTask { let before_ctx = ctx.clone(); let wrapped = format!("{{ {} ; ctx }}", script); - let res = super::super::engine::eval_rhai_expr_json(&wrapped, ctx); + let res = eval_rhai_expr_json(&wrapped, ctx); let dur_ms = start.elapsed().as_millis(); match res { - Some(new_ctx) => { + Ok(new_ctx) => { let (added, removed, modified) = shallow_diff(&before_ctx, &new_ctx); *ctx = new_ctx; info!(target = "udmin.flow", node=%node_id.0, ms=%dur_ms, added=%added.len(), removed=%removed.len(), modified=%modified.len(), "script_rhai task: inline executed and ctx updated"); @@ -125,8 +125,8 @@ impl Executor for ScriptRhaiTask { debug!(target = "udmin.flow", node=%node_id.0, ?added, ?removed, ?modified, "script_rhai task: ctx shallow diff"); } } - None => { - info!(target = "udmin.flow", node=%node_id.0, ms=%dur_ms, preview=%script_preview, "script_rhai task: inline execution failed, ctx unchanged"); + Err(err) => { + info!(target = "udmin.flow", node=%node_id.0, ms=%dur_ms, preview=%script_preview, err=%err, "script_rhai task: inline execution failed, ctx unchanged"); } } return Ok(()); diff --git a/backend/src/flow/executors/variable.rs b/backend/src/flow/executors/variable.rs index 4a35edc..9f75cda 100644 --- a/backend/src/flow/executors/variable.rs +++ b/backend/src/flow/executors/variable.rs @@ -56,7 +56,7 @@ fn resolve_assign_value(ctx: &Value, v: &Value) -> Value { } // ctx[...] / ctx. 前缀 -> 表达式求值 if s_trim.starts_with("ctx[") || s_trim.starts_with("ctx.") { - return eval_rhai_expr_json(s_trim, ctx).unwrap_or(V::Null); + return eval_rhai_expr_json(s_trim, ctx).unwrap_or_else(|_| V::Null); } } v.get("content").cloned().unwrap_or(V::Null) @@ -97,7 +97,7 @@ fn resolve_assign_value(ctx: &Value, v: &Value) -> Value { "expression" => { let expr = v.get("content").and_then(|x| x.as_str()).unwrap_or(""); if expr.trim().is_empty() { return V::Null; } - eval_rhai_expr_json(expr, ctx).unwrap_or(V::Null) + eval_rhai_expr_json(expr, ctx).unwrap_or_else(|_| V::Null) } _ => { // fallback: if content exists, treat as constant diff --git a/frontend/src/flows/editor.tsx b/frontend/src/flows/editor.tsx index 0ec878c..3471848 100644 --- a/frontend/src/flows/editor.tsx +++ b/frontend/src/flows/editor.tsx @@ -113,14 +113,26 @@ export function Editor() { export default Editor; -// 新增:将后端存储的 javascript 类型还原为前端 UI 的 code 类型 +// 新增:将后端存储的 javascript/script/script_rhai 类型还原为前端 UI 的 code 类型,并设置语言 function transformDesignJsonFromBackend(json: any): any { try { const clone = JSON.parse(JSON.stringify(json)); if (Array.isArray(clone?.nodes)) { clone.nodes = clone.nodes.map((n: any) => { - if (n && n.type === 'javascript') { - return { ...n, type: 'code' }; + if (!n) return n; + if (n.type === 'javascript') { + // JS 节点 -> 前端 code,保留/设置 language=javascript + const data = n.data || {}; + const script = data.script || {}; + if (!script.language) script.language = 'javascript'; + return { ...n, type: 'code', data: { ...data, script } }; + } + if (n.type === 'script' || n.type === 'script_rhai') { + // Rhai 节点 -> 前端 code,设置 language=rhai + const data = n.data || {}; + const script = data.script || {}; + script.language = 'rhai'; + return { ...n, type: 'code', data: { ...data, script } }; } return n; }); diff --git a/frontend/src/flows/nodes/code/components/code.tsx b/frontend/src/flows/nodes/code/components/code.tsx index 5692b67..47e8c59 100644 --- a/frontend/src/flows/nodes/code/components/code.tsx +++ b/frontend/src/flows/nodes/code/components/code.tsx @@ -5,7 +5,7 @@ import { Field } from '@flowgram.ai/free-layout-editor'; import { CodeEditor } from '@flowgram.ai/form-materials'; -import { Divider } from '@douyinfe/semi-ui'; +import { Divider, Select, Typography } from '@douyinfe/semi-ui'; import { useIsSidebar, useNodeRenderContext } from '../../../hooks'; import { FormItem } from '../../../form-components'; @@ -21,14 +21,39 @@ export function Code() { return ( <> - name="script.content"> - {({ field }) => ( - field.onChange(value)} - readonly={readonly} - /> + {/* Language selector + editor */} + name="script.language"> + {({ field: langField }) => ( + <> + Language as any} vertical> +