Files
yaoayanui/src-tauri/src/commands/nodejs.rs
fengchuanhn@gmail.com 9edf656828 1
2026-05-18 00:27:14 +08:00

146 lines
4.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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::app_config::AppConfig;
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 log_node_event(script_name: &str, level: &str, msg: &str, fields: &Option<Value>) {
let extra = fields
.as_ref()
.map(|f| format!(" {f}"))
.unwrap_or_default();
let line = format!("[{script_name}] {msg}{extra}");
match level {
"error" => log::error!(target: "nodejs", "{line}"),
"warn" => log::warn!(target: "nodejs", "{line}"),
_ => log::info!(target: "nodejs", "{line}"),
}
}
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.clone(),
},
PipelineEvent::Log {
level,
msg,
fields,
} => {
log_node_event(&script_name, level, msg, fields);
FrontendEvent::Log {
script_name: script_name.clone(),
level: level.clone(),
msg: msg.clone(),
fields: fields.clone(),
}
}
};
let _ = app.emit_to(&win_label, EVENT_NAME, payload);
}
})
}
/// 按脚本名跑:去后端拉 `scriptName`,包装 runnerspawn 子进程,回送结果。
#[tauri::command]
pub async fn run_nodejs_script(
app: AppHandle,
window: Window,
app_config: tauri::State<'_, AppConfig>,
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 config_env = app_config.env_for_node().await;
let result = nodejs::run_node_script(script_name, params, tx, config_env)
.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,
app_config: tauri::State<'_, AppConfig>,
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 config_env = app_config.env_for_node().await;
let result = nodejs::run_node_script_source(script_source, params, tx, config_env)
.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())
}