refactor(stage-tamagotchi,crates): split out all using tauri plugins
This commit is contained in:
Generated
+86
-4
@@ -174,9 +174,6 @@ dependencies = [
|
||||
"hf-hub",
|
||||
"log",
|
||||
"ndarray",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
"ort",
|
||||
"rubato",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -186,17 +183,20 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-global-shortcut",
|
||||
"tauri-plugin-ipc-audio-transcription-candle",
|
||||
"tauri-plugin-ipc-audio-vad-ort",
|
||||
"tauri-plugin-log",
|
||||
"tauri-plugin-mcp",
|
||||
"tauri-plugin-os",
|
||||
"tauri-plugin-positioner",
|
||||
"tauri-plugin-prevent-default",
|
||||
"tauri-plugin-window-pass-through-on-hover",
|
||||
"tauri-plugin-window-router-link",
|
||||
"tauri-plugin-window-state",
|
||||
"tauri-specta",
|
||||
"tokenizers",
|
||||
"tokio",
|
||||
"url",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5666,6 +5666,53 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-ipc-audio-transcription-candle"
|
||||
version = "0.7.0-alpha.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
"candle-core",
|
||||
"candle-nn",
|
||||
"candle-transformers",
|
||||
"clap",
|
||||
"hf-hub",
|
||||
"log",
|
||||
"ndarray",
|
||||
"rubato",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"symphonia",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tokenizers",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-ipc-audio-vad-ort"
|
||||
version = "0.7.0-alpha.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
"clap",
|
||||
"hf-hub",
|
||||
"log",
|
||||
"ndarray",
|
||||
"ort",
|
||||
"rubato",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"specta",
|
||||
"specta-typescript",
|
||||
"symphonia",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-specta",
|
||||
"tokenizers",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-log"
|
||||
version = "2.6.0"
|
||||
@@ -5750,6 +5797,41 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-window-pass-through-on-hover"
|
||||
version = "0.7.0-alpha.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"log",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"specta",
|
||||
"specta-typescript",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-specta",
|
||||
"tokio",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-window-router-link"
|
||||
version = "0.7.0-alpha.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"specta",
|
||||
"specta-typescript",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-specta",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-window-state"
|
||||
version = "2.4.0"
|
||||
|
||||
+11
-5
@@ -1,7 +1,11 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/*",
|
||||
"apps/stage-tamagotchi/src-tauri"
|
||||
"crates/tauri-plugin-ipc-audio-transcription-candle",
|
||||
"crates/tauri-plugin-ipc-audio-vad-ort",
|
||||
"crates/tauri-plugin-mcp",
|
||||
"crates/tauri-plugin-window-pass-through-on-hover",
|
||||
"crates/tauri-plugin-window-router-link",
|
||||
"apps/stage-tamagotchi/src-tauri",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@@ -9,9 +13,11 @@ resolver = "2"
|
||||
version = "0.7.0-alpha.1"
|
||||
|
||||
[workspace.dependencies]
|
||||
|
||||
[workspace.dependencies.tauri-plugin-mcp]
|
||||
path = "./crates/tauri-plugin-mcp"
|
||||
tauri-plugin-ipc-audio-transcription-candle = { path = "./crates/tauri-plugin-ipc-audio-transcription-candle" }
|
||||
tauri-plugin-ipc-audio-vad-ort = { path = "./crates/tauri-plugin-ipc-audio-vad-ort" }
|
||||
tauri-plugin-mcp = { path = "./crates/tauri-plugin-mcp" }
|
||||
tauri-plugin-window-pass-through-on-hover = { path = "./crates/tauri-plugin-window-pass-through-on-hover" }
|
||||
tauri-plugin-window-router-link = { path = "./crates/tauri-plugin-window-router-link" }
|
||||
|
||||
[profile]
|
||||
|
||||
|
||||
@@ -11,12 +11,6 @@ publish = false
|
||||
|
||||
[features]
|
||||
default = []
|
||||
cuda = [
|
||||
"ort/cuda",
|
||||
"candle-core/cuda",
|
||||
"candle-nn/cuda",
|
||||
"candle-transformers/cuda"
|
||||
]
|
||||
|
||||
[lib]
|
||||
name = "app_lib"
|
||||
@@ -39,7 +33,11 @@ tauri = { version = "2.3.1", features = [
|
||||
tauri-plugin-log = "2.0.0-rc"
|
||||
tauri-plugin-os = "2"
|
||||
tauri-plugin-mcp = { workspace = true }
|
||||
tauri-plugin-ipc-audio-transcription-candle = { workspace = true }
|
||||
tauri-plugin-ipc-audio-vad-ort = { workspace = true }
|
||||
tauri-plugin-prevent-default = "1.3"
|
||||
tauri-plugin-window-pass-through-on-hover = { workspace = true }
|
||||
tauri-plugin-window-router-link = { workspace = true }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
tokio = "1.45.1"
|
||||
@@ -62,26 +60,5 @@ specta = "=2.0.0-rc.22"
|
||||
specta-typescript = "0.0.9"
|
||||
tauri-specta = { version = "=2.0.0-rc.21", features = ["derive", "typescript"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2 = "0.6.1"
|
||||
objc2-foundation = "0.3.1"
|
||||
ort = { version = "2.0.0-rc.10", features = ["ndarray", "coreml"] }
|
||||
candle-core = { version = "0.9.1", features = ["metal"] }
|
||||
candle-nn = { version = "0.9.1", features = ["metal"] }
|
||||
candle-transformers = { version = "0.9.1", features = ["metal"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows = { version = "0.61.1", features = ["Win32_UI_WindowsAndMessaging", "Win32_Foundation"] }
|
||||
ort = { version = "2.0.0-rc.10", features = ["ndarray", "directml"] }
|
||||
candle-core = { version = "0.9.1" }
|
||||
candle-nn = { version = "0.9.1" }
|
||||
candle-transformers = { version = "0.9.1" }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
ort = { version = "2.0.0-rc.10", features = ["ndarray"] }
|
||||
candle-core = { version = "0.9.1" }
|
||||
candle-nn = { version = "0.9.1" }
|
||||
candle-transformers = { version = "0.9.1" }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-global-shortcut = "2.0.0"
|
||||
|
||||
@@ -1,51 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::try_build(
|
||||
tauri_build::Attributes::new()
|
||||
.plugin(
|
||||
"proj-airi-tauri-plugin-audio-transcription",
|
||||
tauri_build::InlinedPlugin::new()
|
||||
.commands(&["load_model_whisper", "audio_transcription"])
|
||||
.default_permission(tauri_build::DefaultPermissionRule::AllowAllCommands),
|
||||
)
|
||||
.plugin(
|
||||
"proj-airi-tauri-plugin-audio-vad",
|
||||
tauri_build::InlinedPlugin::new()
|
||||
.commands(&["load_model_silero_vad", "audio_vad"])
|
||||
.default_permission(tauri_build::DefaultPermissionRule::AllowAllCommands),
|
||||
)
|
||||
.plugin(
|
||||
"proj-airi-tauri-plugin-window",
|
||||
tauri_build::InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"get_display_info",
|
||||
"get_current_window_info",
|
||||
"set_position",
|
||||
])
|
||||
.default_permission(tauri_build::DefaultPermissionRule::AllowAllCommands),
|
||||
)
|
||||
.plugin(
|
||||
"proj-airi-tauri-plugin-window-pass-through-on-hover",
|
||||
tauri_build::InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"start_monitor",
|
||||
"stop_monitor",
|
||||
"start_pass_through",
|
||||
"stop_pass_through",
|
||||
])
|
||||
.default_permission(tauri_build::DefaultPermissionRule::AllowAllCommands),
|
||||
)
|
||||
.plugin(
|
||||
"proj-airi-tauri-plugin-window-router-link",
|
||||
tauri_build::InlinedPlugin::new()
|
||||
.commands(&["go"])
|
||||
.default_permission(tauri_build::DefaultPermissionRule::AllowAllCommands),
|
||||
)
|
||||
.plugin(
|
||||
"proj-airi-tauri-plugin-window-persistence",
|
||||
tauri_build::InlinedPlugin::new()
|
||||
.commands(&["save", "restore"])
|
||||
.default_permission(tauri_build::DefaultPermissionRule::AllowAllCommands),
|
||||
),
|
||||
)
|
||||
.expect("Failed to build Tauri application");
|
||||
tauri_build::build();
|
||||
}
|
||||
|
||||
@@ -26,11 +26,9 @@
|
||||
"global-shortcut:allow-register",
|
||||
"global-shortcut:allow-unregister",
|
||||
"global-shortcut:allow-unregister-all",
|
||||
"proj-airi-tauri-plugin-window-pass-through-on-hover:default",
|
||||
"proj-airi-tauri-plugin-window-persistence:default",
|
||||
"proj-airi-tauri-plugin-window:default",
|
||||
"proj-airi-tauri-plugin-window-router-link:default",
|
||||
"proj-airi-tauri-plugin-audio-transcription:default",
|
||||
"proj-airi-tauri-plugin-audio-vad:default"
|
||||
"window-pass-through-on-hover:default",
|
||||
"window-router-link:default",
|
||||
"ipc-audio-transcription-candle:default",
|
||||
"ipc-audio-vad-ort:default"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod commands;
|
||||
pub mod models;
|
||||
pub mod windows;
|
||||
|
||||
@@ -8,11 +8,12 @@ use tauri::{
|
||||
};
|
||||
use tauri_plugin_positioner::WindowExt;
|
||||
use tauri_plugin_prevent_default::Flags;
|
||||
use tauri_plugin_window_router_link::WindowMatcher;
|
||||
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
||||
|
||||
mod app;
|
||||
mod helpers;
|
||||
mod plugins;
|
||||
|
||||
use app::windows::{chat, onboarding, settings};
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
@@ -28,11 +29,25 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.plugin(tauri_plugin_positioner::init())
|
||||
// Internal plugins
|
||||
.plugin(plugins::window_pass_through_on_hover::init())
|
||||
.plugin(plugins::window_router_link::init())
|
||||
.plugin(plugins::audio_transcription::init())
|
||||
.plugin(plugins::audio_vad::init())
|
||||
// Project AIRI plugins
|
||||
.plugin(tauri_plugin_ipc_audio_transcription_candle::init())
|
||||
.plugin(tauri_plugin_ipc_audio_vad_ort::init())
|
||||
.plugin(tauri_plugin_window_pass_through_on_hover::init())
|
||||
.plugin(tauri_plugin_window_router_link::init(
|
||||
WindowMatcher::new()
|
||||
.register("chat", |app| {
|
||||
chat::new_chat_window(&app)
|
||||
.map_err(|e| e)
|
||||
})
|
||||
.register("settings", |app| {
|
||||
settings::new_settings_window(&app)
|
||||
.map_err(|e| e)
|
||||
})
|
||||
.register("onboarding", |app| {
|
||||
onboarding::new_onboarding_window(&app)
|
||||
.map_err(|e| e)
|
||||
})
|
||||
))
|
||||
.setup(|app| {
|
||||
let mut builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default());
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod audio_transcription;
|
||||
pub mod audio_vad;
|
||||
pub mod window_pass_through_on_hover;
|
||||
pub mod window_router_link;
|
||||
@@ -1,85 +0,0 @@
|
||||
#[cfg(debug_assertions)]
|
||||
use specta_typescript::Typescript;
|
||||
use tauri::{
|
||||
Manager,
|
||||
Runtime,
|
||||
plugin::{Builder, TauriPlugin},
|
||||
};
|
||||
|
||||
use crate::app::windows::{chat, onboarding, settings};
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn go<R: Runtime>(
|
||||
window: tauri::Window<R>,
|
||||
route: String,
|
||||
// ↓ #[specta(optional)] is not available in parameters.
|
||||
// ↓ This will be `string | null` in the generated TypeScript code
|
||||
window_label: Option<String>,
|
||||
) -> std::result::Result<(), String> {
|
||||
let app = window.app_handle();
|
||||
|
||||
let target_window = match window_label.as_deref() {
|
||||
Some("chat") => match app.get_webview_window("chat") {
|
||||
Some(window) => window,
|
||||
None => {
|
||||
chat::new_chat_window(app).map_err(|e| format!("Failed to create chat window: {}", e))?
|
||||
},
|
||||
},
|
||||
Some("settings") => match app.get_webview_window("settings") {
|
||||
Some(window) => window,
|
||||
None => settings::new_settings_window(app)
|
||||
.map_err(|e| format!("Failed to create settings window: {}", e))?,
|
||||
},
|
||||
Some("onboarding") => match app.get_webview_window("onboarding") {
|
||||
Some(window) => window,
|
||||
None => onboarding::new_onboarding_window(app)
|
||||
.map_err(|e| format!("Failed to create onboarding window: {}", e))?,
|
||||
},
|
||||
Some(label) => {
|
||||
return Err(format!("Unknown window label: {}", label));
|
||||
},
|
||||
None => {
|
||||
return Err("Missing window label".into());
|
||||
},
|
||||
};
|
||||
|
||||
let mut current_url = target_window
|
||||
.url()
|
||||
.map_err(|e| format!("Failed to get current URL: {}", e))?;
|
||||
let route: String = "/".to_string() + route.trim_start_matches('/');
|
||||
current_url.set_fragment(Some(route.to_string().as_str()));
|
||||
|
||||
let _ = target_window.show();
|
||||
if let Ok(url) = target_window.url() {
|
||||
if url == current_url {
|
||||
// If the URL is already the same, we don't need to navigate again.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _ = target_window.navigate(current_url);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const PLUGIN_NAME: &str = "proj-airi-tauri-plugin-window-router-link";
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
let builder = tauri_specta::Builder::<R>::new()
|
||||
.plugin_name(PLUGIN_NAME)
|
||||
.commands(tauri_specta::collect_commands![go::<tauri::Wry>]);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
builder
|
||||
.export(
|
||||
Typescript::default().header("// @ts-nocheck\n"),
|
||||
"../src/commands/bindings/window-router-link.ts",
|
||||
)
|
||||
.expect("Failed to export typescript bindings");
|
||||
|
||||
Builder::new(PLUGIN_NAME)
|
||||
.invoke_handler(builder.invoke_handler())
|
||||
.setup(|_, _| Ok(()))
|
||||
.build()
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { computed, onMounted, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { RouterView } from 'vue-router'
|
||||
|
||||
import { WindowRouterLink } from './commands'
|
||||
import { commands } from './bindings/tauri-plugins/window-router-link'
|
||||
import { useAppRuntime } from './composables/runtime'
|
||||
import { useTauriEvent } from './composables/tauri'
|
||||
import { useWindowControlStore } from './stores/window-controls'
|
||||
@@ -48,7 +48,7 @@ watch(themeColorsHueDynamic, () => {
|
||||
|
||||
watch(shouldShowSetup, () => {
|
||||
if (shouldShowSetup.value) {
|
||||
WindowRouterLink.commands.go('/onboarding', 'onboarding')
|
||||
commands.go('/onboarding', 'onboarding')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@
|
||||
export const commands = {
|
||||
async startPassThrough() : Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|start_pass_through") };
|
||||
return { status: "ok", data: await TAURI_INVOKE("plugin:window-pass-through-on-hover|start_pass_through") };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@@ -16,7 +16,7 @@ async startPassThrough() : Promise<Result<null, string>> {
|
||||
},
|
||||
async stopPassThrough() : Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|stop_pass_through") };
|
||||
return { status: "ok", data: await TAURI_INVOKE("plugin:window-pass-through-on-hover|stop_pass_through") };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
export const commands = {
|
||||
async go(route: string, windowLabel: string | null) : Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("plugin:proj-airi-tauri-plugin-window-router-link|go", { route, windowLabel }) };
|
||||
return { status: "ok", data: await TAURI_INVOKE("plugin:window-router-link|go", { route, windowLabel }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
@@ -1,2 +0,0 @@
|
||||
export * as WindowPassThroughOnHover from './bindings/window-pass-through-on-hover'
|
||||
export * as WindowRouterLink from './bindings/window-router-link'
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { WindowRouterLink } from '../../commands'
|
||||
import { commands } from '../../bindings/tauri-plugins/window-router-link'
|
||||
|
||||
const props = defineProps<{
|
||||
to: string
|
||||
@@ -7,7 +7,7 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
function handleClick() {
|
||||
WindowRouterLink.commands.go(props.to, props.label ?? null)
|
||||
commands.go(props.to, props.label ?? null)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ export function useWhisperTranscription(config: Partial<WhisperConfig> = {}) {
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
await invoke('plugin:proj-airi-tauri-plugin-audio-transcription|load_model_whisper', { modelType })
|
||||
await invoke('plugin:ipc-audio-transcription-candle|load_candle_model_whisper', { modelType })
|
||||
isModelLoaded.value = true
|
||||
}
|
||||
catch (err) {
|
||||
@@ -86,7 +86,7 @@ export function useWhisperTranscription(config: Partial<WhisperConfig> = {}) {
|
||||
isProcessing.value = true
|
||||
|
||||
const audioArray = Array.from(segment.audioData)
|
||||
const [result, language] = await invoke('plugin:proj-airi-tauri-plugin-audio-transcription|audio_transcription', {
|
||||
const [result, language] = await invoke('plugin:ipc-audio-transcription-candle|ipc_audio_transcription', {
|
||||
chunk: audioArray,
|
||||
language: mapLanguageCodeToName(locale),
|
||||
}) || ['', '']
|
||||
|
||||
@@ -28,13 +28,13 @@ export function useTauriPointAndWindowFrame() {
|
||||
}
|
||||
|
||||
function addListeners() {
|
||||
listen('tauri-app:proj-airi:window-pass-through-on-hover:cursor-position', (event) => {
|
||||
listen('tauri-plugins:tauri-plugin-window-pass-through-on-hover:cursor-position', (event) => {
|
||||
_onCursorPosition(event)
|
||||
}).then((fn) => {
|
||||
unListenFuncs.value.push(fn)
|
||||
})
|
||||
|
||||
listen('tauri-app:proj-airi:window-pass-through-on-hover:window-frame', (event) => {
|
||||
listen('tauri-plugins:tauri-plugin-window-pass-through-on-hover:window-frame', (event) => {
|
||||
_onWindowFrame(event)
|
||||
}).then((fn) => {
|
||||
unListenFuncs.value.push(fn)
|
||||
@@ -48,13 +48,13 @@ export function useTauriPointAndWindowFrame() {
|
||||
unListenFuncs.value.forEach(fn => fn?.())
|
||||
unListenFuncs.value.length = 0
|
||||
|
||||
invoke('plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|stop_monitor')
|
||||
invoke('plugin:window-pass-through-on-hover|stop_tracing_cursor')
|
||||
})
|
||||
|
||||
import.meta.hot.on('vite:afterUpdate', () => {
|
||||
unListenFuncs.value.length === 0 && addListeners()
|
||||
|
||||
invoke('plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|start_monitor')
|
||||
invoke('plugin:window-pass-through-on-hover|start_tracing_cursor')
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -51,12 +51,12 @@ interface Events {
|
||||
}
|
||||
|
||||
export interface AiriTamagotchiEvents extends Events {
|
||||
'tauri-app:proj-airi:window-pass-through-on-hover:cursor-position': Point
|
||||
'tauri-app:proj-airi:window-pass-through-on-hover:window-frame': WindowFrame
|
||||
'tauri-app:proj-airi:window-pass-through-on-hover:pass-through-enabled': boolean
|
||||
'tauri-plugins:tauri-plugin-window-pass-through-on-hover:cursor-position': Point
|
||||
'tauri-plugins:tauri-plugin-window-pass-through-on-hover:window-frame': WindowFrame
|
||||
'tauri-plugins:tauri-plugin-window-pass-through-on-hover:pass-through-enabled': boolean
|
||||
'tauri-plugins:tauri-plugin-ipc-audio-transcription-candle:load-model-silero-vad-progress': [boolean, string, number, number, number]
|
||||
'tauri-plugins:tauri-plugin-ipc-audio-vad-ort:load-model-whisper-progress': [boolean, string, number, number, number]
|
||||
'mcp_plugin_destroyed': undefined
|
||||
'tauri-tamagotchi://plugins:proj-airi:audio-vad:load-model-silero-vad-progress': [boolean, string, number, number, number]
|
||||
'tauri-tamagotchi://plugins:proj-airi:audio-transcription:load-model-whisper-progress': [boolean, string, number, number, number]
|
||||
}
|
||||
|
||||
export interface DisplayInfo {
|
||||
|
||||
@@ -50,13 +50,13 @@ const modeIndicatorClass = computed(() => {
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await invoke('plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|start_monitor')
|
||||
await invoke('plugin:window-pass-through-on-hover|start_tracing_cursor')
|
||||
await startClickThrough()
|
||||
})
|
||||
|
||||
onUnmounted(async () => {
|
||||
await stopClickThrough()
|
||||
await invoke('plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|stop_monitor')
|
||||
await invoke('plugin:window-pass-through-on-hover|stop_tracing_cursor')
|
||||
})
|
||||
|
||||
const unListenFuncs: (() => void)[] = []
|
||||
@@ -71,18 +71,18 @@ function openChat() {
|
||||
|
||||
onMounted(async () => {
|
||||
// VAD
|
||||
unListenFuncs.push(await listen('tauri-tamagotchi://plugins:proj-airi:audio-vad:load-model-silero-vad-progress', (event) => {
|
||||
unListenFuncs.push(await listen('tauri-plugins:tauri-plugin-ipc-audio-transcription-candle:load-model-silero-vad-progress', (event) => {
|
||||
const [_, filename, progress, totalSize, currentSize] = event.payload
|
||||
resourcesStore.updateResourceProgress('hearing', 'vad', { filename, progress, totalSize, currentSize })
|
||||
}))
|
||||
invoke('plugin:proj-airi-tauri-plugin-audio-vad|load_model_silero_vad')
|
||||
invoke('plugin:ipc-audio-vad-ort|load_ort_model_silero_vad')
|
||||
|
||||
// Whisper
|
||||
unListenFuncs.push(await listen('tauri-tamagotchi://plugins:proj-airi:audio-transcription:load-model-whisper-progress', (event) => {
|
||||
unListenFuncs.push(await listen('tauri-plugins:tauri-plugin-ipc-audio-vad-ort:load-model-whisper-progress', (event) => {
|
||||
const [_, filename, progress, totalSize, currentSize] = event.payload
|
||||
resourcesStore.updateResourceProgress('hearing', 'whisper', { filename, progress, totalSize, currentSize })
|
||||
}))
|
||||
invoke('plugin:proj-airi-tauri-plugin-audio-transcription|load_model_whisper', { modelType: 'medium' })
|
||||
invoke('plugin:ipc-audio-transcription-candle|load_candle_model_whisper', { modelType: 'medium' })
|
||||
|
||||
if (connected.value)
|
||||
return
|
||||
@@ -107,10 +107,10 @@ if (import.meta.hot) { // For better DX
|
||||
unListenFuncs.forEach(fn => fn?.())
|
||||
unListenFuncs.length = 0
|
||||
|
||||
invoke('plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|stop_monitor')
|
||||
invoke('plugin:window-pass-through-on-hover|stop_tracing_cursor')
|
||||
})
|
||||
import.meta.hot.on('vite:afterUpdate', async () => {
|
||||
invoke('plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|start_monitor')
|
||||
invoke('plugin:window-pass-through-on-hover|start_tracing_cursor')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -8,18 +8,16 @@ export interface InvokeMethods {
|
||||
'open_chat_window': { args: undefined, options: undefined, returns: void }
|
||||
|
||||
// Plugin - Audio Transcription
|
||||
'plugin:proj-airi-tauri-plugin-audio-transcription|load_model_whisper': { args: { modelType: 'base' | 'largev3' | 'tiny' | 'medium' }, options: undefined, returns: void }
|
||||
'plugin:proj-airi-tauri-plugin-audio-transcription|audio_transcription': { args: { chunk: number[], language: string }, options: undefined, returns: [string, string] }
|
||||
'plugin:ipc-audio-transcription-candle|load_candle_model_whisper': { args: { modelType: 'base' | 'largev3' | 'tiny' | 'medium' }, options: undefined, returns: void }
|
||||
'plugin:ipc-audio-transcription-candle|ipc_audio_transcription': { args: { chunk: number[], language: string }, options: undefined, returns: [string, string] }
|
||||
|
||||
// Plugin - Audio VAD
|
||||
'plugin:proj-airi-tauri-plugin-audio-vad|load_model_silero_vad': { args: undefined, options: undefined, returns: void }
|
||||
'plugin:proj-airi-tauri-plugin-audio-vad|audio_vad': { args: { inputData: { input: number[], sr: number, state: number[] } }, options: undefined, returns: number }
|
||||
'plugin:ipc-audio-vad-ort|load_ort_model_silero_vad': { args: undefined, options: undefined, returns: void }
|
||||
'plugin:ipc-audio-vad-ort|ipc_audio_vad': { args: { inputData: { input: number[], sr: number, state: number[] } }, options: undefined, returns: number }
|
||||
|
||||
// Plugin - Window Pass through on hover
|
||||
'plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|start_monitor': { args: undefined, options: undefined, returns: void }
|
||||
'plugin:proj-airi-tauri-plugin-window-pass-through-on-hover|stop_monitor': { args: undefined, options: undefined, returns: void }
|
||||
|
||||
// Plugin - WindowRouterLink
|
||||
'plugin:window-pass-through-on-hover|start_tracing_cursor': { args: undefined, options: undefined, returns: void }
|
||||
'plugin:window-pass-through-on-hover|stop_tracing_cursor': { args: undefined, options: undefined, returns: void }
|
||||
}
|
||||
|
||||
export interface InvokeMethodShape {
|
||||
|
||||
@@ -53,7 +53,7 @@ export class VAD {
|
||||
try {
|
||||
this.emit('status', { type: 'info', message: 'Loading VAD model...' })
|
||||
|
||||
await invoke('plugin:proj-airi-tauri-plugin-audio-vad|load_model_silero_vad')
|
||||
await invoke('plugin:ipc-audio-vad-ort|load_ort_model_silero_vad')
|
||||
this.isReady = true
|
||||
|
||||
this.emit('status', { type: 'info', message: 'VAD model loaded successfully' })
|
||||
@@ -150,7 +150,7 @@ export class VAD {
|
||||
private async detectSpeech(buffer: Float32Array): Promise<boolean> {
|
||||
// Use Rust backend for inference
|
||||
const result = await (this.inferenceChain = this.inferenceChain.then(() =>
|
||||
invoke('plugin:proj-airi-tauri-plugin-audio-vad|audio_vad', {
|
||||
invoke('plugin:ipc-audio-vad-ort|ipc_audio_vad', {
|
||||
inputData: {
|
||||
input: Array.from(buffer),
|
||||
sr: this.config.sampleRate,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { WindowPassThroughOnHover } from '../commands'
|
||||
import { commands } from '../bindings/tauri-plugins/window-pass-through-on-hover'
|
||||
|
||||
export async function startClickThrough() {
|
||||
await WindowPassThroughOnHover.commands.startPassThrough()
|
||||
await commands.startPassThrough()
|
||||
}
|
||||
|
||||
export async function stopClickThrough() {
|
||||
await WindowPassThroughOnHover.commands.stopPassThrough()
|
||||
await commands.stopPassThrough()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
[package]
|
||||
name = "tauri-plugin-ipc-audio-transcription-candle"
|
||||
version.workspace = true
|
||||
description = "Audio transcription plugin for Tauri using Candle (huggingface/candle)"
|
||||
authors = [ "Neko Ayaka <neko@ayaka.moe>" ]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/moeru-ai/airi"
|
||||
edition = "2024"
|
||||
rust-version = "1.85.0"
|
||||
links = "tauri-plugin-ipc-audio-transcription-candle"
|
||||
|
||||
[lib]
|
||||
name = "tauri_plugin_ipc_audio_transcription_candle"
|
||||
crate-type = [
|
||||
"staticlib",
|
||||
"cdylib",
|
||||
"rlib"
|
||||
]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
cuda = [
|
||||
"candle-core/cuda",
|
||||
"candle-nn/cuda",
|
||||
"candle-transformers/cuda"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
tauri = "2.3.1"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
tokio = "1.45.1"
|
||||
ndarray = "0.16.1"
|
||||
candle-core = { version = "0.9.1" }
|
||||
candle-nn = { version = "0.9.1" }
|
||||
candle-transformers = { version = "0.9.1" }
|
||||
log = "0.4"
|
||||
anyhow = "1"
|
||||
tokenizers = "0.21.2"
|
||||
hf-hub = "0.4.3"
|
||||
symphonia = "0.5.4"
|
||||
rubato = "0.16.2"
|
||||
byteorder = "1.5.0"
|
||||
clap = { version = "4.5.40", features = ["derive"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
candle-core = { version = "0.9.1", features = ["metal"] }
|
||||
candle-nn = { version = "0.9.1", features = ["metal"] }
|
||||
candle-transformers = { version = "0.9.1", features = ["metal"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
candle-core = { version = "0.9.1" }
|
||||
candle-nn = { version = "0.9.1" }
|
||||
candle-transformers = { version = "0.9.1" }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
candle-core = { version = "0.9.1" }
|
||||
candle-nn = { version = "0.9.1" }
|
||||
candle-transformers = { version = "0.9.1" }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { version = "2.2", features = [ "build" ] }
|
||||
@@ -0,0 +1,5 @@
|
||||
const COMMANDS: &[&str] = &["load_candle_model_whisper", "ipc_audio_transcription"];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS).build();
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-ipc-audio-transcription"
|
||||
description = "Enables the ipc_audio_transcription command without any pre-configured scope."
|
||||
commands.allow = ["ipc_audio_transcription"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-ipc-audio-transcription"
|
||||
description = "Denies the ipc_audio_transcription command without any pre-configured scope."
|
||||
commands.deny = ["ipc_audio_transcription"]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-load-candle-model-whisper"
|
||||
description = "Enables the load_candle_model_whisper command without any pre-configured scope."
|
||||
commands.allow = ["load_candle_model_whisper"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-load-candle-model-whisper"
|
||||
description = "Denies the load_candle_model_whisper command without any pre-configured scope."
|
||||
commands.deny = ["load_candle_model_whisper"]
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
## Default Permission
|
||||
|
||||
This permission set configures what kind of
|
||||
operations are available from the mcp plugin.
|
||||
|
||||
#### Granted Permissions
|
||||
|
||||
All operations are enabled by default.
|
||||
|
||||
#### This default permission set includes the following:
|
||||
|
||||
- `allow-load-candle-model-whisper`
|
||||
- `allow-ipc-audio-transcription`
|
||||
|
||||
## Permission Table
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Identifier</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`ipc-audio-transcription-candle:allow-ipc-audio-transcription`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the ipc_audio_transcription command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`ipc-audio-transcription-candle:deny-ipc-audio-transcription`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the ipc_audio_transcription command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`ipc-audio-transcription-candle:allow-load-candle-model-whisper`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the load_candle_model_whisper command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`ipc-audio-transcription-candle:deny-load-candle-model-whisper`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the load_candle_model_whisper command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -0,0 +1,15 @@
|
||||
"$schema" = "schemas/schema.json"
|
||||
|
||||
[default]
|
||||
description = """
|
||||
This permission set configures what kind of
|
||||
operations are available from the mcp plugin.
|
||||
|
||||
#### Granted Permissions
|
||||
|
||||
All operations are enabled by default.
|
||||
"""
|
||||
permissions = [
|
||||
"allow-load-candle-model-whisper",
|
||||
"allow-ipc-audio-transcription",
|
||||
]
|
||||
@@ -0,0 +1,330 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PermissionFile",
|
||||
"description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"default": {
|
||||
"description": "The default permission set for the plugin",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/DefaultPermission"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"set": {
|
||||
"description": "A list of permissions sets defined",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PermissionSet"
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"description": "A list of inlined permissions",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Permission"
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"DefaultPermission": {
|
||||
"description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"permissions"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"description": "The version of the permission.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint64",
|
||||
"minimum": 1.0
|
||||
},
|
||||
"description": {
|
||||
"description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"permissions": {
|
||||
"description": "All permissions this set contains.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"PermissionSet": {
|
||||
"description": "A set of direct permissions grouped together under a new name.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"description",
|
||||
"identifier",
|
||||
"permissions"
|
||||
],
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"description": "A unique identifier for the permission.",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Human-readable description of what the permission does.",
|
||||
"type": "string"
|
||||
},
|
||||
"permissions": {
|
||||
"description": "All permissions this set contains.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PermissionKind"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Permission": {
|
||||
"description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"identifier"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"description": "The version of the permission.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint64",
|
||||
"minimum": 1.0
|
||||
},
|
||||
"identifier": {
|
||||
"description": "A unique identifier for the permission.",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"commands": {
|
||||
"description": "Allowed or denied commands when using this permission.",
|
||||
"default": {
|
||||
"allow": [],
|
||||
"deny": []
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Commands"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scope": {
|
||||
"description": "Allowed or denied scoped when using this permission.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Scopes"
|
||||
}
|
||||
]
|
||||
},
|
||||
"platforms": {
|
||||
"description": "Target platforms this permission applies. By default all platforms are affected by this permission.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/Target"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Commands": {
|
||||
"description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow": {
|
||||
"description": "Allowed command.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"deny": {
|
||||
"description": "Denied command, which takes priority.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Scopes": {
|
||||
"description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow": {
|
||||
"description": "Data that defines what is allowed by the scope.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
},
|
||||
"deny": {
|
||||
"description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Value": {
|
||||
"description": "All supported ACL values.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Represents a null JSON value.",
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"description": "Represents a [`bool`].",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"description": "Represents a valid ACL [`Number`].",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Number"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Represents a [`String`].",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Represents a list of other [`Value`]s.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Represents a map of [`String`] keys to [`Value`]s.",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Number": {
|
||||
"description": "A valid ACL number.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Represents an [`i64`].",
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
{
|
||||
"description": "Represents a [`f64`].",
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Target": {
|
||||
"description": "Platform target.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "MacOS.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"macOS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Windows.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"windows"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Linux.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Android.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "iOS.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"iOS"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionKind": {
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Enables the ipc_audio_transcription command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-ipc-audio-transcription",
|
||||
"markdownDescription": "Enables the ipc_audio_transcription command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the ipc_audio_transcription command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-ipc-audio-transcription",
|
||||
"markdownDescription": "Denies the ipc_audio_transcription command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the load_candle_model_whisper command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-load-candle-model-whisper",
|
||||
"markdownDescription": "Enables the load_candle_model_whisper command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the load_candle_model_whisper command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-load-candle-model-whisper",
|
||||
"markdownDescription": "Denies the load_candle_model_whisper command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures what kind of\noperations are available from the mcp plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n#### This default permission set includes:\n\n- `allow-load-candle-model-whisper`\n- `allow-ipc-audio-transcription`",
|
||||
"type": "string",
|
||||
"const": "default",
|
||||
"markdownDescription": "This permission set configures what kind of\noperations are available from the mcp plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n#### This default permission set includes:\n\n- `allow-load-candle-model-whisper`\n- `allow-ipc-audio-transcription`"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-7
@@ -8,15 +8,18 @@ use tauri::{
|
||||
plugin::{Builder as PluginBuilder, TauriPlugin},
|
||||
};
|
||||
|
||||
use crate::app::models::{new_whisper_processor, whisper::WhichWhisperModel};
|
||||
mod helpers;
|
||||
mod models;
|
||||
|
||||
#[derive(Default)]
|
||||
struct AppDataWhisperProcessor {
|
||||
whisper_processor: Option<crate::app::models::whisper::Processor>,
|
||||
whisper_processor: Option<models::whisper::Processor>,
|
||||
}
|
||||
|
||||
use crate::models::{new_whisper_processor, whisper::WhichWhisperModel};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn load_model_whisper<R: Runtime>(
|
||||
async fn load_candle_model_whisper<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
window: tauri::WebviewWindow<R>,
|
||||
model_type: Option<String>,
|
||||
@@ -60,7 +63,7 @@ pub async fn load_model_whisper<R: Runtime>(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn audio_transcription<R: Runtime>(
|
||||
async fn ipc_audio_transcription<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
chunk: Vec<f32>,
|
||||
language: Option<String>,
|
||||
@@ -91,15 +94,15 @@ pub async fn audio_transcription<R: Runtime>(
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
PluginBuilder::new("proj-airi-tauri-plugin-audio-transcription")
|
||||
PluginBuilder::new("ipc-audio-transcription-candle")
|
||||
.setup(|app, _| {
|
||||
info!("Initializing audio transcription plugin...");
|
||||
app.manage(Mutex::new(AppDataWhisperProcessor::default()));
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
load_model_whisper,
|
||||
audio_transcription,
|
||||
load_candle_model_whisper,
|
||||
ipc_audio_transcription,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
+1
-9
@@ -1,10 +1,9 @@
|
||||
pub mod silero_vad;
|
||||
pub mod whisper;
|
||||
|
||||
use log::info;
|
||||
use tauri::Runtime;
|
||||
|
||||
use crate::{app::models::whisper::WhichWhisperModel, helpers::huggingface::load_device};
|
||||
use crate::{helpers::huggingface::load_device, models::whisper::WhichWhisperModel};
|
||||
|
||||
pub fn new_whisper_processor<R: Runtime>(
|
||||
window: tauri::WebviewWindow<R>,
|
||||
@@ -23,10 +22,3 @@ pub fn new_whisper_processor<R: Runtime>(
|
||||
info!("Loading whisper model: {:?}", whisper_model);
|
||||
whisper::Processor::new(whisper_model, device.clone(), window)
|
||||
}
|
||||
|
||||
pub fn new_silero_vad_processor<R: Runtime>(
|
||||
window: tauri::WebviewWindow<R>
|
||||
) -> anyhow::Result<silero_vad::Processor> {
|
||||
info!("Loading VAD model");
|
||||
silero_vad::Processor::new(window)
|
||||
}
|
||||
+3
-3
@@ -160,7 +160,7 @@ impl Processor {
|
||||
config_filename_sub_name,
|
||||
create_progress_emitter(
|
||||
window.clone(),
|
||||
"tauri-tamagotchi://plugins:proj-airi:audio-transcription:load-model-whisper-progress",
|
||||
"tauri-plugins:tauri-plugin-ipc-audio-transcription-candle:load-model-whisper-progress",
|
||||
config_filename_sub_name.to_string(),
|
||||
),
|
||||
)?,
|
||||
@@ -174,7 +174,7 @@ impl Processor {
|
||||
tokenizer_filename_sub_name,
|
||||
create_progress_emitter(
|
||||
window.clone(),
|
||||
"tauri-tamagotchi://plugins:proj-airi:audio-transcription:load-model-whisper-progress",
|
||||
"tauri-plugins:tauri-plugin-ipc-audio-transcription-candle:load-model-whisper-progress",
|
||||
tokenizer_filename_sub_name.to_string(),
|
||||
),
|
||||
)?,
|
||||
@@ -188,7 +188,7 @@ impl Processor {
|
||||
model_filename_sub_name,
|
||||
create_progress_emitter(
|
||||
window.clone(),
|
||||
"tauri-tamagotchi://plugins:proj-airi:audio-transcription:load-model-whisper-progress",
|
||||
"tauri-plugins:tauri-plugin-ipc-audio-transcription-candle:load-model-whisper-progress",
|
||||
model_filename_sub_name.to_string(),
|
||||
),
|
||||
)?,
|
||||
+4732
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
[package]
|
||||
name = "tauri-plugin-ipc-audio-vad-ort"
|
||||
version.workspace = true
|
||||
description = "Audio VAD (Voice Activity Detection) plugin for Tauri using ONNX Runtime"
|
||||
authors = [ "Neko Ayaka <neko@ayaka.moe>" ]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/moeru-ai/airi"
|
||||
edition = "2024"
|
||||
rust-version = "1.85.0"
|
||||
links = "tauri-plugin-ipc-audio-vad-ort"
|
||||
|
||||
[lib]
|
||||
name = "tauri_plugin_ipc_audio_vad_ort"
|
||||
crate-type = [
|
||||
"staticlib",
|
||||
"cdylib",
|
||||
"rlib"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
tauri = "2.3.1"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
tokio = "1.45.1"
|
||||
ndarray = "0.16.1"
|
||||
log = "0.4"
|
||||
anyhow = "1"
|
||||
tokenizers = "0.21.2"
|
||||
specta = "=2.0.0-rc.22"
|
||||
specta-typescript = "0.0.9"
|
||||
tauri-specta = { version = "=2.0.0-rc.21", features = ["derive", "typescript"] }
|
||||
hf-hub = "0.4.3"
|
||||
symphonia = "0.5.4"
|
||||
rubato = "0.16.2"
|
||||
byteorder = "1.5.0"
|
||||
clap = { version = "4.5.40", features = ["derive"] }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { version = "2.2", features = [ "build" ] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
ort = { version = "2.0.0-rc.10", features = ["ndarray", "coreml"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
ort = { version = "2.0.0-rc.10", features = ["ndarray", "directml"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
ort = { version = "2.0.0-rc.10", features = ["ndarray"] }
|
||||
@@ -0,0 +1,5 @@
|
||||
const COMMANDS: &[&str] = &["load_ort_model_silero_vad", "ipc_audio_vad"];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS).build();
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-ipc-audio-vad"
|
||||
description = "Enables the ipc_audio_vad command without any pre-configured scope."
|
||||
commands.allow = ["ipc_audio_vad"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-ipc-audio-vad"
|
||||
description = "Denies the ipc_audio_vad command without any pre-configured scope."
|
||||
commands.deny = ["ipc_audio_vad"]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-load-ort-model-silero-vad"
|
||||
description = "Enables the load_ort_model_silero_vad command without any pre-configured scope."
|
||||
commands.allow = ["load_ort_model_silero_vad"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-load-ort-model-silero-vad"
|
||||
description = "Denies the load_ort_model_silero_vad command without any pre-configured scope."
|
||||
commands.deny = ["load_ort_model_silero_vad"]
|
||||
@@ -0,0 +1,75 @@
|
||||
## Default Permission
|
||||
|
||||
This permission set configures what kind of
|
||||
operations are available from the mcp plugin.
|
||||
|
||||
#### Granted Permissions
|
||||
|
||||
All operations are enabled by default.
|
||||
|
||||
#### This default permission set includes the following:
|
||||
|
||||
- `allow-load-ort-model-silero-vad`
|
||||
- `allow-ipc-audio-vad`
|
||||
|
||||
## Permission Table
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Identifier</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`ipc-audio-vad-ort:allow-ipc-audio-vad`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the ipc_audio_vad command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`ipc-audio-vad-ort:deny-ipc-audio-vad`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the ipc_audio_vad command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`ipc-audio-vad-ort:allow-load-ort-model-silero-vad`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the load_ort_model_silero_vad command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`ipc-audio-vad-ort:deny-load-ort-model-silero-vad`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the load_ort_model_silero_vad command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -0,0 +1,15 @@
|
||||
"$schema" = "schemas/schema.json"
|
||||
|
||||
[default]
|
||||
description = """
|
||||
This permission set configures what kind of
|
||||
operations are available from the mcp plugin.
|
||||
|
||||
#### Granted Permissions
|
||||
|
||||
All operations are enabled by default.
|
||||
"""
|
||||
permissions = [
|
||||
"allow-load-ort-model-silero-vad",
|
||||
"allow-ipc-audio-vad"
|
||||
]
|
||||
@@ -0,0 +1,330 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PermissionFile",
|
||||
"description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"default": {
|
||||
"description": "The default permission set for the plugin",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/DefaultPermission"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"set": {
|
||||
"description": "A list of permissions sets defined",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PermissionSet"
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"description": "A list of inlined permissions",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Permission"
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"DefaultPermission": {
|
||||
"description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"permissions"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"description": "The version of the permission.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint64",
|
||||
"minimum": 1.0
|
||||
},
|
||||
"description": {
|
||||
"description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"permissions": {
|
||||
"description": "All permissions this set contains.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"PermissionSet": {
|
||||
"description": "A set of direct permissions grouped together under a new name.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"description",
|
||||
"identifier",
|
||||
"permissions"
|
||||
],
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"description": "A unique identifier for the permission.",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Human-readable description of what the permission does.",
|
||||
"type": "string"
|
||||
},
|
||||
"permissions": {
|
||||
"description": "All permissions this set contains.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PermissionKind"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Permission": {
|
||||
"description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"identifier"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"description": "The version of the permission.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint64",
|
||||
"minimum": 1.0
|
||||
},
|
||||
"identifier": {
|
||||
"description": "A unique identifier for the permission.",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"commands": {
|
||||
"description": "Allowed or denied commands when using this permission.",
|
||||
"default": {
|
||||
"allow": [],
|
||||
"deny": []
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Commands"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scope": {
|
||||
"description": "Allowed or denied scoped when using this permission.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Scopes"
|
||||
}
|
||||
]
|
||||
},
|
||||
"platforms": {
|
||||
"description": "Target platforms this permission applies. By default all platforms are affected by this permission.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/Target"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Commands": {
|
||||
"description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow": {
|
||||
"description": "Allowed command.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"deny": {
|
||||
"description": "Denied command, which takes priority.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Scopes": {
|
||||
"description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow": {
|
||||
"description": "Data that defines what is allowed by the scope.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
},
|
||||
"deny": {
|
||||
"description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Value": {
|
||||
"description": "All supported ACL values.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Represents a null JSON value.",
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"description": "Represents a [`bool`].",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"description": "Represents a valid ACL [`Number`].",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Number"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Represents a [`String`].",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Represents a list of other [`Value`]s.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Represents a map of [`String`] keys to [`Value`]s.",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Number": {
|
||||
"description": "A valid ACL number.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Represents an [`i64`].",
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
{
|
||||
"description": "Represents a [`f64`].",
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Target": {
|
||||
"description": "Platform target.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "MacOS.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"macOS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Windows.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"windows"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Linux.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Android.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "iOS.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"iOS"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionKind": {
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Enables the ipc_audio_vad command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-ipc-audio-vad",
|
||||
"markdownDescription": "Enables the ipc_audio_vad command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the ipc_audio_vad command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-ipc-audio-vad",
|
||||
"markdownDescription": "Denies the ipc_audio_vad command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the load_ort_model_silero_vad command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-load-ort-model-silero-vad",
|
||||
"markdownDescription": "Enables the load_ort_model_silero_vad command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the load_ort_model_silero_vad command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-load-ort-model-silero-vad",
|
||||
"markdownDescription": "Denies the load_ort_model_silero_vad command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures what kind of\noperations are available from the mcp plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n#### This default permission set includes:\n\n- `allow-load-ort-model-silero-vad`\n- `allow-ipc-audio-vad`",
|
||||
"type": "string",
|
||||
"const": "default",
|
||||
"markdownDescription": "This permission set configures what kind of\noperations are available from the mcp plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n#### This default permission set includes:\n\n- `allow-load-ort-model-silero-vad`\n- `allow-ipc-audio-vad`"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
use log::error;
|
||||
use tauri::{Emitter, Runtime};
|
||||
|
||||
pub trait ProgressEmitter: Send + Sync {
|
||||
fn emit_progress(
|
||||
&self,
|
||||
event_name: String,
|
||||
filename: String,
|
||||
progress: f32,
|
||||
total_size: usize,
|
||||
current_size: usize,
|
||||
);
|
||||
|
||||
fn emit_done(
|
||||
&self,
|
||||
event_name: String,
|
||||
filename: String,
|
||||
);
|
||||
}
|
||||
|
||||
pub struct ModelLoadProgressEmitter {
|
||||
event_name: String,
|
||||
filename: String,
|
||||
size: usize,
|
||||
total_size: usize,
|
||||
progress: f32,
|
||||
emitter: Box<dyn ProgressEmitter>,
|
||||
}
|
||||
|
||||
impl ModelLoadProgressEmitter {
|
||||
pub fn new(
|
||||
emitter: Box<dyn ProgressEmitter>,
|
||||
event_name: &str,
|
||||
filename: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
event_name: event_name.to_string(),
|
||||
filename,
|
||||
size: 0,
|
||||
total_size: 0,
|
||||
progress: 0.0,
|
||||
emitter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> ProgressEmitter for tauri::WebviewWindow<R> {
|
||||
fn emit_progress(
|
||||
&self,
|
||||
event_name: String,
|
||||
filename: String,
|
||||
progress: f32,
|
||||
total_size: usize,
|
||||
current_size: usize,
|
||||
) {
|
||||
if let Err(err) = self.emit(
|
||||
event_name.as_str(),
|
||||
(false, filename, progress, total_size, current_size),
|
||||
) {
|
||||
error!("Failed to emit model-load-progress: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_done(
|
||||
&self,
|
||||
event_name: String,
|
||||
filename: String,
|
||||
) {
|
||||
if let Err(err) = self.emit(event_name.as_str(), (true, filename, 100.0)) {
|
||||
error!("Failed to emit model-load-done: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_progress_emitter(
|
||||
window: impl ProgressEmitter + 'static,
|
||||
event_name: &str,
|
||||
filename: String,
|
||||
) -> ModelLoadProgressEmitter {
|
||||
ModelLoadProgressEmitter::new(Box::new(window), event_name, filename)
|
||||
}
|
||||
|
||||
// Remove the generic <R: Runtime> since ModelLoadProgressEmitter no longer has generics
|
||||
impl hf_hub::api::Progress for ModelLoadProgressEmitter {
|
||||
fn init(
|
||||
&mut self,
|
||||
size: usize,
|
||||
_: &str,
|
||||
) {
|
||||
self.total_size = size;
|
||||
self.progress = 0.0;
|
||||
self.emitter.emit_progress(
|
||||
self.event_name.clone(),
|
||||
self.filename.clone(),
|
||||
self.progress,
|
||||
self.total_size,
|
||||
self.size,
|
||||
);
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
size: usize,
|
||||
) {
|
||||
self.size += size;
|
||||
self.progress = if self.total_size > 0 {
|
||||
(self.size as f32 / self.total_size as f32 * 100.0).min(100.0)
|
||||
} else {
|
||||
100.0
|
||||
};
|
||||
self.emitter.emit_progress(
|
||||
self.event_name.clone(),
|
||||
self.filename.clone(),
|
||||
self.progress,
|
||||
self.total_size,
|
||||
self.size,
|
||||
);
|
||||
}
|
||||
|
||||
fn finish(&mut self) {
|
||||
self.progress = 100.0;
|
||||
self.emitter.emit_progress(
|
||||
self.event_name.clone(),
|
||||
self.filename.clone(),
|
||||
self.progress,
|
||||
self.total_size,
|
||||
self.size,
|
||||
);
|
||||
self
|
||||
.emitter
|
||||
.emit_done(self.event_name.clone(), self.filename.clone());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod huggingface;
|
||||
+12
-6
@@ -7,18 +7,21 @@ use tauri::{
|
||||
plugin::{Builder as PluginBuilder, TauriPlugin},
|
||||
};
|
||||
|
||||
use crate::app::models::{
|
||||
mod helpers;
|
||||
mod models;
|
||||
|
||||
use crate::models::{
|
||||
new_silero_vad_processor,
|
||||
silero_vad::{VADInferenceInput, VADInferenceResult},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
struct AppDataSileroVadProcessor {
|
||||
silero_vad_processor: Option<crate::app::models::silero_vad::Processor>,
|
||||
silero_vad_processor: Option<crate::models::silero_vad::Processor>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn load_model_silero_vad<R: Runtime>(
|
||||
async fn load_ort_model_silero_vad<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
window: tauri::WebviewWindow<R>,
|
||||
) -> Result<(), String> {
|
||||
@@ -52,7 +55,7 @@ pub async fn load_model_silero_vad<R: Runtime>(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn audio_vad<R: Runtime>(
|
||||
async fn ipc_audio_vad<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
input_data: VADInferenceInput,
|
||||
) -> Result<VADInferenceResult, String> {
|
||||
@@ -69,12 +72,15 @@ pub async fn audio_vad<R: Runtime>(
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
PluginBuilder::new("proj-airi-tauri-plugin-audio-vad")
|
||||
PluginBuilder::new("ipc-audio-vad-ort")
|
||||
.setup(|app, _| {
|
||||
info!("Initializing audio VAD plugin...");
|
||||
app.manage(Mutex::new(AppDataSileroVadProcessor::default()));
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![load_model_silero_vad, audio_vad])
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
load_ort_model_silero_vad,
|
||||
ipc_audio_vad
|
||||
])
|
||||
.build()
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
pub mod silero_vad;
|
||||
use log::info;
|
||||
use tauri::Runtime;
|
||||
|
||||
pub fn new_silero_vad_processor<R: Runtime>(
|
||||
window: tauri::WebviewWindow<R>
|
||||
) -> anyhow::Result<silero_vad::Processor> {
|
||||
info!("Loading VAD model");
|
||||
silero_vad::Processor::new(window)
|
||||
}
|
||||
+1
-1
@@ -62,7 +62,7 @@ impl Processor {
|
||||
model_path_sub_name,
|
||||
create_progress_emitter(
|
||||
window.clone(),
|
||||
"tauri-tamagotchi://plugins:proj-airi:audio-vad:load-model-silero-vad-progress",
|
||||
"tauri-plugins:tauri-plugin-ipc-audio-vad-ort:load-model-silero-vad-progress",
|
||||
model_path_sub_name.to_string(),
|
||||
),
|
||||
)?,
|
||||
+4732
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
[package]
|
||||
name = "tauri-plugin-window-pass-through-on-hover"
|
||||
version.workspace = true
|
||||
description = "Fade on Hover (FoH), or pass through on hover of window, plugin for Tauri"
|
||||
authors = [ "Neko Ayaka <neko@ayaka.moe>" ]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/moeru-ai/airi"
|
||||
edition = "2024"
|
||||
rust-version = "1.85.0"
|
||||
links = "tauri-plugin-window-pass-through-on-hover"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "tauri_plugin_window_pass_through_on_hover"
|
||||
crate-type = [
|
||||
"staticlib",
|
||||
"cdylib",
|
||||
"rlib"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
tauri = "2.3.1"
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
log = "0.4"
|
||||
tokio = { version = "1", features = [ "full" ] }
|
||||
anyhow = "1"
|
||||
specta = "=2.0.0-rc.22"
|
||||
specta-typescript = "0.0.9"
|
||||
tauri-specta = { version = "=2.0.0-rc.21", features = ["derive", "typescript"] }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { version = "2.2", features = [ "build" ] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2 = "0.6.1"
|
||||
objc2-foundation = "0.3.1"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows = { version = "0.61.1", features = ["Win32_UI_WindowsAndMessaging", "Win32_Foundation"] }
|
||||
@@ -0,0 +1,10 @@
|
||||
const COMMANDS: &[&str] = &[
|
||||
"start_tracing_cursor",
|
||||
"stop_tracing_cursor",
|
||||
"start_pass_through",
|
||||
"stop_pass_through",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS).build();
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-start-pass-through"
|
||||
description = "Enables the start_pass_through command without any pre-configured scope."
|
||||
commands.allow = ["start_pass_through"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-start-pass-through"
|
||||
description = "Denies the start_pass_through command without any pre-configured scope."
|
||||
commands.deny = ["start_pass_through"]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-start-tracing-cursor"
|
||||
description = "Enables the start_tracing_cursor command without any pre-configured scope."
|
||||
commands.allow = ["start_tracing_cursor"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-start-tracing-cursor"
|
||||
description = "Denies the start_tracing_cursor command without any pre-configured scope."
|
||||
commands.deny = ["start_tracing_cursor"]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-stop-pass-through"
|
||||
description = "Enables the stop_pass_through command without any pre-configured scope."
|
||||
commands.allow = ["stop_pass_through"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-stop-pass-through"
|
||||
description = "Denies the stop_pass_through command without any pre-configured scope."
|
||||
commands.deny = ["stop_pass_through"]
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-stop-tracing-cursor"
|
||||
description = "Enables the stop_tracing_cursor command without any pre-configured scope."
|
||||
commands.allow = ["stop_tracing_cursor"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-stop-tracing-cursor"
|
||||
description = "Denies the stop_tracing_cursor command without any pre-configured scope."
|
||||
commands.deny = ["stop_tracing_cursor"]
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
## Default Permission
|
||||
|
||||
This permission set configures what kind of
|
||||
operations are available from the mcp plugin.
|
||||
|
||||
#### Granted Permissions
|
||||
|
||||
All operations are enabled by default.
|
||||
|
||||
#### This default permission set includes the following:
|
||||
|
||||
- `allow-start-tracing-cursor`
|
||||
- `allow-stop-tracing-cursor`
|
||||
- `allow-start-pass-through`
|
||||
- `allow-stop-pass-through`
|
||||
|
||||
## Permission Table
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Identifier</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`window-pass-through-on-hover:allow-start-pass-through`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the start_pass_through command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`window-pass-through-on-hover:deny-start-pass-through`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the start_pass_through command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`window-pass-through-on-hover:allow-start-tracing-cursor`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the start_tracing_cursor command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`window-pass-through-on-hover:deny-start-tracing-cursor`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the start_tracing_cursor command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`window-pass-through-on-hover:allow-stop-pass-through`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the stop_pass_through command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`window-pass-through-on-hover:deny-stop-pass-through`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the stop_pass_through command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`window-pass-through-on-hover:allow-stop-tracing-cursor`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the stop_tracing_cursor command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`window-pass-through-on-hover:deny-stop-tracing-cursor`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the stop_tracing_cursor command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -0,0 +1,17 @@
|
||||
"$schema" = "schemas/schema.json"
|
||||
|
||||
[default]
|
||||
description = """
|
||||
This permission set configures what kind of
|
||||
operations are available from the mcp plugin.
|
||||
|
||||
#### Granted Permissions
|
||||
|
||||
All operations are enabled by default.
|
||||
"""
|
||||
permissions = [
|
||||
"allow-start-tracing-cursor",
|
||||
"allow-stop-tracing-cursor",
|
||||
"allow-start-pass-through",
|
||||
"allow-stop-pass-through"
|
||||
]
|
||||
@@ -0,0 +1,354 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PermissionFile",
|
||||
"description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"default": {
|
||||
"description": "The default permission set for the plugin",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/DefaultPermission"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"set": {
|
||||
"description": "A list of permissions sets defined",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PermissionSet"
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"description": "A list of inlined permissions",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Permission"
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"DefaultPermission": {
|
||||
"description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"permissions"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"description": "The version of the permission.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint64",
|
||||
"minimum": 1.0
|
||||
},
|
||||
"description": {
|
||||
"description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"permissions": {
|
||||
"description": "All permissions this set contains.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"PermissionSet": {
|
||||
"description": "A set of direct permissions grouped together under a new name.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"description",
|
||||
"identifier",
|
||||
"permissions"
|
||||
],
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"description": "A unique identifier for the permission.",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Human-readable description of what the permission does.",
|
||||
"type": "string"
|
||||
},
|
||||
"permissions": {
|
||||
"description": "All permissions this set contains.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PermissionKind"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Permission": {
|
||||
"description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"identifier"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"description": "The version of the permission.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint64",
|
||||
"minimum": 1.0
|
||||
},
|
||||
"identifier": {
|
||||
"description": "A unique identifier for the permission.",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"commands": {
|
||||
"description": "Allowed or denied commands when using this permission.",
|
||||
"default": {
|
||||
"allow": [],
|
||||
"deny": []
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Commands"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scope": {
|
||||
"description": "Allowed or denied scoped when using this permission.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Scopes"
|
||||
}
|
||||
]
|
||||
},
|
||||
"platforms": {
|
||||
"description": "Target platforms this permission applies. By default all platforms are affected by this permission.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/Target"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Commands": {
|
||||
"description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow": {
|
||||
"description": "Allowed command.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"deny": {
|
||||
"description": "Denied command, which takes priority.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Scopes": {
|
||||
"description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow": {
|
||||
"description": "Data that defines what is allowed by the scope.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
},
|
||||
"deny": {
|
||||
"description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Value": {
|
||||
"description": "All supported ACL values.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Represents a null JSON value.",
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"description": "Represents a [`bool`].",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"description": "Represents a valid ACL [`Number`].",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Number"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Represents a [`String`].",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Represents a list of other [`Value`]s.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Represents a map of [`String`] keys to [`Value`]s.",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Number": {
|
||||
"description": "A valid ACL number.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Represents an [`i64`].",
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
{
|
||||
"description": "Represents a [`f64`].",
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Target": {
|
||||
"description": "Platform target.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "MacOS.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"macOS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Windows.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"windows"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Linux.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Android.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "iOS.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"iOS"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionKind": {
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Enables the start_pass_through command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-start-pass-through",
|
||||
"markdownDescription": "Enables the start_pass_through command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the start_pass_through command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-start-pass-through",
|
||||
"markdownDescription": "Denies the start_pass_through command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the start_tracing_cursor command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-start-tracing-cursor",
|
||||
"markdownDescription": "Enables the start_tracing_cursor command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the start_tracing_cursor command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-start-tracing-cursor",
|
||||
"markdownDescription": "Denies the start_tracing_cursor command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the stop_pass_through command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-stop-pass-through",
|
||||
"markdownDescription": "Enables the stop_pass_through command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the stop_pass_through command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-stop-pass-through",
|
||||
"markdownDescription": "Denies the stop_pass_through command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the stop_tracing_cursor command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-stop-tracing-cursor",
|
||||
"markdownDescription": "Enables the stop_tracing_cursor command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the stop_tracing_cursor command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-stop-tracing-cursor",
|
||||
"markdownDescription": "Denies the stop_tracing_cursor command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures what kind of\noperations are available from the mcp plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n#### This default permission set includes:\n\n- `allow-start-tracing-cursor`\n- `allow-stop-tracing-cursor`\n- `allow-start-pass-through`\n- `allow-stop-pass-through`",
|
||||
"type": "string",
|
||||
"const": "default",
|
||||
"markdownDescription": "This permission set configures what kind of\noperations are available from the mcp plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n#### This default permission set includes:\n\n- `allow-start-tracing-cursor`\n- `allow-stop-tracing-cursor`\n- `allow-start-pass-through`\n- `allow-stop-pass-through`"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-11
@@ -15,14 +15,16 @@ use state::{WindowClickThroughState, set_pass_through_enabled};
|
||||
use tauri::{
|
||||
Emitter,
|
||||
Manager,
|
||||
Result,
|
||||
Runtime,
|
||||
plugin::{Builder as PluginBuilder, TauriPlugin},
|
||||
};
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[tauri::command]
|
||||
async fn start_monitor<R: Runtime>(window: tauri::Window<R>) -> Result<()> {
|
||||
#[specta::specta]
|
||||
async fn start_tracing_cursor<R: Runtime>(
|
||||
window: tauri::Window<R>
|
||||
) -> std::result::Result<(), String> {
|
||||
let window = window;
|
||||
let state = window.state::<WindowClickThroughState>();
|
||||
let monitoring_enabled = state.monitoring_enabled.clone();
|
||||
@@ -50,11 +52,11 @@ async fn start_monitor<R: Runtime>(window: tauri::Window<R>) -> Result<()> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let _ = window.emit(
|
||||
"tauri-app:proj-airi:window-pass-through-on-hover:cursor-position",
|
||||
"tauri-plugins:tauri-plugin-window-pass-through-on-hover:cursor-position",
|
||||
get_mouse_location(),
|
||||
);
|
||||
let _ = window.emit(
|
||||
"tauri-app:proj-airi:window-pass-through-on-hover:window-frame",
|
||||
"tauri-plugins:tauri-plugin-window-pass-through-on-hover:window-frame",
|
||||
get_window_frame(&window),
|
||||
);
|
||||
}
|
||||
@@ -62,11 +64,11 @@ async fn start_monitor<R: Runtime>(window: tauri::Window<R>) -> Result<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let _ = window.emit(
|
||||
"tauri-app:proj-airi:window-pass-through-on-hover:cursor-position",
|
||||
"tauri-plugins:tauri-plugin-window-pass-through-on-hover:cursor-position",
|
||||
get_mouse_location(),
|
||||
);
|
||||
let _ = window.emit(
|
||||
"tauri-app:proj-airi:window-pass-through-on-hover:window-frame",
|
||||
"tauri-plugins:tauri-plugin-window-pass-through-on-hover:window-frame",
|
||||
get_window_frame(&window),
|
||||
);
|
||||
}
|
||||
@@ -77,7 +79,10 @@ async fn start_monitor<R: Runtime>(window: tauri::Window<R>) -> Result<()> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn stop_monitor<R: Runtime>(window: tauri::Window<R>) -> Result<()> {
|
||||
#[specta::specta]
|
||||
async fn stop_tracing_cursor<R: Runtime>(
|
||||
window: tauri::Window<R>
|
||||
) -> std::result::Result<(), String> {
|
||||
let window = window;
|
||||
let state = window.state::<WindowClickThroughState>();
|
||||
|
||||
@@ -112,7 +117,7 @@ async fn stop_pass_through<R: Runtime>(
|
||||
})
|
||||
}
|
||||
|
||||
const PLUGIN_NAME: &str = "proj-airi-tauri-plugin-window-pass-through-on-hover";
|
||||
const PLUGIN_NAME: &str = "window-pass-through-on-hover";
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
let builder = tauri_specta::Builder::<R>::new()
|
||||
@@ -122,8 +127,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
// ↓ as the original macro does not accept a mix of commands with and without `#[specta::specta]`
|
||||
.commands(tauri_specta::internal::command(
|
||||
tauri::generate_handler![
|
||||
start_monitor,
|
||||
stop_monitor,
|
||||
start_tracing_cursor,
|
||||
stop_tracing_cursor,
|
||||
start_pass_through,
|
||||
stop_pass_through
|
||||
],
|
||||
@@ -141,7 +146,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
builder
|
||||
.export(
|
||||
Typescript::default().header("// @ts-nocheck\n"),
|
||||
"../src/commands/bindings/window-pass-through-on-hover.ts",
|
||||
"../src/bindings/tauri-plugins/window-pass-through-on-hover.ts",
|
||||
)
|
||||
.expect("Failed to export typescript bindings");
|
||||
|
||||
+4732
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "tauri-plugin-window-router-link"
|
||||
version.workspace = true
|
||||
description = "<RouterLink> like primitive support for opening links in desired window in Tauri"
|
||||
authors = [ "LemonNekoGH <self@lemonneko.moe>" ]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/moeru-ai/airi"
|
||||
edition = "2024"
|
||||
rust-version = "1.85.0"
|
||||
links = "tauri-plugin-window-router-link"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "tauri_plugin_window_router_link"
|
||||
crate-type = [
|
||||
"staticlib",
|
||||
"cdylib",
|
||||
"rlib"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
tauri = "2.3.1"
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
log = "0.4"
|
||||
tokio = { version = "1", features = [ "full" ] }
|
||||
anyhow = "1"
|
||||
specta = "=2.0.0-rc.22"
|
||||
specta-typescript = "0.0.9"
|
||||
tauri-specta = { version = "=2.0.0-rc.21", features = ["derive", "typescript"] }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { version = "2.2", features = [ "build" ] }
|
||||
@@ -0,0 +1,104 @@
|
||||
# `tauri-plugin-mcp`
|
||||
|
||||

|
||||
|
||||
A Tauri plugin for interacting with MCP servers.
|
||||
|
||||
## Install
|
||||
|
||||
This plugin requires a Rust version of at least 1.77.2
|
||||
|
||||
`src-tauri/Cargo.toml`
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tauri-plugin-mcp = { version = "0.4.27" }
|
||||
# or
|
||||
tauri-plugin-mcp = { git = "https://github.com/moeru-ai/airi", tag = "0.4.27" }
|
||||
```
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
```bash
|
||||
pnpm add @proj-airi/tauri-plugin-mcp
|
||||
# or use @antfu/ni
|
||||
ni @proj-airi/tauri-plugin-mcp
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
First, you need to register the plugin with Tauri:
|
||||
|
||||
`src-tauri/src/lib.rs`
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_mcp::Builder.build())
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
```
|
||||
|
||||
Afterwards all the plugin's APIs are available through the JavaScript guest bindings:
|
||||
|
||||
```ts
|
||||
import { callTool, connectServer, disconnectServer, listTools } from '@proj-airi/tauri-plugin-mcp'
|
||||
|
||||
// Sample: Connect to a container running AIRI Android
|
||||
await connectServer('docker', 'run -i --rm -e ADB_HOST=host.docker.internal ghcr.io/lemonnekogh/airi-android:v0.1.0')
|
||||
|
||||
console.log(await listTools())
|
||||
// [
|
||||
// {
|
||||
// description: 'Get the battery level of the device',
|
||||
// inputSchema: {
|
||||
// required: [],
|
||||
// title: 'battery_level',
|
||||
// type: 'object',
|
||||
// },
|
||||
// name: 'battery_level',
|
||||
// },
|
||||
// ]
|
||||
|
||||
console.log(await callTool('battery_level', {}))
|
||||
// {
|
||||
// content: [
|
||||
// {
|
||||
// type: 'text',
|
||||
// text: '100',
|
||||
// },
|
||||
// ],
|
||||
// isError: false,
|
||||
// }
|
||||
|
||||
await disconnectServer()
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Source code of JavaScript bindings is located in [packages/tauri-plugin-mcp](../../packages/tauri-plugin-mcp/src/index.ts).
|
||||
|
||||
These files are generated from `tauri-plugin` crate:
|
||||
|
||||
- `permissions/autogenerated`
|
||||
- `permissions/schemas`
|
||||
|
||||
## TODO List
|
||||
|
||||
- [x] Move to a single repository
|
||||
- [ ] Server connection
|
||||
- [x] stdio
|
||||
- [ ] SSE
|
||||
- [x] Tools
|
||||
- [x] List tools
|
||||
- [x] Simple: Names only
|
||||
- [ ] With description, parameters, returns
|
||||
- [x] Call tool
|
||||
- [x] Simple: Name only
|
||||
- [x] With parameters
|
||||
- [x] Returns
|
||||
- [ ] Image returns
|
||||
- [ ] Prompts...
|
||||
- [ ] Resources...
|
||||
- [x] JavaScript package to call commands conveniently
|
||||
@@ -0,0 +1,5 @@
|
||||
const COMMANDS: &[&str] = &["go"];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS).build();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-go"
|
||||
description = "Enables the go command without any pre-configured scope."
|
||||
commands.allow = ["go"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-go"
|
||||
description = "Denies the go command without any pre-configured scope."
|
||||
commands.deny = ["go"]
|
||||
@@ -0,0 +1,48 @@
|
||||
## Default Permission
|
||||
|
||||
This permission set configures what kind of
|
||||
operations are available from the mcp plugin.
|
||||
|
||||
#### Granted Permissions
|
||||
|
||||
All operations are enabled by default.
|
||||
|
||||
#### This default permission set includes the following:
|
||||
|
||||
- `allow-go`
|
||||
|
||||
## Permission Table
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Identifier</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`window-router-link:allow-go`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the go command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`window-router-link:deny-go`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the go command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -0,0 +1,14 @@
|
||||
"$schema" = "schemas/schema.json"
|
||||
|
||||
[default]
|
||||
description = """
|
||||
This permission set configures what kind of
|
||||
operations are available from the mcp plugin.
|
||||
|
||||
#### Granted Permissions
|
||||
|
||||
All operations are enabled by default.
|
||||
"""
|
||||
permissions = [
|
||||
"allow-go"
|
||||
]
|
||||
@@ -0,0 +1,318 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PermissionFile",
|
||||
"description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"default": {
|
||||
"description": "The default permission set for the plugin",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/DefaultPermission"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"set": {
|
||||
"description": "A list of permissions sets defined",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PermissionSet"
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"description": "A list of inlined permissions",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Permission"
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"DefaultPermission": {
|
||||
"description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"permissions"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"description": "The version of the permission.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint64",
|
||||
"minimum": 1.0
|
||||
},
|
||||
"description": {
|
||||
"description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"permissions": {
|
||||
"description": "All permissions this set contains.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"PermissionSet": {
|
||||
"description": "A set of direct permissions grouped together under a new name.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"description",
|
||||
"identifier",
|
||||
"permissions"
|
||||
],
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"description": "A unique identifier for the permission.",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Human-readable description of what the permission does.",
|
||||
"type": "string"
|
||||
},
|
||||
"permissions": {
|
||||
"description": "All permissions this set contains.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/PermissionKind"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Permission": {
|
||||
"description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"identifier"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"description": "The version of the permission.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint64",
|
||||
"minimum": 1.0
|
||||
},
|
||||
"identifier": {
|
||||
"description": "A unique identifier for the permission.",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"commands": {
|
||||
"description": "Allowed or denied commands when using this permission.",
|
||||
"default": {
|
||||
"allow": [],
|
||||
"deny": []
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Commands"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scope": {
|
||||
"description": "Allowed or denied scoped when using this permission.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Scopes"
|
||||
}
|
||||
]
|
||||
},
|
||||
"platforms": {
|
||||
"description": "Target platforms this permission applies. By default all platforms are affected by this permission.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/Target"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Commands": {
|
||||
"description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow": {
|
||||
"description": "Allowed command.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"deny": {
|
||||
"description": "Denied command, which takes priority.",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Scopes": {
|
||||
"description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow": {
|
||||
"description": "Data that defines what is allowed by the scope.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
},
|
||||
"deny": {
|
||||
"description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Value": {
|
||||
"description": "All supported ACL values.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Represents a null JSON value.",
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"description": "Represents a [`bool`].",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"description": "Represents a valid ACL [`Number`].",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Number"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Represents a [`String`].",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Represents a list of other [`Value`]s.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Represents a map of [`String`] keys to [`Value`]s.",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/Value"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Number": {
|
||||
"description": "A valid ACL number.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Represents an [`i64`].",
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
{
|
||||
"description": "Represents a [`f64`].",
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Target": {
|
||||
"description": "Platform target.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "MacOS.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"macOS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Windows.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"windows"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Linux.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Android.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "iOS.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"iOS"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionKind": {
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Enables the go command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-go",
|
||||
"markdownDescription": "Enables the go command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the go command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-go",
|
||||
"markdownDescription": "Denies the go command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures what kind of\noperations are available from the mcp plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n#### This default permission set includes:\n\n- `allow-go`",
|
||||
"type": "string",
|
||||
"const": "default",
|
||||
"markdownDescription": "This permission set configures what kind of\noperations are available from the mcp plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n#### This default permission set includes:\n\n- `allow-go`"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
#[cfg(debug_assertions)]
|
||||
use specta_typescript::Typescript;
|
||||
use tauri::{
|
||||
Manager,
|
||||
Runtime,
|
||||
plugin::{Builder, TauriPlugin},
|
||||
};
|
||||
|
||||
pub type WindowCreator<R> =
|
||||
Box<dyn Fn(tauri::AppHandle<R>) -> Result<tauri::WebviewWindow<R>> + Send + Sync>;
|
||||
|
||||
// Define a matcher struct that holds window creation logic
|
||||
pub struct WindowMatcher<R: Runtime> {
|
||||
creators: HashMap<String, WindowCreator<R>>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> WindowMatcher<R> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
creators: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register<F>(
|
||||
mut self,
|
||||
label: &str,
|
||||
creator: F,
|
||||
) -> Self
|
||||
where
|
||||
F: Fn(tauri::AppHandle<R>) -> Result<tauri::WebviewWindow<R>> + Send + Sync + 'static,
|
||||
{
|
||||
self
|
||||
.creators
|
||||
.insert(label.to_string(), Box::new(creator));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_or_create_window(
|
||||
&self,
|
||||
app: tauri::AppHandle<R>,
|
||||
label: &str,
|
||||
) -> Result<tauri::WebviewWindow<R>, String> {
|
||||
// First try to get existing window
|
||||
if let Some(window) = app.get_webview_window(label) {
|
||||
return Ok(window);
|
||||
}
|
||||
|
||||
// If no existing window, try to create one using registered creator
|
||||
match self.creators.get(label) {
|
||||
Some(creator) => {
|
||||
creator(app).map_err(|e| format!("Failed to create {} window: {}", label, e))
|
||||
},
|
||||
None => Err(format!("Unknown window label: {}", label)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn go<R: Runtime>(
|
||||
app_handle: tauri::AppHandle<R>,
|
||||
route: String,
|
||||
// ↓ #[specta(optional)] is not available in parameters.
|
||||
// ↓ This will be `string | null` in the generated TypeScript code
|
||||
window_label: Option<String>,
|
||||
) -> std::result::Result<(), String> {
|
||||
let window_label = window_label.ok_or("Missing window label")?;
|
||||
let matcher = app_handle.state::<WindowMatcher<R>>();
|
||||
let target_window = matcher.get_or_create_window(app_handle.clone(), &window_label)?;
|
||||
|
||||
let mut current_url = target_window
|
||||
.url()
|
||||
.map_err(|e| format!("Failed to get current URL: {}", e))?;
|
||||
let route: String = "/".to_string() + route.trim_start_matches('/');
|
||||
current_url.set_fragment(Some(route.to_string().as_str()));
|
||||
|
||||
let _ = target_window.show();
|
||||
if let Ok(url) = target_window.url() {
|
||||
if url == current_url {
|
||||
// If the URL is already the same, we don't need to navigate again.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _ = target_window.navigate(current_url);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const PLUGIN_NAME: &str = "window-router-link";
|
||||
|
||||
pub fn init<R: Runtime>(matcher: WindowMatcher<R>) -> TauriPlugin<R> {
|
||||
let builder = tauri_specta::Builder::<R>::new()
|
||||
.plugin_name(PLUGIN_NAME)
|
||||
.commands(tauri_specta::collect_commands![go::<tauri::Wry>]);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
builder
|
||||
.export(
|
||||
Typescript::default().header("// @ts-nocheck\n"),
|
||||
"../src/bindings/tauri-plugins/window-router-link.ts",
|
||||
)
|
||||
.expect("Failed to export typescript bindings");
|
||||
|
||||
Builder::new(PLUGIN_NAME)
|
||||
.invoke_handler(builder.invoke_handler())
|
||||
.setup(move |app, _| {
|
||||
app.manage(matcher);
|
||||
Ok(())
|
||||
})
|
||||
.build()
|
||||
}
|
||||
+1
-1
@@ -8,7 +8,7 @@ export default await antfu(
|
||||
'**/assets/js/**',
|
||||
'**/assets/live2d/models/**',
|
||||
'apps/stage-tamagotchi/out/**',
|
||||
'apps/stage-tamagotchi/src/commands/bindings/**',
|
||||
'apps/stage-tamagotchi/src/bindings/**',
|
||||
'apps/stage-tamagotchi/src-tauri/**',
|
||||
'crates/**',
|
||||
'**/drizzle/**',
|
||||
|
||||
Reference in New Issue
Block a user