666
This commit is contained in:
54
package-lock.json
generated
54
package-lock.json
generated
@@ -11,8 +11,11 @@
|
||||
"@primeuix/themes": "^1.2.1",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"monaco-editor-vue3": "^1.0.5",
|
||||
"pinia": "^3.0.3",
|
||||
"primevue": "^4.3.6",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vue": "^3.5.17",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
@@ -1406,6 +1409,12 @@
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "6.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz",
|
||||
@@ -1680,6 +1689,14 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
|
||||
"integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.356",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.356.tgz",
|
||||
@@ -2097,11 +2114,40 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
|
||||
"integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/mitt": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
|
||||
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
|
||||
},
|
||||
"node_modules/monaco-editor": {
|
||||
"version": "0.55.1",
|
||||
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
|
||||
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
|
||||
"dependencies": {
|
||||
"dompurify": "3.2.7",
|
||||
"marked": "14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/monaco-editor-vue3": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/monaco-editor-vue3/-/monaco-editor-vue3-1.0.5.tgz",
|
||||
"integrity": "sha512-1n4ayGcuYpZcQ6A4PUYZYqXuf6n+A/2pUNdLnb8I39WZV0zkhfvSRNau3hB0qzq6vp6wSNZ7MgcjQRCJCiGHWQ==",
|
||||
"peerDependencies": {
|
||||
"monaco-editor": ">= 0.25.0 < 1",
|
||||
"vue": "^3"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.12",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||
@@ -2444,6 +2490,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-monaco-editor": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-monaco-editor/-/vite-plugin-monaco-editor-1.1.0.tgz",
|
||||
"integrity": "sha512-IvtUqZotrRoVqwT0PBBDIZPNraya3BxN/bfcNfnxZ5rkJiGcNtO5eAOWWSgT7zullIAEqQwxMU83yL9J5k7gww==",
|
||||
"peerDependencies": {
|
||||
"monaco-editor": ">=0.33.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.5.34",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.34.tgz",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"@primeuix/themes": "^1.2.1",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"monaco-editor-vue3": "^1.0.5",
|
||||
"pinia": "^3.0.3",
|
||||
"primevue": "^4.3.6",
|
||||
"vue": "^3.5.17",
|
||||
@@ -25,6 +27,7 @@
|
||||
"autoprefixer": "^10.4.21",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"vite": "^6.0.3"
|
||||
"vite": "^6.0.3",
|
||||
"vite-plugin-monaco-editor": "^1.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! QuickJS 流水线命令:脚本由后端 `/api/v1/quickjs-scripts` 下发,
|
||||
//! 运行时注入 `__native` 后 eval,并调用 `globalThis.__quickjsMain(params)`。
|
||||
//! 调试命令 `run_quickjs_script_source` 则将源码当作异步函数体直接执行,并注入变量 `params`。
|
||||
//!
|
||||
//! 前端用法示例:
|
||||
//!
|
||||
@@ -80,6 +81,45 @@ pub async fn run_quickjs_script(
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 调试:直接执行传入的 QuickJS 源码(异步函数体),可使用 `__native`、`params`、`return`,无需 `__quickjsMain`。
|
||||
#[tauri::command]
|
||||
pub async fn run_quickjs_script_source(
|
||||
app: AppHandle,
|
||||
window: Window,
|
||||
script_source: String,
|
||||
params: Value,
|
||||
) -> Result<Value, String> {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<PipelineEvent>();
|
||||
|
||||
let app_for_emit = app.clone();
|
||||
let win_label = window.label().to_string();
|
||||
const EMIT_LABEL: &str = "<debug-source>";
|
||||
let pump = tokio::spawn(async move {
|
||||
while let Some(ev) = rx.recv().await {
|
||||
let payload: FrontendEvent = match ev {
|
||||
PipelineEvent::Progress(s) => FrontendEvent::Progress {
|
||||
script_name: EMIT_LABEL.to_string(),
|
||||
status: s,
|
||||
},
|
||||
PipelineEvent::Log { level, msg, fields } => FrontendEvent::Log {
|
||||
script_name: EMIT_LABEL.to_string(),
|
||||
level,
|
||||
msg,
|
||||
fields,
|
||||
},
|
||||
};
|
||||
let _ = app_for_emit.emit_to(&win_label, EVENT_NAME, payload);
|
||||
}
|
||||
});
|
||||
|
||||
let result = js_runtime::run_script_source(script_source, params, tx)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
pump.await.ok();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 列出后端 `scripts/quickjs` 目录中的 `.js` 文件名。
|
||||
#[tauri::command]
|
||||
pub async fn list_quickjs_scripts() -> Result<Vec<String>, String> {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Embedded QuickJS runtime:脚本源码由 **Python 后端** 按文件名下发,
|
||||
//! 桌面端通过 HTTP 拉取后在运行时 `eval`,入口为单一的 `globalThis.__quickjsMain`。
|
||||
//! 或由调试接口直接传入字符串:**调试路径**下整段源码作为异步函数体执行,并注入变量 `params`;
|
||||
//! **服务端脚本路径**仍在 eval 后调用 `globalThis.__quickjsMain(params)`。
|
||||
//!
|
||||
//! 后端:`GET /api/v1/quickjs-scripts?name=<文件名.js>`(`ApiResponse`,`data.source`)。
|
||||
//! API 基址:`AICLIENT_API_BASE` 环境变量,默认 `http://127.0.0.1:8001`。
|
||||
@@ -20,6 +21,9 @@ const ENTRY_GLOBAL: &str = "__quickjsMain";
|
||||
|
||||
const RESET_ENTRY_JS: &str = "globalThis.__quickjsMain = undefined;";
|
||||
|
||||
/// 调试执行路径:暂存 invoke 传入的 `params`,eval 前写入 globalThis,立即读入局部变量 `params` 后删除。
|
||||
const INLINE_PARAMS_GLOBAL: &str = "__quickjsInlineParams";
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum JsError {
|
||||
#[error("rquickjs: {0}")]
|
||||
@@ -94,29 +98,34 @@ pub enum PipelineEvent {
|
||||
},
|
||||
}
|
||||
|
||||
/// 从服务端拉取 `script_name` 对应源码,执行 `__quickjsMain(params)` 并返回 JSON 结果。
|
||||
pub async fn run_script(
|
||||
script_name: String,
|
||||
async fn eval_quickjs_main_entry(
|
||||
source: String,
|
||||
eval_label: String,
|
||||
params: Value,
|
||||
events: UnboundedSender<PipelineEvent>,
|
||||
) -> Result<Value, JsError> {
|
||||
let source = fetch_script_source(&script_name).await?;
|
||||
|
||||
let bridge = Arc::new(NativeBridge::new(events));
|
||||
|
||||
let runtime = AsyncRuntime::new()?;
|
||||
let context = AsyncContext::full(&runtime).await?;
|
||||
|
||||
let label_for_eval = eval_label.clone();
|
||||
let label_for_await = eval_label;
|
||||
|
||||
let result_json: Value = async_with!(context => |ctx| {
|
||||
let native_obj = native::build_native_object(ctx.clone(), bridge.clone())
|
||||
.map_err(|e| JsError::QuickJs(format!("install native: {e}")))?;
|
||||
ctx.globals().set("__native", native_obj)
|
||||
.map_err(|e| JsError::QuickJs(format!("set __native: {e}")))?;
|
||||
|
||||
// async 类原生函数依赖全局 __native,须在挂到 globalThis 之后再包一层解析 JSON / 抛错
|
||||
let _: () = ctx.eval(native::NATIVE_ASYNC_SHIM).catch(&ctx)
|
||||
.map_err(|e| JsError::QuickJs(format!("native async shim: {e}")))?;
|
||||
|
||||
let _: () = ctx.eval(RESET_ENTRY_JS).catch(&ctx)
|
||||
.map_err(|e| JsError::QuickJs(format!("reset entry: {e}")))?;
|
||||
let _: () = ctx.eval(source.as_str()).catch(&ctx)
|
||||
.map_err(|e| JsError::QuickJs(format!("eval {}: {e}", script_name)))?;
|
||||
.map_err(|e| JsError::QuickJs(format!("eval {label_for_eval}: {e}")))?;
|
||||
|
||||
let main_fn: rquickjs::Function = ctx.globals().get(ENTRY_GLOBAL)
|
||||
.map_err(|_| JsError::MissingMainEntry)?;
|
||||
@@ -130,7 +139,7 @@ pub async fn run_script(
|
||||
.map_err(|e| JsError::QuickJs(format!("call __quickjsMain: {e}")))?;
|
||||
let value: rquickjs::Value = match Promise::from_value(raw.clone()) {
|
||||
Ok(p) => p.into_future().await
|
||||
.map_err(|e| JsError::QuickJs(format!("await {}: {e}", script_name)))?,
|
||||
.map_err(|e| JsError::QuickJs(format!("await {label_for_await}: {e}")))?,
|
||||
Err(_) => raw,
|
||||
};
|
||||
|
||||
@@ -148,6 +157,89 @@ pub async fn run_script(
|
||||
Ok(result_json)
|
||||
}
|
||||
|
||||
/// 调试:`source` 作为异步函数体执行,可使用 `params`、`await`、`return`;不要求 `__quickjsMain`。
|
||||
async fn eval_quickjs_inline_source(
|
||||
source: String,
|
||||
params: Value,
|
||||
events: UnboundedSender<PipelineEvent>,
|
||||
) -> Result<Value, JsError> {
|
||||
let bridge = Arc::new(NativeBridge::new(events));
|
||||
|
||||
let runtime = AsyncRuntime::new()?;
|
||||
let context = AsyncContext::full(&runtime).await?;
|
||||
|
||||
let wrapped = format!(
|
||||
r#"(async () => {{
|
||||
const params = globalThis.{global};
|
||||
try {{ delete globalThis.{global}; }} catch (_) {{}}
|
||||
{user}
|
||||
}})()"#,
|
||||
global = INLINE_PARAMS_GLOBAL,
|
||||
user = source,
|
||||
);
|
||||
|
||||
let result_json: Value = async_with!(context => |ctx| {
|
||||
let native_obj = native::build_native_object(ctx.clone(), bridge.clone())
|
||||
.map_err(|e| JsError::QuickJs(format!("install native: {e}")))?;
|
||||
ctx.globals().set("__native", native_obj)
|
||||
.map_err(|e| JsError::QuickJs(format!("set __native: {e}")))?;
|
||||
|
||||
let _: () = ctx.eval(native::NATIVE_ASYNC_SHIM).catch(&ctx)
|
||||
.map_err(|e| JsError::QuickJs(format!("native async shim: {e}")))?;
|
||||
|
||||
let _: () = ctx.eval(RESET_ENTRY_JS).catch(&ctx)
|
||||
.map_err(|e| JsError::QuickJs(format!("reset entry: {e}")))?;
|
||||
|
||||
let params_str = serde_json::to_string(¶ms)
|
||||
.map_err(|e| JsError::QuickJs(format!("serialize params: {e}")))?;
|
||||
let params_value: rquickjs::Value = ctx.json_parse(params_str)
|
||||
.map_err(|e| JsError::QuickJs(format!("parse params: {e}")))?;
|
||||
ctx.globals().set(INLINE_PARAMS_GLOBAL, params_value).map_err(|e| {
|
||||
JsError::QuickJs(format!("set {}: {e}", INLINE_PARAMS_GLOBAL))
|
||||
})?;
|
||||
|
||||
let raw: rquickjs::Value = ctx.eval(wrapped.as_str()).catch(&ctx)
|
||||
.map_err(|e| JsError::QuickJs(format!("eval <inline>: {e}")))?;
|
||||
|
||||
let value: rquickjs::Value = match Promise::from_value(raw.clone()) {
|
||||
Ok(p) => p.into_future().await
|
||||
.map_err(|e| JsError::QuickJs(format!("await <inline>: {e}")))?,
|
||||
Err(_) => raw,
|
||||
};
|
||||
|
||||
let json_global: rquickjs::Object = ctx.globals().get("JSON")
|
||||
.map_err(|e| JsError::QuickJs(format!("get JSON: {e}")))?;
|
||||
let stringify: rquickjs::Function = json_global.get("stringify")
|
||||
.map_err(|e| JsError::QuickJs(format!("get JSON.stringify: {e}")))?;
|
||||
let s: String = stringify.call((value,))
|
||||
.map_err(|e| JsError::QuickJs(format!("stringify result: {e}")))?;
|
||||
let parsed: Value = serde_json::from_str(&s)?;
|
||||
Ok::<Value, JsError>(parsed)
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(result_json)
|
||||
}
|
||||
|
||||
/// 从服务端拉取 `script_name` 对应源码,执行 `__quickjsMain(params)` 并返回 JSON 结果。
|
||||
pub async fn run_script(
|
||||
script_name: String,
|
||||
params: Value,
|
||||
events: UnboundedSender<PipelineEvent>,
|
||||
) -> Result<Value, JsError> {
|
||||
let source = fetch_script_source(&script_name).await?;
|
||||
eval_quickjs_main_entry(source, script_name, params, events).await
|
||||
}
|
||||
|
||||
/// 调试执行:整段 `source` 作为异步函数体运行,注入 `params`,无需 `__quickjsMain`。
|
||||
pub async fn run_script_source(
|
||||
source: String,
|
||||
params: Value,
|
||||
events: UnboundedSender<PipelineEvent>,
|
||||
) -> Result<Value, JsError> {
|
||||
eval_quickjs_inline_source(source, params, events).await
|
||||
}
|
||||
|
||||
/// 当前服务端 `scripts/quickjs` 目录下的 `.js` 文件名列表。
|
||||
pub async fn list_scripts() -> Result<Vec<String>, JsError> {
|
||||
let base = api_base().trim_end_matches('/').to_string();
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
//! and may panic-free `Func::from` directly.
|
||||
//! - Async helpers always return a `String` (JSON-encoded result).
|
||||
//! Failures are encoded as `{ "__error": "..." }`; a JS shim
|
||||
//! installed at the end of `build_native_object` detects that shape
|
||||
//! and `throw`s, so JS callers can use natural `try/catch`.
|
||||
//! installed **after** `__native` is assigned on `globalThis` (see `mod.rs`)
|
||||
//! detects that shape and `throw`s, so JS callers can use natural `try/catch`.
|
||||
//! - On success, the JSON string is parsed by the same JS shim and
|
||||
//! returned to the caller as a plain object.
|
||||
|
||||
@@ -26,6 +26,27 @@ use uuid::Uuid;
|
||||
use super::PipelineEvent;
|
||||
use crate::{asr, chat, ffmpeg, http, oss};
|
||||
|
||||
/// 必须在 `globalThis.__native` 已赋值之后执行,否则会访问未定义的 `__native`。
|
||||
pub const NATIVE_ASYNC_SHIM: &str = r#"
|
||||
(function () {
|
||||
const wrap = (name) => {
|
||||
const orig = __native[name];
|
||||
__native[name] = async function (...args) {
|
||||
const s = await orig.apply(__native, args);
|
||||
if (typeof s !== "string") return s;
|
||||
let v;
|
||||
try { v = JSON.parse(s); } catch (_) { return s; }
|
||||
if (v && typeof v === "object" && typeof v.__error === "string") {
|
||||
throw new Error(v.__error);
|
||||
}
|
||||
return v;
|
||||
};
|
||||
};
|
||||
["httpRequest", "ffmpegExtractAudio", "aliyunOssUpload",
|
||||
"aliyunAsrFiletrans", "localAsr", "openaiChat"].forEach(wrap);
|
||||
})();
|
||||
"#;
|
||||
|
||||
/// Shared host state passed by reference into every native function.
|
||||
pub struct NativeBridge {
|
||||
pub events: UnboundedSender<PipelineEvent>,
|
||||
@@ -267,29 +288,5 @@ pub fn build_native_object<'js>(
|
||||
})),
|
||||
)?;
|
||||
|
||||
// The async functions above all return JSON strings. Wrap each one in
|
||||
// a JS shim that JSON.parses the result and throws when the payload
|
||||
// carries the `__error` discriminator.
|
||||
let shim = r#"
|
||||
(function () {
|
||||
const wrap = (name) => {
|
||||
const orig = __native[name];
|
||||
__native[name] = async function (...args) {
|
||||
const s = await orig.apply(__native, args);
|
||||
if (typeof s !== "string") return s;
|
||||
let v;
|
||||
try { v = JSON.parse(s); } catch (_) { return s; }
|
||||
if (v && typeof v === "object" && typeof v.__error === "string") {
|
||||
throw new Error(v.__error);
|
||||
}
|
||||
return v;
|
||||
};
|
||||
};
|
||||
["httpRequest", "ffmpegExtractAudio", "aliyunOssUpload",
|
||||
"aliyunAsrFiletrans", "localAsr", "openaiChat"].forEach(wrap);
|
||||
})();
|
||||
"#;
|
||||
let _: () = ctx.eval(shim)?;
|
||||
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ pub fn run() {
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::auth::sync_auth_session,
|
||||
commands::quickjs::run_quickjs_script,
|
||||
commands::quickjs::run_quickjs_script_source,
|
||||
commands::quickjs::list_quickjs_scripts,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useRoute } from "vue-router";
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const menuItems = [
|
||||
const baseMenuItems = [
|
||||
{ name: "home", label: "首页", to: "/", icon: "home" },
|
||||
{ name: "avatar", label: "形象", to: "/avatar", icon: "avatar" },
|
||||
{ name: "material", label: "素材", to: "/material", icon: "material" },
|
||||
@@ -16,6 +16,13 @@ const menuItems = [
|
||||
{ name: "help", label: "帮助", to: "/help", icon: "help" },
|
||||
];
|
||||
|
||||
const menuItems = computed(() => {
|
||||
if (import.meta.env.DEV) {
|
||||
return [...baseMenuItems, { name: "debug", label: "调试", to: "/debug", icon: "debug" }];
|
||||
}
|
||||
return baseMenuItems;
|
||||
});
|
||||
|
||||
const activeName = computed(() => route.name);
|
||||
</script>
|
||||
|
||||
@@ -65,6 +72,11 @@ const activeName = computed(() => route.name);
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M9.5 9a2.5 2.5 0 1 1 4.2 1.8c-.8.7-1.2 1.2-1.2 2.2M12 17h.01" stroke-linecap="round" />
|
||||
</svg>
|
||||
<svg v-else-if="item.icon === 'debug'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M12 6v2M12 16v2M6 12h2M16 12h2" stroke-linecap="round" />
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path d="M7 7l1.5 1.5M15.5 15.5L17 17M17 7l-1.5 1.5M8.5 15.5L7 17" stroke-linecap="round" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="sidebar-label">{{ item.label }}</span>
|
||||
</RouterLink>
|
||||
|
||||
@@ -3,12 +3,7 @@ import { useAuthStore } from "../stores/auth";
|
||||
import { useAuthStore } from "../stores/auth";
|
||||
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
component: () => import("../layouts/MainLayout.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
children: [
|
||||
|
||||
const placeholder = () => import("../views/PlaceholderView.vue");
|
||||
|
||||
|
||||
@@ -62,7 +57,23 @@ const routes = [
|
||||
},
|
||||
|
||||
{
|
||||
],
|
||||
|
||||
path: "compose",
|
||||
|
||||
name: "compose",
|
||||
|
||||
component: placeholder,
|
||||
|
||||
meta: { title: "合成" },
|
||||
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
path: "extract",
|
||||
|
||||
name: "extract",
|
||||
|
||||
component: placeholder,
|
||||
|
||||
meta: { title: "提取" },
|
||||
|
||||
123
src/views/DebugView.vue
Normal file
123
src/views/DebugView.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { CodeEditor } from "monaco-editor-vue3";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
const DEFAULT_JS = `__native.log("info", "debug.run", { note: "编辑器内脚本" });
|
||||
return { ok: true, echo: params };
|
||||
`;
|
||||
|
||||
const editorCode = ref(DEFAULT_JS);
|
||||
const running = ref(false);
|
||||
const resultText = ref("");
|
||||
const eventLog = ref("");
|
||||
|
||||
let unlisten = null;
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
unlisten = await listen("quickjs:event", (e) => {
|
||||
const line =
|
||||
typeof e.payload === "object"
|
||||
? JSON.stringify(e.payload)
|
||||
: String(e.payload);
|
||||
eventLog.value += `${line}\n`;
|
||||
});
|
||||
} catch {
|
||||
/* 非 Tauri WebView */
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (typeof unlisten === "function") {
|
||||
unlisten();
|
||||
}
|
||||
});
|
||||
|
||||
async function executeScript() {
|
||||
running.value = true;
|
||||
resultText.value = "";
|
||||
try {
|
||||
const out = await invoke("run_quickjs_script_source", {
|
||||
scriptSource: editorCode.value,
|
||||
params: {},
|
||||
});
|
||||
resultText.value = JSON.stringify(out, null, 2);
|
||||
} catch (err) {
|
||||
resultText.value = String(err);
|
||||
} finally {
|
||||
running.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function clearEventLog() {
|
||||
eventLog.value = "";
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-full min-h-0 flex-col gap-4 p-6">
|
||||
<div>
|
||||
<h1 class="gradient-text text-xl font-semibold">调试 · QuickJS</h1>
|
||||
<p class="mt-1 text-sm text-text-muted">
|
||||
源码按<strong class="text-text-secondary">异步函数体</strong>执行,可使用变量
|
||||
<code class="rounded bg-white/10 px-1 py-0.5 font-mono text-xs">params</code>、
|
||||
<code class="rounded bg-white/10 px-1 py-0.5 font-mono text-xs">await</code>、
|
||||
<code class="rounded bg-white/10 px-1 py-0.5 font-mono text-xs">return</code>
|
||||
及全局
|
||||
<code class="rounded bg-white/10 px-1 py-0.5 font-mono text-xs">__native</code>。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex shrink-0 flex-wrap items-center gap-2">
|
||||
<Button label="执行" :loading="running" :disabled="running" @click="executeScript" />
|
||||
<Button
|
||||
label="清空事件日志"
|
||||
severity="secondary"
|
||||
outlined
|
||||
size="small"
|
||||
@click="clearEventLog"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="min-h-[280px] flex-1 overflow-hidden rounded-lg border border-white/10 bg-[#1e1e1e]"
|
||||
style="height: min(58vh, 520px)"
|
||||
>
|
||||
<CodeEditor
|
||||
v-model:value="editorCode"
|
||||
language="javascript"
|
||||
theme="vs-dark"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:options="{
|
||||
automaticLayout: true,
|
||||
minimap: { enabled: true },
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
tabSize: 2,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid min-h-0 shrink-0 gap-3 md:grid-cols-2">
|
||||
<div class="flex min-h-[140px] flex-col rounded-lg border border-white/10 bg-bg-deep/80">
|
||||
<div class="border-b border-white/10 px-3 py-2 text-xs font-medium text-text-muted">返回值</div>
|
||||
<pre
|
||||
class="min-h-[120px] flex-1 overflow-auto p-3 font-mono text-xs text-emerald-200/90 whitespace-pre-wrap"
|
||||
>{{ resultText || "(尚无)" }}</pre
|
||||
>
|
||||
</div>
|
||||
<div class="flex min-h-[140px] flex-col rounded-lg border border-white/10 bg-bg-deep/80">
|
||||
<div class="border-b border-white/10 px-3 py-2 text-xs font-medium text-text-muted">
|
||||
quickjs:event
|
||||
</div>
|
||||
<pre
|
||||
class="min-h-[120px] flex-1 overflow-auto p-3 font-mono text-[11px] text-text-muted whitespace-pre-wrap"
|
||||
>{{ eventLog || "(尚无)" }}</pre
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,11 +1,17 @@
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import monacoEditorPluginRaw from "vite-plugin-monaco-editor";
|
||||
|
||||
const monacoEditorPlugin =
|
||||
typeof monacoEditorPluginRaw === "function"
|
||||
? monacoEditorPluginRaw
|
||||
: monacoEditorPluginRaw.default;
|
||||
|
||||
const host = process.env.TAURI_DEV_HOST;
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue(), tailwindcss()],
|
||||
plugins: [monacoEditorPlugin({}), vue(), tailwindcss()],
|
||||
clearScreen: false,
|
||||
server: {
|
||||
port: 1420,
|
||||
|
||||
Reference in New Issue
Block a user