This commit is contained in:
fengchuanhn@gmail.com
2026-05-16 23:03:27 +08:00
parent c0ddaceb43
commit e6c8c18be0
6 changed files with 519 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
//! Node.js 流水线命令:脚本由后端 `/api/v1/nodejs-scripts` 下发,
//! 由打包在 `resources/nodejs/node.exe` 的 Node 解释器执行。
//!
//! 前端用法示例:
//!
//! ```ignore
//! import { invoke } from "@tauri-apps/api/core";
//! import { listen } from "@tauri-apps/api/event";
//!
//! const off = await listen("nodejs:event", (e) => console.log(e.payload));
//! const result = await invoke("run_nodejs_script", {
//! scriptName: "douyin_pipeline.js",
//! params: { shareText: "...", modelConfig: {...} },
//! });
//! off();
//! ```
//!
//! 与 QuickJS 流水线对比:
//! - 入口约定:`globalThis.__nodejsMain(params)`QuickJS 那边是 `__quickjsMain`
//! - 事件通道:`nodejs:event`QuickJS 那边是 `quickjs:event`payload 形状一致
//! - __native 只暴露 `emitProgress` / `log` 两个能力,其它 IO / 网络
//! 直接用 Node 自带 API 即可,无需 Rust 端再封一遍桥。
use serde::Serialize;
use serde_json::Value;
use tauri::{AppHandle, Emitter, Window};
use tokio::sync::mpsc;
use crate::js_runtime::PipelineEvent;
use crate::nodejs;
const EVENT_NAME: &str = "nodejs:event";
#[derive(Debug, Serialize, Clone)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum FrontendEvent {
Progress {
#[serde(rename = "scriptName")]
script_name: String,
status: String,
},
Log {
#[serde(rename = "scriptName")]
script_name: String,
level: String,
msg: String,
#[serde(skip_serializing_if = "Option::is_none")]
fields: Option<Value>,
},
}
fn spawn_event_pump(
app: AppHandle,
window: Window,
script_name: String,
mut rx: mpsc::UnboundedReceiver<PipelineEvent>,
) -> tokio::task::JoinHandle<()> {
let win_label = window.label().to_string();
tokio::spawn(async move {
while let Some(ev) = rx.recv().await {
let payload = match ev {
PipelineEvent::Progress(s) => FrontendEvent::Progress {
script_name: script_name.clone(),
status: s,
},
PipelineEvent::Log { level, msg, fields } => FrontendEvent::Log {
script_name: script_name.clone(),
level,
msg,
fields,
},
};
let _ = app.emit_to(&win_label, EVENT_NAME, payload);
}
})
}
/// 按脚本名跑:去后端拉 `scriptName`,包装 runnerspawn 子进程,回送结果。
#[tauri::command]
pub async fn run_nodejs_script(
app: AppHandle,
window: Window,
script_name: String,
params: Value,
) -> Result<Value, String> {
let (tx, rx) = mpsc::unbounded_channel::<PipelineEvent>();
let pump = spawn_event_pump(app, window, script_name.clone(), rx);
let result = nodejs::run_node_script(script_name, params, tx)
.await
.map_err(|e| e.to_string())?;
pump.await.ok();
Ok(result)
}
/// 调试:直接把传入的 source 当用户脚本喂给 runner不走后端。
#[tauri::command]
pub async fn run_nodejs_script_source(
app: AppHandle,
window: Window,
script_source: String,
params: Value,
) -> Result<Value, String> {
let (tx, rx) = mpsc::unbounded_channel::<PipelineEvent>();
let pump = spawn_event_pump(app, window, "<debug-source>".to_string(), rx);
let result = nodejs::run_node_script_source(script_source, params, tx)
.await
.map_err(|e| e.to_string())?;
pump.await.ok();
Ok(result)
}
/// 列出后端 `scripts/nodejs` 目录下的 `.js` 文件名。
#[tauri::command]
pub async fn list_nodejs_scripts() -> Result<Vec<String>, String> {
nodejs::list_scripts().await.map_err(|e| e.to_string())
}