11
This commit is contained in:
120
src-tauri/src/commands/nodejs.rs
Normal file
120
src-tauri/src/commands/nodejs.rs
Normal 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`,包装 runner,spawn 子进程,回送结果。
|
||||
#[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())
|
||||
}
|
||||
Reference in New Issue
Block a user