146 lines
4.7 KiB
Rust
146 lines
4.7 KiB
Rust
//! 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`,包装 runner,spawn 子进程,回送结果。
|
||
#[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())
|
||
}
|