chore(tauri-*,crates): now maintaining tauri and rust packages in another repo (#1533)
This commit is contained in:
@@ -1,42 +0,0 @@
|
||||
name: Release Crates
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
env:
|
||||
STORE_PATH: ''
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y libwebkit2gtk-4.1-dev
|
||||
|
||||
- name: Login to crates.io
|
||||
run: |
|
||||
echo ${{ secrets.CRATES_IO_API_TOKEN }} | cargo login
|
||||
|
||||
# TODO: cargo publish --workspace is still experimental
|
||||
- name: Crates publish
|
||||
run: |
|
||||
cargo publish -p tauri-plugin-mcp
|
||||
Vendored
-1
@@ -9,7 +9,6 @@
|
||||
"lokalise.i18n-ally",
|
||||
"mikestead.dotenv",
|
||||
"redhat.vscode-yaml",
|
||||
"rust-lang.rust-analyzer",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"usernamehw.errorlens",
|
||||
"Vue.volar",
|
||||
|
||||
Vendored
-12
@@ -9,11 +9,6 @@
|
||||
"vue"
|
||||
],
|
||||
|
||||
"rust-analyzer.cargo.extraEnv": {
|
||||
"MACOSX_DEPLOYMENT_TARGET": "10.13"
|
||||
},
|
||||
"rust-analyzer.cargo.targetDir": "target/rust-analyzer",
|
||||
|
||||
// Disable the default formatter
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
@@ -62,12 +57,5 @@
|
||||
"appimage",
|
||||
"localforage"
|
||||
],
|
||||
"[rust]": {
|
||||
"editor.defaultFormatter": "rust-lang.rust-analyzer",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"rust-analyzer.checkOnSave": true,
|
||||
"rust-analyzer.cachePriming.enable": false, // Disable cache priming on workspace startup to save some memory while working on non-Rust tasks
|
||||
|
||||
"vitest.disableWorkspaceWarning": true
|
||||
}
|
||||
|
||||
Generated
-6922
File diff suppressed because it is too large
Load Diff
-40
@@ -1,40 +0,0 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/tauri-plugin-ipc-audio-transcription-ort",
|
||||
"crates/tauri-plugin-ipc-audio-vad-ort",
|
||||
"crates/tauri-plugin-mcp",
|
||||
"crates/tauri-plugin-rdev",
|
||||
"crates/tauri-plugin-window-pass-through-on-hover",
|
||||
"crates/tauri-plugin-window-router-link"
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.9.0-beta.2"
|
||||
|
||||
[workspace.dependencies.tauri-plugin-ipc-audio-transcription-ort]
|
||||
path = "./crates/tauri-plugin-ipc-audio-transcription-ort"
|
||||
|
||||
[workspace.dependencies.tauri-plugin-ipc-audio-vad-ort]
|
||||
path = "./crates/tauri-plugin-ipc-audio-vad-ort"
|
||||
|
||||
[workspace.dependencies.tauri-plugin-mcp]
|
||||
path = "./crates/tauri-plugin-mcp"
|
||||
|
||||
[workspace.dependencies.tauri-plugin-rdev]
|
||||
path = "./crates/tauri-plugin-rdev"
|
||||
|
||||
[workspace.dependencies.tauri-plugin-window-pass-through-on-hover]
|
||||
path = "./crates/tauri-plugin-window-pass-through-on-hover"
|
||||
|
||||
[workspace.dependencies.tauri-plugin-window-router-link]
|
||||
path = "./crates/tauri-plugin-window-router-link"
|
||||
|
||||
[profile.dev]
|
||||
incremental = true
|
||||
opt-level = 0
|
||||
debug = true
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 1
|
||||
debug = false
|
||||
@@ -319,7 +319,7 @@ pnpm dev:docs
|
||||
|
||||
### Publish
|
||||
|
||||
Please update the version in `Cargo.toml` after running `bumpp`:
|
||||
Run `bumpp` to update the monorepo version:
|
||||
|
||||
```shell
|
||||
npx bumpp --no-commit --no-tag
|
||||
@@ -368,7 +368,6 @@ npx bumpp --no-commit --no-tag
|
||||
- [WebAI: Realtime Voice Chat](https://github.com/proj-airi/webai-realtime-voice-chat): Full example of implementing ChatGPT's realtime voice from scratch with VAD + STT + LLM + TTS.
|
||||
- [`@proj-airi/drizzle-duckdb-wasm`](https://github.com/moeru-ai/airi/tree/main/packages/drizzle-duckdb-wasm/README.md): Drizzle ORM driver for DuckDB WASM
|
||||
- [`@proj-airi/duckdb-wasm`](https://github.com/moeru-ai/airi/tree/main/packages/duckdb-wasm/README.md): Easy to use wrapper for `@duckdb/duckdb-wasm`
|
||||
- [`tauri-plugin-mcp`](https://github.com/moeru-ai/airi/blob/main/crates/tauri-plugin-mcp/README.md): A Tauri plugin for interacting with MCP servers.
|
||||
- [AIRI Factorio](https://github.com/moeru-ai/airi-factorio): Allow AIRI to play Factorio.
|
||||
- [AIRI DomeKeeper](https://github.com/proj-airi/game-playing-ai-dome-keeper): Allow AIRI to play DomeKeeper.
|
||||
- [Factorio RCON API](https://github.com/nekomeowww/factorio-rcon-api): RESTful API wrapper for Factorio headless server console
|
||||
@@ -450,7 +449,6 @@ flowchart TD
|
||||
XSAI --> F_AGENT
|
||||
XSAI --> MC_AGENT
|
||||
|
||||
Core --> TauriMCP("@proj-airi/tauri-plugin-mcp")
|
||||
Memory_PGVector("@proj-airi/memory-pgvector") --> Memory
|
||||
|
||||
style Core fill:#f9d4d4,stroke:#333,stroke-width:1px
|
||||
|
||||
@@ -1,23 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
// import { commands } from '../../bindings/tauri-plugins/window-router-link'
|
||||
|
||||
// const props = defineProps<{
|
||||
// to: string
|
||||
// label?: string
|
||||
// }>()
|
||||
|
||||
defineProps<{
|
||||
to: string
|
||||
label?: string
|
||||
}>()
|
||||
|
||||
function handleClick() {
|
||||
// commands.go(props.to, props.label ?? null)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a cursor-pointer @click="handleClick">
|
||||
<a cursor-pointer>
|
||||
<slot />
|
||||
</a>
|
||||
</template>
|
||||
|
||||
@@ -1,137 +1,3 @@
|
||||
<!-- <script setup lang="ts">
|
||||
import { useMagicKeys } from '@vueuse/core'
|
||||
import { computed, defineComponent, h } from 'vue'
|
||||
|
||||
import { useTauriRdevEventTarget } from '../../composables/tauri-rdev'
|
||||
|
||||
const eventTarget = useTauriRdevEventTarget()
|
||||
const {
|
||||
shift,
|
||||
control,
|
||||
escape,
|
||||
tab,
|
||||
v,
|
||||
u,
|
||||
e,
|
||||
s,
|
||||
v_u_e,
|
||||
u_s_e,
|
||||
i_Shift_Alt,
|
||||
a_Shift_Alt,
|
||||
n_Shift_Alt,
|
||||
current,
|
||||
} = useMagicKeys({ target: eventTarget })
|
||||
const keys = computed(() => Array.from(current))
|
||||
|
||||
const Key = defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
render() {
|
||||
return h('div', {
|
||||
class: [
|
||||
'font-mono px-4 py-2 rounded',
|
||||
this.value
|
||||
? 'opacity-100 text-primary bg-primary bg-opacity-15'
|
||||
: 'opacity-50 bg-gray-600 bg-opacity-10 dark:(bg-gray-400 bg-opacity-10)',
|
||||
],
|
||||
}, this.$slots.default?.())
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col md:flex-row">
|
||||
<img
|
||||
class="m-auto h-38 transform py-8 transition duration-500"
|
||||
:class="{ 'opacity-0': !v_u_e, 'rotate-180': shift }"
|
||||
>
|
||||
|
||||
<div>
|
||||
<div class="mb-5 mt-0 text-center">
|
||||
Press the following keys to test out
|
||||
</div>
|
||||
<div class="flex justify-center gap-3">
|
||||
<Key :value="v">
|
||||
V
|
||||
</Key>
|
||||
<Key :value="u">
|
||||
u
|
||||
</Key>
|
||||
<Key :value="e">
|
||||
e
|
||||
</Key>
|
||||
<div class="mx-1" />
|
||||
<Key :value="u">
|
||||
U
|
||||
</Key>
|
||||
<Key :value="s">
|
||||
s
|
||||
</Key>
|
||||
<Key :value="e">
|
||||
e
|
||||
</Key>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex justify-center gap-3">
|
||||
<Key :value="escape">
|
||||
Escape
|
||||
</Key>
|
||||
<Key :value="shift">
|
||||
Shift
|
||||
</Key>
|
||||
<Key :value="control">
|
||||
Control
|
||||
</Key>
|
||||
<Key :value="tab">
|
||||
Tab
|
||||
</Key>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex justify-center gap-3">
|
||||
<Key :value="v_u_e">
|
||||
Vue
|
||||
</Key>
|
||||
<Key :value="u_s_e">
|
||||
Use
|
||||
</Key>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex flex-col items-center gap-3">
|
||||
<Key :value="i_Shift_Alt">
|
||||
Shift + Alt + I
|
||||
</Key>
|
||||
<Key :value="a_Shift_Alt">
|
||||
Shift + Alt + A
|
||||
</Key>
|
||||
<Key :value="n_Shift_Alt">
|
||||
Shift + Alt + N
|
||||
</Key>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<div>Keys Pressed</div>
|
||||
<div class="mt-2 min-h-1.5em flex justify-center space-x-1">
|
||||
<code
|
||||
v-for="key in keys"
|
||||
:key="key"
|
||||
class="font-mono"
|
||||
>
|
||||
{{ key }}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<img
|
||||
class="m-auto h-38 transform py-8 transition duration-500"
|
||||
:class="{ 'opacity-0': !u_s_e, 'rotate-180': shift }"
|
||||
>
|
||||
</div>
|
||||
</template> -->
|
||||
<template>
|
||||
<slot />
|
||||
</template>
|
||||
|
||||
@@ -33,7 +33,7 @@ async function handleConfigured() {
|
||||
|
||||
<template>
|
||||
<div class="onboarding-root" h-full w-full flex flex-col overflow-x-hidden overflow-y-auto overscroll-none :class="bgClass">
|
||||
<div :class="bgClass" w="100dvw" min-h="12" w-full flex-shrink-0 select-none data-tauri-drag-region />
|
||||
<div :class="bgClass" w="100dvw" min-h="12" w-full flex-shrink-0 select-none />
|
||||
<div class="onboarding-scroll" w-full flex-1 px-3>
|
||||
<div class="onboarding-content" h-full>
|
||||
<OnboardingScreen :extra-steps="extraSteps" @skipped="handleSkipped" @configured="handleConfigured" />
|
||||
|
||||
@@ -1,154 +1,3 @@
|
||||
<!-- <script setup lang="ts">
|
||||
import { useSettings } from '@proj-airi/stage-ui/stores/settings'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useShortcutsStore } from '../../../stores/shortcuts'
|
||||
|
||||
const settings = useSettings()
|
||||
|
||||
const { t } = useI18n()
|
||||
const { shortcuts } = storeToRefs(useShortcutsStore())
|
||||
|
||||
const usePageSpecificTransitionsSettingChanged = ref(false)
|
||||
|
||||
// avoid showing the animation component when the page specific transitions are enabled
|
||||
watch(() => [settings.usePageSpecificTransitions, settings.disableTransitions], () => {
|
||||
usePageSpecificTransitionsSettingChanged.value = true
|
||||
})
|
||||
|
||||
const recordingFor = ref<string | null>(null)
|
||||
const recordingKeys = ref<{
|
||||
modifier: string[]
|
||||
key: string
|
||||
}>({
|
||||
modifier: [],
|
||||
key: '',
|
||||
})
|
||||
|
||||
// Add function to handle shortcut recording
|
||||
function startRecording(shortcut: typeof shortcuts.value[0]) {
|
||||
recordingFor.value = shortcut.type
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCurrentWindow().setMinimizable(false) // to avoid the window from being minimized when the shortcut is being recorded
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
getCurrentWindow().setMinimizable(true)
|
||||
})
|
||||
|
||||
// Handle key combinations
|
||||
useEventListener('keydown', (e) => {
|
||||
if (!recordingFor.value)
|
||||
return
|
||||
if (!e.code.startsWith('Key')) // ignore non-key events
|
||||
return
|
||||
if (e.metaKey)
|
||||
recordingKeys.value.modifier.push('Meta')
|
||||
if (e.ctrlKey)
|
||||
recordingKeys.value.modifier.push('Control')
|
||||
if (e.altKey)
|
||||
recordingKeys.value.modifier.push('Alt')
|
||||
if (e.shiftKey)
|
||||
recordingKeys.value.modifier.push('Shift')
|
||||
|
||||
if (recordingKeys.value.modifier.length === 0)
|
||||
return
|
||||
|
||||
recordingKeys.value.key = e.code.slice(3)
|
||||
const shortcut = shortcuts.value.find(s => s.type === recordingFor.value)
|
||||
if (shortcut)
|
||||
shortcut.shortcut = `${recordingKeys.value.modifier.join('+')}+${recordingKeys.value.key}`
|
||||
recordingKeys.value = {
|
||||
modifier: [],
|
||||
key: '',
|
||||
}
|
||||
recordingFor.value = null
|
||||
}, { passive: false })
|
||||
// Add click outside handler to cancel recording
|
||||
useEventListener('click', (e) => {
|
||||
if (recordingFor.value) {
|
||||
const target = e.target as HTMLElement
|
||||
if (!target.closest('.shortcut-item')) {
|
||||
recordingFor.value = null
|
||||
recordingKeys.value = {
|
||||
modifier: [],
|
||||
key: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const pressKeysMessage = computed(() => {
|
||||
if (recordingKeys.value.modifier.length === 0)
|
||||
return t('tamagotchi.settings.pages.themes.window-shortcuts.press-keys')
|
||||
return `${t('tamagotchi.settings.pages.themes.window-shortcuts.press-keys')}: ${recordingKeys.value.modifier.join('+')}+${recordingKeys.value.key}`
|
||||
})
|
||||
function isConflict(shortcut: typeof shortcuts.value[0]) {
|
||||
return shortcuts.value.some(s => s.type !== shortcut.type && s.shortcut === shortcut.shortcut)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div pb-2>
|
||||
<div
|
||||
v-for="shortcut in shortcuts" :key="shortcut.type"
|
||||
v-motion
|
||||
mb-2
|
||||
class="w-full flex items-center justify-between rounded-lg px-4 py-3 text-sm outline-none transition-all duration-250 ease-in-out"
|
||||
bg="neutral-50 dark:neutral-800"
|
||||
hover="bg-neutral-200 dark:bg-neutral-700"
|
||||
:initial="{ opacity: 0, y: 10 }"
|
||||
:enter="{ opacity: 1, y: 0 }"
|
||||
:duration="250 + (3 * 10)"
|
||||
:delay="3 * 50"
|
||||
transition="all ease-in-out duration-250"
|
||||
cursor-pointer
|
||||
@click="startRecording(shortcut)"
|
||||
>
|
||||
<span text="xs">
|
||||
{{ t(shortcut.name) }}
|
||||
</span>
|
||||
<div
|
||||
class="shortcut-item flex items-center justify-end gap-x-2 px-2 py-0.5"
|
||||
:class="{ recording: recordingFor === shortcut.type }"
|
||||
text="xs"
|
||||
>
|
||||
<div v-if="recordingFor === shortcut.type" class="pointer-events-none animate-flash animate-count-infinite">
|
||||
{{ pressKeysMessage }}
|
||||
</div>
|
||||
<div v-else class="pointer-events-none">
|
||||
{{ shortcut.shortcut }}
|
||||
</div>
|
||||
<div v-if="isConflict(shortcut)" text="xs" i-solar:danger-square-bold w-4 />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-motion
|
||||
text="neutral-200/50 dark:neutral-600/20" pointer-events-none
|
||||
fixed top="[65dvh]" right--15 z--1
|
||||
:initial="{ scale: 0.9, opacity: 0, rotate: 30 }"
|
||||
:enter="{ scale: 1, opacity: 1, rotate: 0 }"
|
||||
:duration="250"
|
||||
flex items-center justify-center
|
||||
>
|
||||
<div text="60" i-solar:keyboard-bold-duotone />
|
||||
</div>
|
||||
</template> -->
|
||||
<template>
|
||||
<slot />
|
||||
</template>
|
||||
|
||||
<!-- <route lang="yaml">
|
||||
meta:
|
||||
layout: settings
|
||||
stageTransition:
|
||||
name: slide
|
||||
</route> -->
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
// import { commands } from '../bindings/tauri-plugins/window-pass-through-on-hover'
|
||||
|
||||
export async function startClickThrough() {
|
||||
// await commands.startPassThrough()
|
||||
}
|
||||
|
||||
export async function stopClickThrough() {
|
||||
// await commands.stopPassThrough()
|
||||
}
|
||||
|
||||
@@ -3,42 +3,12 @@ import { join } from 'node:path'
|
||||
import { cwd } from 'node:process'
|
||||
|
||||
import { defineConfig } from 'bumpp'
|
||||
import { parse, stringify } from 'smol-toml'
|
||||
import { x } from 'tinyexec'
|
||||
|
||||
const androidVersionFile = join(cwd(), 'apps/stage-pocket/android/app-version.properties')
|
||||
|
||||
const androidVersionCodePattern = /^AIRI_VERSION_CODE=(\d+)$/m
|
||||
|
||||
async function syncCargoToml() {
|
||||
const cargoTomlFile = await readFile(join(cwd(), 'Cargo.toml'))
|
||||
const cargoToml = parse(cargoTomlFile.toString('utf-8')) as {
|
||||
workspace?: {
|
||||
package?: {
|
||||
version?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof cargoToml !== 'object' || cargoToml === null) {
|
||||
throw new TypeError('Cargo.toml does not contain a valid object')
|
||||
}
|
||||
if (typeof cargoToml.workspace?.package?.version !== 'string') {
|
||||
throw new TypeError('Cargo.toml does not contain a valid version in workspace.package.version')
|
||||
}
|
||||
|
||||
const packageJSONFile = join(cwd(), 'package.json')
|
||||
const packageJSON = JSON.parse(await readFile(packageJSONFile, 'utf-8'))
|
||||
if (typeof packageJSON?.version !== 'string' || packageJSON?.version === null) {
|
||||
throw new TypeError('package.json does not contain a valid version')
|
||||
}
|
||||
|
||||
cargoToml.workspace.package.version = packageJSON.version
|
||||
console.info(`Bumping Cargo.toml version to ${cargoToml.workspace.package.version} (from package.json, ${packageJSON.version})`)
|
||||
|
||||
await writeFile(join(cwd(), 'Cargo.toml'), stringify(cargoToml))
|
||||
}
|
||||
|
||||
async function syncAndroidVersion(version: string) {
|
||||
const androidVersionContent = await readFile(androidVersionFile, 'utf-8')
|
||||
const currentVersionCodeMatch = androidVersionContent.match(androidVersionCodePattern)
|
||||
@@ -69,9 +39,6 @@ export default defineConfig({
|
||||
execute: async (operation) => {
|
||||
await x('pnpm', ['publish', '-r', '--access', 'public', '--no-git-checks', '--dry-run'])
|
||||
const nextVersion = operation.state.newVersion
|
||||
|
||||
await syncCargoToml()
|
||||
await syncAndroidVersion(nextVersion)
|
||||
await x('cargo', ['generate-lockfile'])
|
||||
},
|
||||
})
|
||||
|
||||
-4732
File diff suppressed because it is too large
Load Diff
@@ -1,50 +0,0 @@
|
||||
[package]
|
||||
name = "tauri-plugin-ipc-audio-transcription-ort"
|
||||
version.workspace = true
|
||||
description = "Audio transcription 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-transcription-ort"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "tauri_plugin_ipc_audio_transcription_ort"
|
||||
crate-type = [
|
||||
"staticlib",
|
||||
"cdylib",
|
||||
"rlib"
|
||||
]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[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"
|
||||
hf-hub = "0.4.3"
|
||||
symphonia = "0.5.4"
|
||||
rubato = "0.16.2"
|
||||
byteorder = "1.5.0"
|
||||
clap = { version = "4.5.40", features = ["derive"] }
|
||||
rustfft = "6.4.0"
|
||||
|
||||
[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", "cuda"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
ort = { version = "2.0.0-rc.10", features = ["ndarray", "cuda"] }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { version = "2.2", features = [ "build" ] }
|
||||
@@ -1,5 +0,0 @@
|
||||
const COMMANDS: &[&str] = &["load_ort_model_whisper", "ipc_audio_transcription"];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS).build();
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
# 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
@@ -1,13 +0,0 @@
|
||||
# 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"]
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-load-ort-model-whisper"
|
||||
description = "Enables the load_ort_model_whisper command without any pre-configured scope."
|
||||
commands.allow = ["load_ort_model_whisper"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-load-ort-model-whisper"
|
||||
description = "Denies the load_ort_model_whisper command without any pre-configured scope."
|
||||
commands.deny = ["load_ort_model_whisper"]
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
## 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-whisper`
|
||||
- `allow-ipc-audio-transcription`
|
||||
|
||||
## Permission Table
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Identifier</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`ipc-audio-transcription-ort:allow-ipc-audio-transcription`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the ipc_audio_transcription command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`ipc-audio-transcription-ort:deny-ipc-audio-transcription`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the ipc_audio_transcription command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`ipc-audio-transcription-ort: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-ort:deny-load-candle-model-whisper`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the load_candle_model_whisper command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`ipc-audio-transcription-ort:allow-load-ort-model-whisper`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the load_ort_model_whisper command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`ipc-audio-transcription-ort:deny-load-ort-model-whisper`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the load_ort_model_whisper command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -1,15 +0,0 @@
|
||||
"$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-whisper",
|
||||
"allow-ipc-audio-transcription",
|
||||
]
|
||||
@@ -1,342 +0,0 @@
|
||||
{
|
||||
"$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": "Enables the load_ort_model_whisper command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-load-ort-model-whisper",
|
||||
"markdownDescription": "Enables the load_ort_model_whisper command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the load_ort_model_whisper command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-load-ort-model-whisper",
|
||||
"markdownDescription": "Denies the load_ort_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-ort-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-ort-model-whisper`\n- `allow-ipc-audio-transcription`"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod huggingface;
|
||||
@@ -1,114 +0,0 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
use clap::ValueEnum;
|
||||
use log::info;
|
||||
use tauri::{
|
||||
Manager,
|
||||
Runtime,
|
||||
plugin::{Builder as PluginBuilder, TauriPlugin},
|
||||
};
|
||||
|
||||
mod helpers;
|
||||
mod models;
|
||||
|
||||
#[derive(Default)]
|
||||
struct AppDataWhisperProcessor {
|
||||
whisper_processor: Option<models::whisper::whisper::WhisperPipeline>,
|
||||
}
|
||||
|
||||
use crate::models::{
|
||||
new_whisper_processor,
|
||||
whisper::{self, whisper::WhichModel},
|
||||
};
|
||||
|
||||
#[tauri::command]
|
||||
async fn load_ort_model_whisper<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
window: tauri::WebviewWindow<R>,
|
||||
model_type: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
info!("Loading models...");
|
||||
|
||||
{
|
||||
let data = app.state::<Mutex<AppDataWhisperProcessor>>();
|
||||
let data = data.lock().unwrap();
|
||||
if data.whisper_processor.is_some() {
|
||||
info!("Whisper model already loaded, skipping...");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Load the traditional whisper models first
|
||||
match new_whisper_processor(
|
||||
window,
|
||||
Some(WhichModel::from_str(
|
||||
model_type
|
||||
.unwrap_or_else(|| "medium".to_string())
|
||||
.as_str(),
|
||||
true,
|
||||
)?),
|
||||
) {
|
||||
Ok(p) => {
|
||||
let data = app.state::<Mutex<AppDataWhisperProcessor>>();
|
||||
let mut data = data.lock().unwrap();
|
||||
data.whisper_processor = Some(p);
|
||||
info!("Whisper model loaded successfully");
|
||||
},
|
||||
Err(e) => {
|
||||
let error_message = format!("Failed to load Whisper model: {}", e);
|
||||
info!("{}", error_message);
|
||||
return Err(error_message);
|
||||
},
|
||||
}
|
||||
|
||||
info!("All models loaded successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn ipc_audio_transcription<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
chunk: Vec<f32>,
|
||||
language: Option<String>,
|
||||
) -> Result<String, String> {
|
||||
info!("Processing audio transcription...");
|
||||
|
||||
let data = app.state::<Mutex<AppDataWhisperProcessor>>();
|
||||
|
||||
// Check if processor exists first
|
||||
{
|
||||
let data = data.lock().unwrap();
|
||||
if data.whisper_processor.is_none() {
|
||||
return Err("Whisper model is not loaded".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Then mutable borrow
|
||||
let mut data = data.lock().unwrap();
|
||||
let processor = data.whisper_processor.as_mut().unwrap();
|
||||
|
||||
let mut config = whisper::whisper::GenerationConfig::default();
|
||||
config.language = language;
|
||||
|
||||
let transcription = processor
|
||||
.transcribe(chunk.as_slice(), &config)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
info!("Transcription completed: {}", transcription);
|
||||
|
||||
Ok(transcription)
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
PluginBuilder::new("ipc-audio-transcription-ort")
|
||||
.setup(|app, _| {
|
||||
info!("Initializing audio transcription plugin...");
|
||||
app.manage(Mutex::new(AppDataWhisperProcessor::default()));
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
load_ort_model_whisper,
|
||||
ipc_audio_transcription,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
pub mod whisper;
|
||||
|
||||
use log::info;
|
||||
use ort::execution_providers::{CUDAExecutionProvider, CoreMLExecutionProvider, ExecutionProvider};
|
||||
use tauri::Runtime;
|
||||
|
||||
pub fn new_whisper_processor<R: Runtime>(
|
||||
window: tauri::WebviewWindow<R>,
|
||||
model_type: Option<whisper::whisper::WhichModel>,
|
||||
) -> anyhow::Result<whisper::whisper::WhisperPipeline> {
|
||||
let cuda = CUDAExecutionProvider::default().with_device_id(0);
|
||||
let coreml = CoreMLExecutionProvider::default();
|
||||
|
||||
let whisper_model = model_type.unwrap_or_else(|| {
|
||||
if cuda.is_available().unwrap_or(false) {
|
||||
whisper::whisper::WhichModel::LargeV3
|
||||
} else if coreml.is_available().unwrap_or(false) {
|
||||
whisper::whisper::WhichModel::Base
|
||||
} else {
|
||||
whisper::whisper::WhichModel::Tiny
|
||||
}
|
||||
});
|
||||
|
||||
info!("Loading whisper model: {:?}", whisper_model);
|
||||
let (model_id, revision) = whisper_model.model_and_revision();
|
||||
whisper::whisper::WhisperPipeline::new(whisper_model, model_id, revision, window)
|
||||
}
|
||||
Binary file not shown.
BIN
Binary file not shown.
@@ -1,2 +0,0 @@
|
||||
pub mod whisper;
|
||||
pub mod whisper_processor;
|
||||
@@ -1,561 +0,0 @@
|
||||
use std::{borrow::Cow, collections::HashMap, path::PathBuf};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use clap::ValueEnum;
|
||||
use hf_hub::{
|
||||
Repo,
|
||||
RepoType,
|
||||
api::sync::{Api, ApiBuilder},
|
||||
};
|
||||
use ndarray::{Array2, ArrayView3, Axis, s};
|
||||
use ort::{
|
||||
execution_providers::{
|
||||
CPUExecutionProvider,
|
||||
CUDAExecutionProvider,
|
||||
CoreMLExecutionProvider,
|
||||
DirectMLExecutionProvider,
|
||||
},
|
||||
session::{Session, SessionInputValue, builder::GraphOptimizationLevel},
|
||||
value::Value,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tauri::Runtime;
|
||||
use tokenizers::Tokenizer;
|
||||
|
||||
use super::whisper_processor::WhisperProcessor;
|
||||
use crate::helpers::huggingface::create_progress_emitter;
|
||||
|
||||
// Helper function to provide a default value of true for serde
|
||||
const fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
// Task token ids used by the decoder prompt.
|
||||
// These are the special token ids added to the tokenizer / model vocabulary.
|
||||
const TASK_TRANSLATE_ID: i64 = 50358;
|
||||
const TASK_TRANSCRIBE_ID: i64 = 50359;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct WhisperConfig {
|
||||
pub num_mel_bins: i64,
|
||||
pub decoder_start_token_id: i64,
|
||||
pub eos_token_id: i64,
|
||||
// Corrected: Use serde default for missing is_multilingual field
|
||||
#[serde(default = "default_true")]
|
||||
pub is_multilingual: bool,
|
||||
#[serde(default)]
|
||||
pub no_timestamps_token_id: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub lang_to_id: HashMap<String, i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GenerationConfig {
|
||||
pub language: Option<String>,
|
||||
pub task: String,
|
||||
pub return_timestamps: bool,
|
||||
pub max_new_tokens: usize,
|
||||
}
|
||||
|
||||
impl Default for GenerationConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
language: Some("en".to_string()),
|
||||
task: "transcribe".to_string(),
|
||||
return_timestamps: true,
|
||||
max_new_tokens: 128,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static WHISPER_LANGUAGES: std::sync::LazyLock<HashMap<&'static str, &'static str>> =
|
||||
std::sync::LazyLock::new(|| {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("en", "english");
|
||||
m.insert("zh", "chinese");
|
||||
m.insert("de", "german");
|
||||
m.insert("es", "spanish");
|
||||
m.insert("ru", "russian");
|
||||
m.insert("ko", "korean");
|
||||
m.insert("fr", "french");
|
||||
m.insert("ja", "japanese");
|
||||
m.insert("pt", "portuguese");
|
||||
m.insert("tr", "turkish");
|
||||
m.insert("pl", "polish");
|
||||
m.insert("ca", "catalan");
|
||||
m.insert("nl", "dutch");
|
||||
m.insert("ar", "arabic");
|
||||
m.insert("sv", "swedish");
|
||||
m.insert("it", "italian");
|
||||
m.insert("id", "indonesian");
|
||||
m.insert("hi", "hindi");
|
||||
m.insert("fi", "finnish");
|
||||
m.insert("vi", "vietnamese");
|
||||
m.insert("he", "hebrew");
|
||||
m.insert("uk", "ukrainian");
|
||||
m.insert("el", "greek");
|
||||
m.insert("ms", "malay");
|
||||
m.insert("cs", "czech");
|
||||
m.insert("ro", "romanian");
|
||||
m.insert("da", "danish");
|
||||
m.insert("hu", "hungarian");
|
||||
m.insert("ta", "tamil");
|
||||
m.insert("no", "norwegian");
|
||||
m.insert("th", "thai");
|
||||
m.insert("ur", "urdu");
|
||||
m.insert("hr", "croatian");
|
||||
m.insert("bg", "bulgarian");
|
||||
m.insert("lt", "lithuanian");
|
||||
m.insert("la", "latin");
|
||||
m.insert("mi", "maori");
|
||||
m.insert("ml", "malayalam");
|
||||
m.insert("cy", "welsh");
|
||||
m.insert("sk", "slovak");
|
||||
m.insert("te", "telugu");
|
||||
m.insert("fa", "persian");
|
||||
m.insert("lv", "latvian");
|
||||
m.insert("bn", "bengali");
|
||||
m.insert("sr", "serbian");
|
||||
m.insert("az", "azerbaijani");
|
||||
m.insert("sl", "slovenian");
|
||||
m.insert("kn", "kannada");
|
||||
m.insert("et", "estonian");
|
||||
m.insert("mk", "macedonian");
|
||||
m.insert("br", "breton");
|
||||
m.insert("eu", "basque");
|
||||
m.insert("is", "icelandic");
|
||||
m.insert("hy", "armenian");
|
||||
m.insert("ne", "nepali");
|
||||
m.insert("mn", "mongolian");
|
||||
m.insert("bs", "bosnian");
|
||||
m.insert("kk", "kazakh");
|
||||
m.insert("sq", "albanian");
|
||||
m.insert("sw", "swahili");
|
||||
m.insert("gl", "galician");
|
||||
m.insert("mr", "marathi");
|
||||
m.insert("pa", "punjabi");
|
||||
m.insert("si", "sinhala");
|
||||
m.insert("km", "khmer");
|
||||
m.insert("sn", "shona");
|
||||
m.insert("yo", "yoruba");
|
||||
m.insert("so", "somali");
|
||||
m.insert("af", "afrikaans");
|
||||
m.insert("oc", "occitan");
|
||||
m.insert("ka", "georgian");
|
||||
m.insert("be", "belarusian");
|
||||
m.insert("tg", "tajik");
|
||||
m.insert("sd", "sindhi");
|
||||
m.insert("gu", "gujarati");
|
||||
m.insert("am", "amharic");
|
||||
m.insert("yi", "yiddish");
|
||||
m.insert("lo", "lao");
|
||||
m.insert("uz", "uzbek");
|
||||
m.insert("fo", "faroese");
|
||||
m.insert("ht", "haitian creole");
|
||||
m.insert("ps", "pashto");
|
||||
m.insert("tk", "turkmen");
|
||||
m.insert("nn", "nynorsk");
|
||||
m.insert("mt", "maltese");
|
||||
m.insert("sa", "sanskrit");
|
||||
m.insert("lb", "luxembourgish");
|
||||
m.insert("my", "myanmar");
|
||||
m.insert("bo", "tibetan");
|
||||
m.insert("tl", "tagalog");
|
||||
m.insert("mg", "malagasy");
|
||||
m.insert("as", "assamese");
|
||||
m.insert("tt", "tatar");
|
||||
m.insert("haw", "hawaiian");
|
||||
m.insert("ln", "lingala");
|
||||
m.insert("ha", "hausa");
|
||||
m.insert("ba", "bashkir");
|
||||
m.insert("jw", "javanese");
|
||||
m.insert("su", "sundanese");
|
||||
m
|
||||
});
|
||||
|
||||
static WHISPER_TO_LANGUAGE_CODE: std::sync::LazyLock<HashMap<&'static str, &'static str>> =
|
||||
std::sync::LazyLock::new(|| {
|
||||
let mut m: HashMap<&'static str, &'static str> = WHISPER_LANGUAGES
|
||||
.iter()
|
||||
.map(|(k, v)| (*v, *k))
|
||||
.collect();
|
||||
m.insert("burmese", "my");
|
||||
m.insert("valencian", "ca");
|
||||
m.insert("flemish", "nl");
|
||||
m.insert("haitian", "ht");
|
||||
m.insert("letzeburgesch", "lb");
|
||||
m.insert("pushto", "ps");
|
||||
m.insert("panjabi", "pa");
|
||||
m.insert("moldavian", "ro");
|
||||
m.insert("moldovan", "ro");
|
||||
m.insert("sinhalese", "si");
|
||||
m.insert("castilian", "es");
|
||||
m
|
||||
});
|
||||
|
||||
pub fn whisper_language_to_code(language: &str) -> Result<String> {
|
||||
// Accept either two-letter code (en) or full name (english).
|
||||
let lower_lang = language.to_lowercase();
|
||||
if let Some(&code) = WHISPER_TO_LANGUAGE_CODE.get(lower_lang.as_str()) {
|
||||
return Ok(code.to_string());
|
||||
}
|
||||
if WHISPER_LANGUAGES.contains_key(lower_lang.as_str()) {
|
||||
return Ok(lower_lang);
|
||||
}
|
||||
Err(anyhow!("Language '{}' is not supported.", language))
|
||||
}
|
||||
|
||||
fn get_or_download_file<R: Runtime>(
|
||||
cache_repo: &hf_hub::CacheRepo,
|
||||
repo: &hf_hub::api::sync::ApiRepo,
|
||||
window: tauri::WebviewWindow<R>,
|
||||
file_sub_path: &str,
|
||||
event_name: &str,
|
||||
) -> Result<PathBuf> {
|
||||
match cache_repo.get(file_sub_path) {
|
||||
Some(p) => Ok(p),
|
||||
None => repo
|
||||
.download_with_progress(
|
||||
file_sub_path,
|
||||
create_progress_emitter(window.clone(), event_name, file_sub_path.to_string()),
|
||||
)
|
||||
.map_err(|e| anyhow!("failed to download {}: {}", file_sub_path, e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Whisper {
|
||||
encoder_session: Session,
|
||||
decoder_session: Session,
|
||||
config: WhisperConfig,
|
||||
}
|
||||
|
||||
impl Whisper {
|
||||
pub fn new<R: Runtime>(
|
||||
model_id: &str,
|
||||
revision: &str,
|
||||
window: tauri::WebviewWindow<R>,
|
||||
) -> Result<Self> {
|
||||
let cache_api = hf_hub::Cache::from_env();
|
||||
let cache_repo = cache_api.repo(Repo::with_revision(
|
||||
model_id.to_string(),
|
||||
RepoType::Model,
|
||||
revision.to_string(),
|
||||
));
|
||||
|
||||
let api = ApiBuilder::new().with_progress(false).build()?;
|
||||
let repo = api.repo(Repo::with_revision(
|
||||
model_id.to_string(),
|
||||
RepoType::Model,
|
||||
revision.to_string(),
|
||||
));
|
||||
|
||||
let encoder_model_path = get_or_download_file(
|
||||
&cache_repo,
|
||||
&repo,
|
||||
window.clone(),
|
||||
"onnx/encoder_model.onnx",
|
||||
"tauri-plugins:tauri-plugin-ipc-audio-transcription-ort:load-model-whisper-progress",
|
||||
)?;
|
||||
|
||||
let decoder_model_path = get_or_download_file(
|
||||
&cache_repo,
|
||||
&repo,
|
||||
window.clone(),
|
||||
"onnx/decoder_model.onnx",
|
||||
"tauri-plugins:tauri-plugin-ipc-audio-transcription-ort:load-model-whisper-progress",
|
||||
)?;
|
||||
|
||||
let config_path = match cache_repo.get("config.json") {
|
||||
Some(path) => path,
|
||||
None => repo.download_with_progress(
|
||||
"config.json",
|
||||
create_progress_emitter(
|
||||
window.clone(),
|
||||
"tauri-plugins:tauri-plugin-ipc-audio-transcription-ort:load-model-whisper-progress",
|
||||
"config.json".to_string(),
|
||||
),
|
||||
)?,
|
||||
};
|
||||
|
||||
let tokenizer_config_path = match cache_repo.get("tokenizer_config.json") {
|
||||
Some(path) => path,
|
||||
None => repo.download_with_progress(
|
||||
"tokenizer_config.json",
|
||||
create_progress_emitter(
|
||||
window.clone(),
|
||||
"tauri-plugins:tauri-plugin-ipc-audio-transcription-ort:load-model-whisper-progress",
|
||||
"tokenizer_config.json".to_string(),
|
||||
),
|
||||
)?,
|
||||
};
|
||||
|
||||
let encoder_session = Self::create_optimized_session(encoder_model_path)?;
|
||||
let decoder_session = Self::create_optimized_session(decoder_model_path)?;
|
||||
|
||||
let mut config: WhisperConfig = serde_json::from_str(&std::fs::read_to_string(config_path)?)?;
|
||||
let tokenizer_config: serde_json::Value =
|
||||
serde_json::from_str(&std::fs::read_to_string(tokenizer_config_path)?)?;
|
||||
|
||||
if let Some(added_tokens) = tokenizer_config["added_tokens"].as_array() {
|
||||
let lang_to_id = added_tokens
|
||||
.iter()
|
||||
.filter_map(|token| {
|
||||
let content = token["content"].as_str()?;
|
||||
let id = token["id"].as_i64()?;
|
||||
if content.starts_with("<|")
|
||||
&& content.ends_with("|>")
|
||||
&& content.len() >= 4
|
||||
&& content.len() <= 6
|
||||
{
|
||||
Some((content.to_string(), id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
config.lang_to_id = lang_to_id;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
encoder_session,
|
||||
decoder_session,
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_optimized_session(model_path: PathBuf) -> Result<Session> {
|
||||
let session = Session::builder()?
|
||||
.with_optimization_level(GraphOptimizationLevel::Level3)?
|
||||
.with_parallel_execution(true)?
|
||||
.with_execution_providers([
|
||||
CUDAExecutionProvider::default()
|
||||
.with_device_id(0)
|
||||
.build(),
|
||||
CoreMLExecutionProvider::default().build(),
|
||||
DirectMLExecutionProvider::default()
|
||||
.with_device_id(0)
|
||||
.build(),
|
||||
CPUExecutionProvider::default().build(),
|
||||
])?
|
||||
.commit_from_file(model_path)?;
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
fn retrieve_init_tokens(
|
||||
&self,
|
||||
gen_config: &GenerationConfig,
|
||||
) -> Result<Vec<i64>> {
|
||||
let mut init_tokens = vec![self.config.decoder_start_token_id];
|
||||
let task_id = if gen_config.task == "translate" {
|
||||
TASK_TRANSLATE_ID
|
||||
} else {
|
||||
TASK_TRANSCRIBE_ID
|
||||
};
|
||||
|
||||
if self.config.is_multilingual {
|
||||
let lang = gen_config.language.as_deref().unwrap_or("en");
|
||||
let lang_code = whisper_language_to_code(lang)?;
|
||||
let lang_token = format!("<|{lang_code}|>");
|
||||
let lang_token_id = self
|
||||
.config
|
||||
.lang_to_id
|
||||
.get(&lang_token)
|
||||
.ok_or_else(|| anyhow!("Language token not found for: {}", lang_token))?;
|
||||
init_tokens.push(*lang_token_id);
|
||||
}
|
||||
init_tokens.push(task_id);
|
||||
|
||||
if !gen_config.return_timestamps
|
||||
&& let Some(no_timestamps_id) = self.config.no_timestamps_token_id
|
||||
{
|
||||
init_tokens.push(no_timestamps_id);
|
||||
}
|
||||
Ok(init_tokens)
|
||||
}
|
||||
|
||||
pub fn generate(
|
||||
&mut self,
|
||||
input_features: ArrayView3<f32>,
|
||||
gen_config: &GenerationConfig,
|
||||
) -> Result<Vec<i64>> {
|
||||
let (batch_size, num_mel_bins, sequence_length) = input_features.dim();
|
||||
let expected_mel_bins = usize::try_from(self.config.num_mel_bins)?;
|
||||
|
||||
if batch_size != 1 || num_mel_bins != expected_mel_bins {
|
||||
return Err(anyhow!(
|
||||
"Incorrect input feature shape. Expected [1, {}, ...], but got [{}, {}, {}]",
|
||||
expected_mel_bins,
|
||||
batch_size,
|
||||
num_mel_bins,
|
||||
sequence_length
|
||||
));
|
||||
}
|
||||
|
||||
let mut decoder_input_ids = self.retrieve_init_tokens(gen_config)?;
|
||||
|
||||
let owned_input = input_features.to_owned();
|
||||
let inputs = vec![("input_features", Value::from_array(owned_input)?)];
|
||||
let encoder_outputs = self.encoder_session.run(inputs)?;
|
||||
let encoder_hidden_states = encoder_outputs
|
||||
.get("last_hidden_state")
|
||||
.ok_or_else(|| anyhow!("encoder output did not contain 'last_hidden_state'"))?;
|
||||
|
||||
let mut generated_tokens = Vec::new();
|
||||
|
||||
// KV Cache (commented scaffold remains)
|
||||
// let num_decoder_layers = self.config.decoder_layers as usize;
|
||||
// let head_dim = self.config.d_model / self.config.decoder_attention_heads;
|
||||
// let mut past_key_values: Vec<Array4<f32>> = (0..num_decoder_layers * 2)
|
||||
// .map(|_| Array4::<f32>::zeros((1, self.config.decoder_attention_heads as usize, 0, head_dim as usize)))
|
||||
// .collect();
|
||||
|
||||
for _step in 0..gen_config.max_new_tokens {
|
||||
let decoder_input_ids_array =
|
||||
Array2::from_shape_vec((1, decoder_input_ids.len()), decoder_input_ids.clone())?
|
||||
.mapv(|x| x);
|
||||
|
||||
// KV Cache
|
||||
// let mut decoder_inputs: Vec<(Cow<'_, str>, SessionInputValue<'_>)> = Vec::with_capacity(2 + past_key_values.len());
|
||||
|
||||
let decoder_inputs: Vec<(Cow<'_, str>, SessionInputValue<'_>)> = vec![
|
||||
// name = encoder_hidden_states, type = tensor: float32[batch_size,encoder_sequence_length / 2,512]
|
||||
("encoder_hidden_states".into(), encoder_hidden_states.into()),
|
||||
// name = input_ids, type = tensor: int64[batch_size,decoder_sequence_length]
|
||||
(
|
||||
"input_ids".into(),
|
||||
Value::from_array(decoder_input_ids_array)?.into(),
|
||||
),
|
||||
];
|
||||
|
||||
let decoder_outputs = self.decoder_session.run(decoder_inputs)?;
|
||||
let logits_ref = decoder_outputs.get("logits").unwrap().view();
|
||||
let logits_view = logits_ref.try_extract_array::<f32>()?;
|
||||
let next_token_logits = logits_view.slice(s![0, -1, ..]);
|
||||
|
||||
let next_token = next_token_logits
|
||||
.iter()
|
||||
.enumerate()
|
||||
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
|
||||
.map(|(index, _)| i64::try_from(index).unwrap())
|
||||
.unwrap();
|
||||
|
||||
if next_token == self.config.eos_token_id {
|
||||
break;
|
||||
}
|
||||
|
||||
generated_tokens.push(next_token);
|
||||
// keep the full prefix for the next iteration (no KV cache): APPEND rather than replace.
|
||||
decoder_input_ids.push(next_token);
|
||||
}
|
||||
|
||||
Ok(generated_tokens)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
|
||||
pub enum WhichModel {
|
||||
Tiny,
|
||||
Base,
|
||||
Small,
|
||||
Medium,
|
||||
LargeV3,
|
||||
LargeV3Turbo,
|
||||
}
|
||||
|
||||
impl WhichModel {
|
||||
pub const fn model_and_revision(self) -> (&'static str, &'static str) {
|
||||
match self {
|
||||
Self::Tiny => ("onnx-community/whisper-tiny-ONNX", "main"),
|
||||
Self::Base => ("onnx-community/whisper-base-ONNX", "main"),
|
||||
Self::Small => ("onnx-community/whisper-small-ONNX", "main"),
|
||||
Self::Medium => ("onnx-community/whisper-medium-ONNX", "main"),
|
||||
Self::LargeV3 => ("onnx-community/whisper-large-v3-ONNX", "main"),
|
||||
Self::LargeV3Turbo => ("onnx-community/whisper-large-v3-turbo-ONNX", "main"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A pipeline that encapsulates the full Whisper transcription process.
|
||||
pub struct WhisperPipeline {
|
||||
model: Whisper,
|
||||
processor: WhisperProcessor,
|
||||
tokenizer: Tokenizer,
|
||||
}
|
||||
|
||||
impl WhisperPipeline {
|
||||
pub fn new<R: Runtime>(
|
||||
which_model: WhichModel,
|
||||
model_id: &str,
|
||||
revision: &str,
|
||||
window: tauri::WebviewWindow<R>,
|
||||
) -> Result<Self> {
|
||||
let model = Whisper::new(model_id, revision, window.clone())?;
|
||||
|
||||
// Initialize our new processor
|
||||
let processor = WhisperProcessor::new(which_model)?;
|
||||
|
||||
let cache_api = hf_hub::Cache::from_env();
|
||||
let cache_repo = cache_api.repo(Repo::with_revision(
|
||||
model_id.to_string(),
|
||||
RepoType::Model,
|
||||
revision.to_string(),
|
||||
));
|
||||
|
||||
let api = Api::new()?;
|
||||
let repo = api.repo(hf_hub::Repo::with_revision(
|
||||
model_id.to_string(),
|
||||
hf_hub::RepoType::Model,
|
||||
revision.to_string(),
|
||||
));
|
||||
|
||||
let tokenizer_path = get_or_download_file(
|
||||
&cache_repo,
|
||||
&repo,
|
||||
window.clone(),
|
||||
"tokenizer.json",
|
||||
"tauri-plugins:tauri-plugin-ipc-audio-transcription-ort:load-model-whisper-progress",
|
||||
)?;
|
||||
|
||||
let tokenizer = Tokenizer::from_file(tokenizer_path)
|
||||
.map_err(|e| anyhow!("Failed to load tokenizer: {}", e))?;
|
||||
|
||||
Ok(Self {
|
||||
model,
|
||||
processor,
|
||||
tokenizer,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn transcribe(
|
||||
&mut self,
|
||||
audio: &[f32],
|
||||
gen_config: &GenerationConfig,
|
||||
) -> Result<String> {
|
||||
// Process the raw audio into a mel spectrogram with the correct shape [80, 3000] for normal, and [128, 3000] for large-v3
|
||||
let input_features = self.processor.process(audio);
|
||||
|
||||
// Add the batch dimension, making the shape [1, 80, 3000] for normal, and [1, 128, 3000] for large-v3
|
||||
let input_features = input_features.insert_axis(Axis(0));
|
||||
|
||||
// Generate tokens. This will now work without a shape error.
|
||||
let generated_tokens = self
|
||||
.model
|
||||
.generate(input_features.view(), gen_config)?;
|
||||
|
||||
// Convert tokens safely to u32 for tokenizer.decode
|
||||
let generated_tokens_u32: Vec<u32> = generated_tokens
|
||||
.iter()
|
||||
.map(|&tok| {
|
||||
u32::try_from(tok)
|
||||
.map_err(|e| anyhow!("token id out of range when converting to u32: {} ({})", tok, e))
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let transcript = self
|
||||
.tokenizer
|
||||
.decode(&generated_tokens_u32, true)
|
||||
.map_err(|e| anyhow!("Failed to decode tokens: {}", e))?;
|
||||
|
||||
Ok(transcript)
|
||||
}
|
||||
}
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
use anyhow::{Result, anyhow};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use ndarray::{Array1, Array2, s};
|
||||
use rustfft::{FftPlanner, num_complex::Complex};
|
||||
|
||||
use super::whisper::WhichModel;
|
||||
|
||||
// Constants from the Whisper paper/implementation
|
||||
const SAMPLE_RATE: usize = 16000;
|
||||
const N_FFT: usize = 400;
|
||||
const N_MELS: usize = 80; // <--- Crucial: Use 80 for base, 128 for large-v3
|
||||
const HOP_LENGTH: usize = 160;
|
||||
const CHUNK_LENGTH: usize = 30;
|
||||
const N_SAMPLES: usize = CHUNK_LENGTH * SAMPLE_RATE; // 480000 samples
|
||||
const N_FRAMES: usize = N_SAMPLES / HOP_LENGTH; // 3000 frames
|
||||
|
||||
pub struct WhisperProcessor {
|
||||
mel_filters: Array2<f32>,
|
||||
}
|
||||
|
||||
impl WhisperProcessor {
|
||||
pub fn new(which_model: WhichModel) -> Result<Self> {
|
||||
// Load the 80-bin mel filters
|
||||
if which_model == WhichModel::LargeV3 || which_model == WhichModel::LargeV3Turbo {
|
||||
let mel_bytes = include_bytes!("./melfilters128.bytes");
|
||||
let mut mel_filters_vec = vec![0f32; mel_bytes.len() / 4];
|
||||
LittleEndian::read_f32_into(mel_bytes, &mut mel_filters_vec);
|
||||
|
||||
// The shape is [80, 201]. N_FFT/2 + 1 = 201.
|
||||
let mel_filters = Array2::from_shape_vec((N_MELS, N_FFT / 2 + 1), mel_filters_vec)
|
||||
.map_err(|e| anyhow!("Failed to create mel filters array: {}", e))?;
|
||||
|
||||
Ok(Self { mel_filters })
|
||||
} else {
|
||||
let mel_bytes = include_bytes!("./melfilters.bytes");
|
||||
let mut mel_filters_vec = vec![0f32; mel_bytes.len() / 4];
|
||||
LittleEndian::read_f32_into(mel_bytes, &mut mel_filters_vec);
|
||||
|
||||
// The shape is [80, 201]. N_FFT/2 + 1 = 201.
|
||||
let mel_filters = Array2::from_shape_vec((N_MELS, N_FFT / 2 + 1), mel_filters_vec)
|
||||
.map_err(|e| anyhow!("Failed to create mel filters array: {}", e))?;
|
||||
|
||||
Ok(Self { mel_filters })
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes raw audio PCM data into a mel spectrogram.
|
||||
pub fn process(
|
||||
&self,
|
||||
audio: &[f32],
|
||||
) -> Array2<f32> {
|
||||
// 1. Pad or truncate the audio to 30 seconds
|
||||
let mut pcm_data = audio.to_vec();
|
||||
if pcm_data.len() < N_SAMPLES {
|
||||
pcm_data.resize(N_SAMPLES, 0.0);
|
||||
} else {
|
||||
pcm_data.truncate(N_SAMPLES);
|
||||
}
|
||||
let pcm_data = Array1::from_vec(pcm_data);
|
||||
|
||||
// 2. Compute the Short-Time Fourier Transform (STFT)
|
||||
let stft = self.stft(&pcm_data);
|
||||
// 3. Apply the mel filter bank
|
||||
let mel_spectrogram = self.mel_filters.dot(&stft);
|
||||
// 4. Apply logarithmic scaling
|
||||
self.log_mel_spectrogram(&mel_spectrogram)
|
||||
}
|
||||
|
||||
/// Computes the Short-Time Fourier Transform (STFT) of the input audio.
|
||||
fn stft(
|
||||
&self,
|
||||
pcm_data: &Array1<f32>,
|
||||
) -> Array2<f32> {
|
||||
// Create a Hann window
|
||||
let window: Array1<f32> = Array1::from_shape_fn(N_FFT, |i| {
|
||||
0.5 * (1.0 - (2.0 * std::f32::consts::PI * i as f32 / N_FFT as f32).cos())
|
||||
});
|
||||
|
||||
// Pad the input data
|
||||
let mut padded_data = Array1::zeros(pcm_data.len() + N_FFT);
|
||||
|
||||
let end = padded_data.len() - N_FFT / 2;
|
||||
padded_data
|
||||
.slice_mut(s![N_FFT / 2..end])
|
||||
.assign(pcm_data);
|
||||
|
||||
let frames = padded_data
|
||||
.windows(N_FFT)
|
||||
.into_iter()
|
||||
.step_by(HOP_LENGTH);
|
||||
|
||||
let mut planner = FftPlanner::<f32>::new();
|
||||
let fft = planner.plan_fft_forward(N_FFT);
|
||||
|
||||
// Process each frame
|
||||
let mut stft_result = Array2::zeros((N_FFT / 2 + 1, N_FRAMES));
|
||||
for (i, frame) in frames.enumerate() {
|
||||
if i >= N_FRAMES {
|
||||
break;
|
||||
}
|
||||
|
||||
// Apply window
|
||||
let windowed_frame = frame.to_owned() * window.view();
|
||||
|
||||
// Prepare buffer for FFT
|
||||
let mut buffer: Vec<Complex<f32>> = windowed_frame
|
||||
.iter()
|
||||
.map(|&x| Complex { re: x, im: 0.0 })
|
||||
.collect();
|
||||
|
||||
// Perform FFT
|
||||
fft.process(&mut buffer);
|
||||
|
||||
// Compute magnitude and store it
|
||||
for j in 0..=(N_FFT / 2) {
|
||||
stft_result[[j, i]] = buffer[j].norm_sqr().sqrt();
|
||||
}
|
||||
}
|
||||
|
||||
stft_result
|
||||
}
|
||||
|
||||
/// Converts a mel spectrogram to a log-scaled mel spectrogram.
|
||||
fn log_mel_spectrogram(
|
||||
&self,
|
||||
mel_spec: &Array2<f32>,
|
||||
) -> Array2<f32> {
|
||||
let mut log_spec = mel_spec.mapv(|x| x.max(1e-10).log10());
|
||||
log_spec =
|
||||
log_spec.mapv(|x| x.max(log_spec.fold(f32::NEG_INFINITY, |acc, &v| acc.max(v)) - 8.0));
|
||||
log_spec = (log_spec + 4.0) / 4.0;
|
||||
log_spec
|
||||
}
|
||||
}
|
||||
-4732
File diff suppressed because it is too large
Load Diff
@@ -1,49 +0,0 @@
|
||||
[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"
|
||||
publish = false
|
||||
|
||||
[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", "cuda"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
ort = { version = "2.0.0-rc.10", features = ["ndarray", "cuda"] }
|
||||
@@ -1,5 +0,0 @@
|
||||
const COMMANDS: &[&str] = &["load_ort_model_silero_vad", "ipc_audio_vad"];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS).build();
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
# 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
@@ -1,13 +0,0 @@
|
||||
# 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"]
|
||||
@@ -1,75 +0,0 @@
|
||||
## 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>
|
||||
@@ -1,15 +0,0 @@
|
||||
"$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"
|
||||
]
|
||||
@@ -1,330 +0,0 @@
|
||||
{
|
||||
"$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`"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod huggingface;
|
||||
@@ -1,86 +0,0 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
use log::info;
|
||||
use tauri::{
|
||||
Manager,
|
||||
Runtime,
|
||||
plugin::{Builder as PluginBuilder, TauriPlugin},
|
||||
};
|
||||
|
||||
mod helpers;
|
||||
mod models;
|
||||
|
||||
use crate::models::{
|
||||
new_silero_vad_processor,
|
||||
silero_vad::{VADInferenceInput, VADInferenceResult},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
struct AppDataSileroVadProcessor {
|
||||
silero_vad_processor: Option<crate::models::silero_vad::Processor>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn load_ort_model_silero_vad<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
window: tauri::WebviewWindow<R>,
|
||||
) -> Result<(), String> {
|
||||
info!("Loading models...");
|
||||
|
||||
{
|
||||
let data = app.state::<Mutex<AppDataSileroVadProcessor>>();
|
||||
let data = data.lock().unwrap();
|
||||
if data.silero_vad_processor.is_some() {
|
||||
info!("Silero VAD model already loaded, skipping...");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
match new_silero_vad_processor(window) {
|
||||
Ok(p) => {
|
||||
let data = app.state::<Mutex<AppDataSileroVadProcessor>>();
|
||||
let mut data = data.lock().unwrap();
|
||||
data.silero_vad_processor = Some(p);
|
||||
info!("Silero VAD model loaded successfully");
|
||||
},
|
||||
Err(e) => {
|
||||
let error_message = format!("Failed to load Silero VAD model: {}", e);
|
||||
info!("{}", error_message);
|
||||
return Err(error_message);
|
||||
},
|
||||
}
|
||||
|
||||
info!("All models loaded successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn ipc_audio_vad<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
input_data: VADInferenceInput,
|
||||
) -> Result<VADInferenceResult, String> {
|
||||
let data = app.state::<Mutex<AppDataSileroVadProcessor>>();
|
||||
let data = data.lock().unwrap();
|
||||
|
||||
if let Some(processor) = &data.silero_vad_processor {
|
||||
processor
|
||||
.inference(input_data)
|
||||
.map_err(|e| e.to_string())
|
||||
} else {
|
||||
Err("Silero VAD model is not loaded".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
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_ort_model_silero_vad,
|
||||
ipc_audio_vad
|
||||
])
|
||||
.build()
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
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,149 +0,0 @@
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use hf_hub::Repo;
|
||||
use log::info;
|
||||
use ort::{
|
||||
execution_providers::{
|
||||
CPUExecutionProvider,
|
||||
CUDAExecutionProvider,
|
||||
CoreMLExecutionProvider,
|
||||
DirectMLExecutionProvider,
|
||||
},
|
||||
session::{Session, builder::GraphOptimizationLevel},
|
||||
util::Mutex,
|
||||
value::Tensor,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::Runtime;
|
||||
|
||||
use crate::helpers::huggingface::create_progress_emitter;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct VADInferenceResult {
|
||||
pub output: Vec<f32>, // Speech probability output
|
||||
pub state: Vec<f32>, // Updated state for next inference
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct VADInferenceInput {
|
||||
pub input: Vec<f32>, // Audio input buffer
|
||||
pub sr: i64, // Sample rate
|
||||
pub state: Vec<f32>, // Current state
|
||||
}
|
||||
|
||||
pub struct Processor {
|
||||
session: Arc<Mutex<Session>>,
|
||||
}
|
||||
|
||||
impl Processor {
|
||||
pub fn new<R: Runtime>(window: tauri::WebviewWindow<R>) -> Result<Self> {
|
||||
let model_id = "onnx-community/silero-vad";
|
||||
let revision = "main";
|
||||
|
||||
let cache_api = hf_hub::Cache::from_env();
|
||||
let cache_repo = cache_api.repo(Repo::with_revision(
|
||||
model_id.into(),
|
||||
hf_hub::RepoType::Model,
|
||||
revision.into(),
|
||||
));
|
||||
|
||||
let api = hf_hub::api::sync::ApiBuilder::new().build()?;
|
||||
let repo = api.repo(hf_hub::Repo::with_revision(
|
||||
model_id.into(),
|
||||
hf_hub::RepoType::Model,
|
||||
revision.into(),
|
||||
));
|
||||
|
||||
let model_path_sub_name = "onnx/model.onnx";
|
||||
let model_path = match cache_repo.get(model_path_sub_name) {
|
||||
Some(path) => path,
|
||||
None => repo.download_with_progress(
|
||||
model_path_sub_name,
|
||||
create_progress_emitter(
|
||||
window.clone(),
|
||||
"tauri-plugins:tauri-plugin-ipc-audio-vad-ort:load-model-silero-vad-progress",
|
||||
model_path_sub_name.to_string(),
|
||||
),
|
||||
)?,
|
||||
};
|
||||
|
||||
let session = Self::create_optimized_session(model_path.clone())?;
|
||||
|
||||
Ok(Self {
|
||||
session: Arc::new(Mutex::new(session)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Create an optimized ONNX session with hardware acceleration
|
||||
fn create_optimized_session(model_path: PathBuf) -> Result<Session> {
|
||||
let builder = Session::builder()?
|
||||
.with_optimization_level(GraphOptimizationLevel::Level3)?
|
||||
.with_intra_threads(1)?;
|
||||
|
||||
let session = builder
|
||||
.with_execution_providers(vec![
|
||||
CUDAExecutionProvider::default()
|
||||
.with_device_id(0)
|
||||
.build(),
|
||||
CoreMLExecutionProvider::default().build(),
|
||||
DirectMLExecutionProvider::default()
|
||||
.with_device_id(0)
|
||||
.build(),
|
||||
CPUExecutionProvider::default().build(),
|
||||
])?
|
||||
.commit_from_file(model_path)?;
|
||||
info!("VAD model loaded successfully");
|
||||
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
/// Stateless inference that matches JavaScript interface
|
||||
/// Returns both output and updated state like the JS version
|
||||
pub fn inference(
|
||||
&self,
|
||||
input_data: VADInferenceInput,
|
||||
) -> Result<VADInferenceResult> {
|
||||
// Validate input dimensions
|
||||
if input_data.state.len() != 2 * 1 * 128 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"State must have 256 elements (2*1*128), got {}",
|
||||
input_data.state.len()
|
||||
));
|
||||
}
|
||||
|
||||
// Create input tensors for the ONNX model
|
||||
let inputs = vec![
|
||||
(
|
||||
"input",
|
||||
Tensor::from_array((vec![1, input_data.input.len()], input_data.input.clone()))?.into_dyn(),
|
||||
),
|
||||
(
|
||||
"sr",
|
||||
Tensor::from_array(([1], vec![input_data.sr]))?.into_dyn(),
|
||||
),
|
||||
(
|
||||
"state",
|
||||
Tensor::from_array((vec![2, 1, 128], input_data.state.clone()))?.into_dyn(),
|
||||
),
|
||||
];
|
||||
|
||||
// Run inference and extract data while session is still locked
|
||||
let (state_data, speech_data) = {
|
||||
let mut session = self.session.lock();
|
||||
let outputs = session.run(inputs)?;
|
||||
|
||||
// Extract and clone the data immediately while session is locked
|
||||
let (_state_shape, state_slice) = outputs[1].try_extract_tensor::<f32>()?;
|
||||
let (_speech_shape, speech_slice) = outputs[0].try_extract_tensor::<f32>()?;
|
||||
|
||||
// Clone the data to owned vectors before the session lock is released
|
||||
(state_slice.to_vec(), speech_slice.to_vec())
|
||||
};
|
||||
|
||||
Ok(VADInferenceResult {
|
||||
output: speech_data,
|
||||
state: state_data,
|
||||
})
|
||||
}
|
||||
}
|
||||
Generated
-4732
File diff suppressed because it is too large
Load Diff
@@ -1,33 +0,0 @@
|
||||
[package]
|
||||
name = "tauri-plugin-mcp"
|
||||
version.workspace = true
|
||||
description = "A Tauri plugin for interacting with MCP servers"
|
||||
authors = [ "LemonNekoGH <self@lemonneko.moe>" ]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/moeru-ai/airi"
|
||||
edition = "2021"
|
||||
rust-version = "1.89.0"
|
||||
links = "tauri-plugin-mcp"
|
||||
|
||||
[lib]
|
||||
name = "tauri_plugin_mcp"
|
||||
crate-type = [
|
||||
"staticlib",
|
||||
"cdylib",
|
||||
"rlib"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
tauri = "2.3.1"
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
log = "0.4"
|
||||
rmcp = { version = "0.1", features = [
|
||||
"client",
|
||||
"transport-child-process"
|
||||
] }
|
||||
tokio = { version = "1", features = [ "full" ] }
|
||||
anyhow = "1"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { version = "2.2", features = [ "build" ] }
|
||||
@@ -1,104 +0,0 @@
|
||||
# `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
|
||||
@@ -1,10 +0,0 @@
|
||||
const COMMANDS: &[&str] = &[
|
||||
"connect_server",
|
||||
"disconnect_server",
|
||||
"list_tools",
|
||||
"call_tool",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS).build();
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-call-tool"
|
||||
description = "Enables the call_tool command without any pre-configured scope."
|
||||
commands.allow = ["call_tool"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-call-tool"
|
||||
description = "Denies the call_tool command without any pre-configured scope."
|
||||
commands.deny = ["call_tool"]
|
||||
@@ -1,13 +0,0 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-connect-server"
|
||||
description = "Enables the connect_server command without any pre-configured scope."
|
||||
commands.allow = ["connect_server"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-connect-server"
|
||||
description = "Denies the connect_server command without any pre-configured scope."
|
||||
commands.deny = ["connect_server"]
|
||||
@@ -1,13 +0,0 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-disconnect-server"
|
||||
description = "Enables the disconnect_server command without any pre-configured scope."
|
||||
commands.allow = ["disconnect_server"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-disconnect-server"
|
||||
description = "Denies the disconnect_server command without any pre-configured scope."
|
||||
commands.deny = ["disconnect_server"]
|
||||
@@ -1,13 +0,0 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-list-tools"
|
||||
description = "Enables the list_tools command without any pre-configured scope."
|
||||
commands.allow = ["list_tools"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-list-tools"
|
||||
description = "Denies the list_tools command without any pre-configured scope."
|
||||
commands.deny = ["list_tools"]
|
||||
@@ -1,129 +0,0 @@
|
||||
## 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-connect-server`
|
||||
- `allow-disconnect-server`
|
||||
- `allow-list-tools`
|
||||
- `allow-call-tool`
|
||||
|
||||
## Permission Table
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Identifier</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`mcp:allow-call-tool`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the call_tool command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`mcp:deny-call-tool`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the call_tool command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`mcp:allow-connect-server`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the connect_server command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`mcp:deny-connect-server`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the connect_server command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`mcp:allow-disconnect-server`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the disconnect_server command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`mcp:deny-disconnect-server`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the disconnect_server command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`mcp:allow-list-tools`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the list_tools command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`mcp:deny-list-tools`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the list_tools command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -1,17 +0,0 @@
|
||||
"$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-connect-server",
|
||||
"allow-disconnect-server",
|
||||
"allow-list-tools",
|
||||
"allow-call-tool"
|
||||
]
|
||||
@@ -1,354 +0,0 @@
|
||||
{
|
||||
"$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 call_tool command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-call-tool",
|
||||
"markdownDescription": "Enables the call_tool command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the call_tool command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-call-tool",
|
||||
"markdownDescription": "Denies the call_tool command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the connect_server command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-connect-server",
|
||||
"markdownDescription": "Enables the connect_server command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the connect_server command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-connect-server",
|
||||
"markdownDescription": "Denies the connect_server command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the disconnect_server command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-disconnect-server",
|
||||
"markdownDescription": "Enables the disconnect_server command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the disconnect_server command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-disconnect-server",
|
||||
"markdownDescription": "Denies the disconnect_server command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the list_tools command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-list-tools",
|
||||
"markdownDescription": "Enables the list_tools command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the list_tools command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-list-tools",
|
||||
"markdownDescription": "Denies the list_tools 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-connect-server`\n- `allow-disconnect-server`\n- `allow-list-tools`\n- `allow-call-tool`",
|
||||
"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-connect-server`\n- `allow-disconnect-server`\n- `allow-list-tools`\n- `allow-call-tool`"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
use std::process::Stdio;
|
||||
|
||||
use log::info;
|
||||
use rmcp::{
|
||||
model::{CallToolRequestParam, CallToolResult, Tool},
|
||||
service::RunningService,
|
||||
transport::TokioChildProcess,
|
||||
RoleClient,
|
||||
ServiceExt,
|
||||
};
|
||||
use serde_json::{Map, Value};
|
||||
use tauri::{
|
||||
plugin::{self, TauriPlugin},
|
||||
AppHandle,
|
||||
Manager,
|
||||
Runtime,
|
||||
State,
|
||||
};
|
||||
use tokio::{process::Command, sync::Mutex};
|
||||
|
||||
pub struct McpState {
|
||||
pub client: Option<RunningService<RoleClient, ()>>,
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
pub fn destroy<R: Runtime>(app_handle: &AppHandle<R>) {
|
||||
info!("Destroying MCP plugin");
|
||||
|
||||
tokio::runtime::Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(async {
|
||||
let state = app_handle.state::<Mutex<McpState>>();
|
||||
|
||||
let mut state = state.lock().await;
|
||||
if state.client.is_none() {
|
||||
info!("MCP plugin not connected, no need to disconnect");
|
||||
return;
|
||||
}
|
||||
|
||||
let client = state.client.take().unwrap();
|
||||
drop(state);
|
||||
|
||||
client.cancel().await.unwrap();
|
||||
// client.waiting().await.unwrap();
|
||||
});
|
||||
|
||||
info!("MCP plugin destroyed");
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn connect_server(
|
||||
state: State<'_, Mutex<McpState>>,
|
||||
command: String,
|
||||
args: Vec<String>,
|
||||
) -> Result<(), String> {
|
||||
let mut state = state.lock().await;
|
||||
|
||||
if state.client.is_some() {
|
||||
return Err("Client already connected".to_string());
|
||||
}
|
||||
|
||||
let child_process = TokioChildProcess::new(
|
||||
Command::new(command)
|
||||
.args(args)
|
||||
.stderr(Stdio::inherit())
|
||||
.stdout(Stdio::inherit()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let service: RunningService<RoleClient, ()> = ().serve(child_process).await.unwrap();
|
||||
|
||||
state.client = Some(service);
|
||||
drop(state);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn disconnect_server(state: State<'_, Mutex<McpState>>) -> Result<(), String> {
|
||||
let mut state = state.lock().await;
|
||||
if state.client.is_none() {
|
||||
return Err("Client not connected".to_string());
|
||||
}
|
||||
|
||||
let cancel_result = state.client.take().unwrap().cancel().await;
|
||||
info!("Cancel result: {cancel_result:?}");
|
||||
// state.client.take().unwrap().waiting().await.unwrap();
|
||||
drop(state);
|
||||
|
||||
info!("Disconnected from MCP server");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn list_tools(state: State<'_, Mutex<McpState>>) -> Result<Vec<Tool>, String> {
|
||||
let state = state.lock().await;
|
||||
let client = state.client.as_ref();
|
||||
if client.is_none() {
|
||||
return Err("Client not connected".to_string());
|
||||
}
|
||||
|
||||
let list_tools_result = client
|
||||
.unwrap()
|
||||
.list_tools(Option::default())
|
||||
.await
|
||||
.unwrap(); // TODO: handle error
|
||||
let tools = list_tools_result.tools;
|
||||
drop(state);
|
||||
|
||||
Ok(tools)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn call_tool(
|
||||
state: State<'_, Mutex<McpState>>,
|
||||
name: String,
|
||||
args: Option<Map<String, Value>>,
|
||||
) -> Result<CallToolResult, String> {
|
||||
info!("Calling tool: {name:?}");
|
||||
info!("Arguments: {args:?}");
|
||||
|
||||
let state = state.lock().await;
|
||||
let client = state.client.as_ref();
|
||||
if client.is_none() {
|
||||
return Err("Client not connected".to_string());
|
||||
}
|
||||
|
||||
let call_tool_result = client
|
||||
.unwrap()
|
||||
.call_tool(CallToolRequestParam {
|
||||
name: name.into(),
|
||||
arguments: args,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
drop(state);
|
||||
|
||||
info!("Tool result: {call_tool_result:?}");
|
||||
|
||||
Ok(call_tool_result)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Builder;
|
||||
|
||||
impl Builder {
|
||||
#[must_use]
|
||||
pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
|
||||
info!("Building MCP plugin");
|
||||
|
||||
plugin::Builder::new("mcp")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
connect_server,
|
||||
disconnect_server,
|
||||
list_tools,
|
||||
call_tool
|
||||
])
|
||||
.setup(|app_handle, _| {
|
||||
app_handle.manage(Mutex::new(McpState { client: None }));
|
||||
Ok(())
|
||||
})
|
||||
.on_drop(|app_handle: AppHandle<R>| {
|
||||
destroy(&app_handle);
|
||||
})
|
||||
.build()
|
||||
}
|
||||
}
|
||||
Generated
-4732
File diff suppressed because it is too large
Load Diff
@@ -1,36 +0,0 @@
|
||||
[package]
|
||||
name = "tauri-plugin-rdev"
|
||||
version.workspace = true
|
||||
description = "Integration of github.com/Narsil/rdev 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-rdev"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "tauri_plugin_rdev"
|
||||
crate-type = [
|
||||
"staticlib",
|
||||
"cdylib",
|
||||
"rlib"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
tauri = "2.3.1"
|
||||
tokio = { version = "1", features = [ "full" ] }
|
||||
anyhow = "1"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.142"
|
||||
log = "0.4.27"
|
||||
|
||||
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||
rdev = { git = "https://github.com/Narsil/rdev", version = "0.6.0", features = ["serialize"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
rdev = { git = "https://github.com/Narsil/rdev", version = "0.6.0", features = ["serialize", "wayland", "x11"] }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { version = "2.2", features = [ "build" ] }
|
||||
@@ -1 +0,0 @@
|
||||
fn main() {}
|
||||
@@ -1,125 +0,0 @@
|
||||
use std::sync::{atomic::Ordering, Mutex};
|
||||
|
||||
use log::error;
|
||||
use rdev::{listen, Event, EventType};
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use tauri::{
|
||||
plugin::{Builder as PluginBuilder, TauriPlugin},
|
||||
Emitter, Manager, Runtime,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub enum DeviceKind {
|
||||
MousePress,
|
||||
MouseRelease,
|
||||
MouseMove,
|
||||
KeyboardPress,
|
||||
KeyboardRelease,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct DeviceEvent {
|
||||
kind: DeviceKind,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct PluginState {
|
||||
window_labels: Vec<String>,
|
||||
}
|
||||
|
||||
const PLUGIN_NAME: &str = "rdev";
|
||||
|
||||
// Code referenced from
|
||||
// https://github.com/liwenka1/bongo-cat-next/blob/891c3380e1d4a8cfe8452668632be8bd5086b46c/src-tauri/src/core/device.rs#L7
|
||||
static IS_RUNNING: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
fn start_listen<R: tauri::Runtime>(app: tauri::AppHandle<R>) {
|
||||
#[cfg(target_os = "macos")]
|
||||
rdev::set_is_main_thread(false);
|
||||
|
||||
// Events needed by
|
||||
// https://github.com/vueuse/vueuse/blob/f8c4e0a128e2d8df52cca0299a08bfd4dd8638da/packages/core/useMagicKeys/index.ts#L153C3-L153C19
|
||||
_ = listen(move |event: Event| {
|
||||
let event_name = match event.event_type {
|
||||
EventType::KeyPress(_) => "tauri-plugins:tauri-plugin-rdev:keydown",
|
||||
EventType::KeyRelease(_) => "tauri-plugins:tauri-plugin-rdev:keyup",
|
||||
EventType::ButtonPress(_) => "tauri-plugins:tauri-plugin-rdev:mousedown",
|
||||
EventType::ButtonRelease(_) => "tauri-plugins:tauri-plugin-rdev:mouseup",
|
||||
EventType::MouseMove { .. } => "tauri-plugins:tauri-plugin-rdev:mousemove",
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let state = app.state::<Mutex<PluginState>>();
|
||||
let state = match state.lock() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
error!("PluginState mutex is poisoned: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for label in &state.window_labels {
|
||||
if let Err(e) = app.emit_to(label, event_name, &event) {
|
||||
error!(
|
||||
"Failed to emit rdev event '{}' to window '{}': {}",
|
||||
event_name, label, e
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
.map_err(|e| {
|
||||
error!("Failed to start rdev listener: {:?}", e);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
PluginBuilder::new(PLUGIN_NAME)
|
||||
.setup(|app, _| {
|
||||
app.manage(Mutex::new(PluginState {
|
||||
window_labels: vec![],
|
||||
}));
|
||||
|
||||
// Code referenced from
|
||||
// https://github.com/liwenka1/bongo-cat-next/blob/891c3380e1d4a8cfe8452668632be8bd5086b46c/src-tauri/src/core/device.rs#L7
|
||||
if IS_RUNNING.load(Ordering::SeqCst) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
IS_RUNNING.store(true, Ordering::SeqCst);
|
||||
|
||||
let app = app.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
start_listen(app);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.on_window_ready(|window| {
|
||||
let app = window.app_handle();
|
||||
let state = app.state::<Mutex<PluginState>>();
|
||||
let mut state = state.lock().unwrap();
|
||||
|
||||
state
|
||||
.window_labels
|
||||
.push(window.label().to_string());
|
||||
|
||||
let window_cloned = window.clone();
|
||||
|
||||
window.on_window_event(move |event| match event {
|
||||
tauri::WindowEvent::CloseRequested { .. } => {
|
||||
let app = window_cloned.app_handle();
|
||||
let state = app.state::<Mutex<PluginState>>();
|
||||
let mut state = state.lock().unwrap();
|
||||
|
||||
state
|
||||
.window_labels
|
||||
.retain(|label| label != window_cloned.label());
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
})
|
||||
.build()
|
||||
}
|
||||
-4732
File diff suppressed because it is too large
Load Diff
@@ -1,40 +0,0 @@
|
||||
[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"] }
|
||||
@@ -1,10 +0,0 @@
|
||||
const COMMANDS: &[&str] = &[
|
||||
"start_tracing_cursor",
|
||||
"stop_tracing_cursor",
|
||||
"start_pass_through",
|
||||
"stop_pass_through",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS).build();
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
# 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
@@ -1,13 +0,0 @@
|
||||
# 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
@@ -1,13 +0,0 @@
|
||||
# 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
@@ -1,13 +0,0 @@
|
||||
# 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
@@ -1,129 +0,0 @@
|
||||
## 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>
|
||||
@@ -1,17 +0,0 @@
|
||||
"$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"
|
||||
]
|
||||
@@ -1,354 +0,0 @@
|
||||
{
|
||||
"$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`"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
pub mod native_macos;
|
||||
pub mod native_windows;
|
||||
pub mod state;
|
||||
pub mod types;
|
||||
|
||||
use std::{sync::atomic::Ordering, time::Duration};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use native_macos::{get_mouse_location, get_window_frame};
|
||||
#[cfg(target_os = "windows")]
|
||||
use native_windows::{get_mouse_location, get_window_frame};
|
||||
#[cfg(debug_assertions)]
|
||||
use specta_typescript::Typescript;
|
||||
use state::{WindowClickThroughState, set_pass_through_enabled};
|
||||
use tauri::{
|
||||
Emitter,
|
||||
Manager,
|
||||
Runtime,
|
||||
plugin::{Builder as PluginBuilder, TauriPlugin},
|
||||
};
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[tauri::command]
|
||||
#[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();
|
||||
|
||||
// Already monitoring?
|
||||
if monitoring_enabled.load(Ordering::Relaxed) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Set to true
|
||||
state
|
||||
.monitoring_enabled
|
||||
.store(true, Ordering::Relaxed);
|
||||
|
||||
// Then start interval timer for monitoring
|
||||
tauri::async_runtime::spawn(async move {
|
||||
loop {
|
||||
sleep(Duration::from_millis(32)).await; // ~30FPS check rate
|
||||
|
||||
// If monitoring is already stopped, break the loop
|
||||
if !monitoring_enabled.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let _ = window.emit(
|
||||
"tauri-plugins:tauri-plugin-window-pass-through-on-hover:cursor-position",
|
||||
get_mouse_location(),
|
||||
);
|
||||
let _ = window.emit(
|
||||
"tauri-plugins:tauri-plugin-window-pass-through-on-hover:window-frame",
|
||||
get_window_frame(&window),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let _ = window.emit(
|
||||
"tauri-plugins:tauri-plugin-window-pass-through-on-hover:cursor-position",
|
||||
get_mouse_location(),
|
||||
);
|
||||
let _ = window.emit(
|
||||
"tauri-plugins:tauri-plugin-window-pass-through-on-hover:window-frame",
|
||||
get_window_frame(&window),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[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>();
|
||||
|
||||
// Set to false
|
||||
// Termination will be triggered in the next interval check (tick)
|
||||
state
|
||||
.monitoring_enabled
|
||||
.store(false, Ordering::Relaxed);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn start_pass_through<R: Runtime>(
|
||||
window: tauri::Window<R>
|
||||
) -> std::result::Result<(), String> {
|
||||
set_pass_through_enabled(&window, true).map_err(|e| {
|
||||
log::error!("Failed to enable click-through: {e}");
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn stop_pass_through<R: Runtime>(
|
||||
window: tauri::Window<R>
|
||||
) -> std::result::Result<(), String> {
|
||||
set_pass_through_enabled(&window, false).map_err(|e| {
|
||||
log::error!("Failed to disable click-through: {e}");
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
const PLUGIN_NAME: &str = "window-pass-through-on-hover";
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
let builder = tauri_specta::Builder::<R>::new()
|
||||
.plugin_name(PLUGIN_NAME)
|
||||
// ↓ HACKY WARNING
|
||||
// ↓ Below is a modified version from the expansion of `tauri_specta::collect_commands!`,
|
||||
// ↓ 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_tracing_cursor,
|
||||
stop_tracing_cursor,
|
||||
start_pass_through,
|
||||
stop_pass_through
|
||||
],
|
||||
specta::function::collect_functions![
|
||||
start_pass_through::<tauri::Wry>,
|
||||
// ^^^^^^^^^^
|
||||
// TODO: We have to specify the runtime type here. This is a known issue:
|
||||
// - https://github.com/specta-rs/tauri-specta/issues/70
|
||||
// - https://github.com/specta-rs/tauri-specta/issues/162
|
||||
stop_pass_through::<tauri::Wry>,
|
||||
]
|
||||
));
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
builder
|
||||
.export(
|
||||
Typescript::default().header("// @ts-nocheck\n"),
|
||||
"../src/bindings/tauri-plugins/window-pass-through-on-hover.ts",
|
||||
)
|
||||
.expect("Failed to export typescript bindings");
|
||||
|
||||
PluginBuilder::new(PLUGIN_NAME)
|
||||
.setup(|app, _| {
|
||||
app.manage(WindowClickThroughState::default());
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(builder.invoke_handler())
|
||||
.build()
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
use objc2::{class, msg_send};
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::Runtime;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use super::types::{Point, Size, WindowFrame};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn get_window_frame<R: Runtime>(window: &tauri::Window<R>) -> WindowFrame {
|
||||
use objc2_foundation::NSRect;
|
||||
|
||||
unsafe {
|
||||
// Get window position and size from Tauri
|
||||
// Get the NSWindow from Tauri window to access native properties
|
||||
//
|
||||
// While Tauri does provide us window.outer_position() and window.outer_size()
|
||||
// it's not enough to determine if the cursor is inside the window.
|
||||
// The value of those two functions returns the in-compatible coordinates (top-left origin)
|
||||
// with OBJ-C Native API values. This produces a lot of issues when calculating the collision
|
||||
// of the cursor relative to the window.
|
||||
//
|
||||
// We need to get the window's frame in macOS coordinates (bottom-left origin)
|
||||
// and check if the cursor is inside that frame.
|
||||
//
|
||||
// For multiple-display users,
|
||||
// in macOS, the Native API returns the mouse coordinates relative to the primary
|
||||
// display, for example, if we say the primary display is 1920x1080, another two lies
|
||||
// on both sides of the primary display with the size of 1920x1080 too, mouse coordinates
|
||||
// will be 0 at the left edge of p-display, and 1920 at the right edge of the p-display,
|
||||
// -1080 at the left edge of the left-side display, and 2160 at the right edge of the right-side
|
||||
// display.
|
||||
let ns_window: *mut objc2::runtime::AnyObject = window.ns_window().unwrap().cast();
|
||||
let window_frame: NSRect = msg_send![ns_window, frame];
|
||||
WindowFrame {
|
||||
origin: Point {
|
||||
x: window_frame.origin.x,
|
||||
y: window_frame.origin.y,
|
||||
},
|
||||
size: Size {
|
||||
width: window_frame.size.width,
|
||||
height: window_frame.size.height,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn get_mouse_location() -> Point {
|
||||
use objc2_foundation::NSPoint;
|
||||
|
||||
unsafe {
|
||||
// Get cursor position in screen coordinates (macOS coordinates - origin at bottom left)
|
||||
let mouse_location: NSPoint = msg_send![class!(NSEvent), mouseLocation];
|
||||
Point {
|
||||
x: mouse_location.x,
|
||||
y: mouse_location.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
#[cfg(target_os = "windows")]
|
||||
use tauri::Runtime;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use super::types::{Point, Size, WindowFrame};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn get_window_frame<R: Runtime>(window: &tauri::Window<R>) -> WindowFrame {
|
||||
use windows::Win32::{Foundation::RECT, UI::WindowsAndMessaging::GetWindowRect};
|
||||
|
||||
unsafe {
|
||||
let hwnd = window.hwnd().unwrap();
|
||||
let mut rect = RECT::default();
|
||||
|
||||
// Get window rectangle
|
||||
if GetWindowRect(hwnd, &mut rect).is_ok() {
|
||||
// Return the coordinates as (left, top, right, bottom)
|
||||
return WindowFrame {
|
||||
origin: Point {
|
||||
x: rect.left.into(),
|
||||
y: rect.top.into(),
|
||||
},
|
||||
size: Size {
|
||||
width: (rect.right - rect.left).into(),
|
||||
height: (rect.bottom - rect.top).into(),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
WindowFrame {
|
||||
origin: Point { x: 0.0, y: 0.0 },
|
||||
size: Size {
|
||||
width: 0.0,
|
||||
height: 0.0,
|
||||
},
|
||||
} // Default if unable to get window frame
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn get_mouse_location() -> Point {
|
||||
use windows::Win32::{Foundation::POINT, UI::WindowsAndMessaging::GetCursorPos};
|
||||
|
||||
unsafe {
|
||||
let mut cursor_pos = POINT::default();
|
||||
|
||||
// Get cursor position in screen coordinates
|
||||
if GetCursorPos(&mut cursor_pos).is_ok() {
|
||||
return Point {
|
||||
x: cursor_pos.x.into(),
|
||||
y: cursor_pos.y.into(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Point { x: 0.0, y: 0.0 } // Default if unable to get cursor position
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
use tauri::{Emitter, Manager, Runtime};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WindowClickThroughState {
|
||||
pub monitoring_enabled: Arc<AtomicBool>,
|
||||
pub enabled: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
pub fn set_pass_through_enabled<R: Runtime>(
|
||||
window: &tauri::Window<R>,
|
||||
enabled: bool,
|
||||
) -> Result<(), String> {
|
||||
let state = window.state::<WindowClickThroughState>();
|
||||
|
||||
state.enabled.store(enabled, Ordering::Relaxed);
|
||||
|
||||
window
|
||||
.set_ignore_cursor_events(enabled)
|
||||
.map_err(|e| format!("Failed to set pass-through state: {e}"))?;
|
||||
|
||||
let _ = window.emit(
|
||||
"tauri-app:proj-airi:window-pass-through-on-hover:pass-through-enabled",
|
||||
enabled,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize)]
|
||||
pub struct Point {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize)]
|
||||
pub struct Size {
|
||||
pub width: f64,
|
||||
pub height: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize)]
|
||||
pub struct WindowFrame {
|
||||
pub origin: Point,
|
||||
pub size: Size,
|
||||
}
|
||||
-4732
File diff suppressed because it is too large
Load Diff
@@ -1,33 +0,0 @@
|
||||
[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" ] }
|
||||
@@ -1,104 +0,0 @@
|
||||
# `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
|
||||
@@ -1,5 +0,0 @@
|
||||
const COMMANDS: &[&str] = &["go"];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS).build();
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
# 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"]
|
||||
@@ -1,48 +0,0 @@
|
||||
## 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>
|
||||
@@ -1,14 +0,0 @@
|
||||
"$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"
|
||||
]
|
||||
@@ -1,318 +0,0 @@
|
||||
{
|
||||
"$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`"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
#[cfg(debug_assertions)]
|
||||
use specta_typescript::Typescript;
|
||||
use tauri::{
|
||||
Manager,
|
||||
Runtime,
|
||||
plugin::{Builder, TauriPlugin},
|
||||
webview::{PageLoadEvent, PageLoadPayload},
|
||||
};
|
||||
|
||||
pub type PageLoadHandler<R> =
|
||||
Option<Arc<dyn Fn(tauri::WebviewWindow<R>, PageLoadPayload) + Send + Sync>>;
|
||||
|
||||
pub type WindowCreator<R> = Box<
|
||||
dyn Fn(tauri::AppHandle<R>, PageLoadHandler<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>, PageLoadHandler<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,
|
||||
custom_page_load_handler: PageLoadHandler<R>,
|
||||
) -> 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, custom_page_load_handler)
|
||||
.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>>();
|
||||
|
||||
matcher.get_or_create_window(
|
||||
app_handle.clone(),
|
||||
&window_label,
|
||||
Some(Arc::new(
|
||||
move |target_window: tauri::WebviewWindow<R>, load| {
|
||||
match load.event() {
|
||||
PageLoadEvent::Finished => {
|
||||
let current_url = target_window
|
||||
.url()
|
||||
.map_err(|e| format!("Failed to get current URL: {}", e));
|
||||
if let Ok(mut current_url) = current_url {
|
||||
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;
|
||||
}
|
||||
|
||||
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
-3
@@ -278,7 +278,7 @@ pnpm dev:docs
|
||||
|
||||
### Publier
|
||||
|
||||
Merci de mettre à jour la version dans `Cargo.toml` après avoir exécuté `bumpp` :
|
||||
Exécutez `bumpp` pour mettre à jour la version du monorepo :
|
||||
|
||||
```shell
|
||||
npx bumpp --no-commit --no-tag
|
||||
@@ -326,7 +326,6 @@ npx bumpp --no-commit --no-tag
|
||||
- [WebAI : Chat vocal en temps réel](https://github.com/proj-airi/webai-realtime-voice-chat) : Exemple complet d’implémentation de la voix temps réel de ChatGPT avec VAD + STT + LLM + TTS
|
||||
- [`@proj-airi/drizzle-duckdb-wasm`](https://github.com/moeru-ai/airi/tree/main/packages/drizzle-duckdb-wasm/README.md) : Driver Drizzle ORM pour DuckDB WASM
|
||||
- [`@proj-airi/duckdb-wasm`](https://github.com/moeru-ai/airi/tree/main/packages/duckdb-wasm/README.md) : Wrapper facile à utiliser pour `@duckdb/duckdb-wasm`
|
||||
- [`tauri-plugin-mcp`](https://github.com/moeru-ai/airi/blob/main/crates/tauri-plugin-mcp/README.md) : Plugin Tauri pour interagir avec les serveurs MCP
|
||||
- [AIRI Factorio](https://github.com/moeru-ai/airi-factorio) : Permet à AIRI de jouer à Factorio
|
||||
- [Factorio RCON API](https://github.com/nekomeowww/factorio-rcon-api) : Wrapper RESTful pour la console headless de Factorio
|
||||
- [`autorio`](https://github.com/moeru-ai/airi-factorio/tree/main/packages/autorio) : Bibliothèque d’automatisation pour Factorio
|
||||
@@ -407,7 +406,6 @@ flowchart TD
|
||||
XSAI --> F_AGENT
|
||||
XSAI --> MC_AGENT
|
||||
|
||||
Core --> TauriMCP("@proj-airi/tauri-plugin-mcp")
|
||||
Memory_PGVector("@proj-airi/memory-pgvector") --> Memory
|
||||
|
||||
style Core fill:#f9d4d4,stroke:#333,stroke-width:1px
|
||||
|
||||
@@ -280,7 +280,7 @@ pnpm dev:docs
|
||||
|
||||
### リリース
|
||||
|
||||
`bumpp` 実行後は、`Cargo.toml` のバージョンも更新してください:
|
||||
`bumpp` を実行してモノレポのバージョンを更新してください:
|
||||
|
||||
```shell
|
||||
npx bumpp --no-commit --no-tag
|
||||
@@ -329,7 +329,6 @@ npx bumpp --no-commit --no-tag
|
||||
- [WebAI: Realtime Voice Chat](https://github.com/proj-airi/webai-realtime-voice-chat): VAD + STT + LLM + TTS で ChatGPT のリアルタイム音声をゼロから実装する完全例
|
||||
- [`@proj-airi/drizzle-duckdb-wasm`](https://github.com/moeru-ai/airi/tree/main/packages/drizzle-duckdb-wasm/README.md): DuckDB WASM の Drizzle ORM ドライバー
|
||||
- [`@proj-airi/duckdb-wasm`](https://github.com/moeru-ai/airi/tree/main/packages/duckdb-wasm/README.md): 使いやすい `@duckdb/duckdb-wasm` ラッパー
|
||||
- [`tauri-plugin-mcp`](https://github.com/moeru-ai/airi/blob/main/crates/tauri-plugin-mcp/README.md): MCPサーバーとやり取りするための Tauri プラグイン
|
||||
- [AIRI Factorio](https://github.com/moeru-ai/airi-factorio): AIRI が Factorio をプレイできるようにする
|
||||
- [Factorio RCON API](https://github.com/nekomeowww/factorio-rcon-api): Factorio ヘッドレスサーバーコンソールの RESTful API ラッパー
|
||||
- [`autorio`](https://github.com/moeru-ai/airi-factorio/tree/main/packages/autorio): Factorio 自動化ライブラリ
|
||||
@@ -410,7 +409,6 @@ flowchart TD
|
||||
XSAI --> F_AGENT
|
||||
XSAI --> MC_AGENT
|
||||
|
||||
Core --> TauriMCP("@proj-airi/tauri-plugin-mcp")
|
||||
Memory_PGVector("@proj-airi/memory-pgvector") --> Memory
|
||||
|
||||
style Core fill:#f9d4d4,stroke:#333,stroke-width:1px
|
||||
|
||||
@@ -313,7 +313,7 @@ pnpm dev:docs
|
||||
|
||||
### 배포
|
||||
|
||||
`bumpp` 실행 후 `Cargo.toml`의 버전도 업데이트하세요:
|
||||
`bumpp`를 실행해 모노레포 버전을 업데이트하세요:
|
||||
|
||||
```shell
|
||||
npx bumpp --no-commit --no-tag
|
||||
@@ -362,7 +362,6 @@ npx bumpp --no-commit --no-tag
|
||||
- [WebAI: Realtime Voice Chat](https://github.com/proj-airi/webai-realtime-voice-chat): VAD + STT + LLM + TTS로 ChatGPT 실시간 음성을 처음부터 구현한 완전한 예제
|
||||
- [`@proj-airi/drizzle-duckdb-wasm`](https://github.com/moeru-ai/airi/tree/main/packages/drizzle-duckdb-wasm/README.md): DuckDB WASM용 Drizzle ORM 드라이버
|
||||
- [`@proj-airi/duckdb-wasm`](https://github.com/moeru-ai/airi/tree/main/packages/duckdb-wasm/README.md): 사용하기 쉬운 `@duckdb/duckdb-wasm` 래퍼
|
||||
- [`tauri-plugin-mcp`](https://github.com/moeru-ai/airi/blob/main/crates/tauri-plugin-mcp/README.md): MCP 서버와 상호작용하기 위한 Tauri 플러그인
|
||||
- [AIRI Factorio](https://github.com/moeru-ai/airi-factorio): AIRI가 Factorio를 플레이할 수 있게 하는 프로젝트
|
||||
- [Factorio RCON API](https://github.com/nekomeowww/factorio-rcon-api): Factorio 헤드리스 서버 콘솔용 RESTful API 래퍼
|
||||
- [`autorio`](https://github.com/moeru-ai/airi-factorio/tree/main/packages/autorio): Factorio 자동화 라이브러리
|
||||
@@ -443,7 +442,6 @@ flowchart TD
|
||||
XSAI --> F_AGENT
|
||||
XSAI --> MC_AGENT
|
||||
|
||||
Core --> TauriMCP("@proj-airi/tauri-plugin-mcp")
|
||||
Memory_PGVector("@proj-airi/memory-pgvector") --> Memory
|
||||
|
||||
style Core fill:#f9d4d4,stroke:#333,stroke-width:1px
|
||||
|
||||
@@ -278,7 +278,7 @@ pnpm dev:docs
|
||||
|
||||
### Публикация
|
||||
|
||||
Пожалуйста, обновите версию в `Cargo.toml` после выполнения `bumpp`:
|
||||
Запустите `bumpp`, чтобы обновить версию монорепозитория:
|
||||
|
||||
```shell
|
||||
npx bumpp --no-commit --no-tag
|
||||
@@ -326,7 +326,6 @@ npx bumpp --no-commit --no-tag
|
||||
- [WebAI: Realtime Voice Chat](https://github.com/proj-airi/webai-realtime-voice-chat): Полный пример реализации реального времени голосового чата ChatGPT с нуля с VAD + STT + LLM + TTS.
|
||||
- [`@proj-airi/drizzle-duckdb-wasm`](https://github.com/moeru-ai/airi/tree/main/packages/drizzle-duckdb-wasm/README.md): Драйвер Drizzle ORM для DuckDB WASM
|
||||
- [`@proj-airi/duckdb-wasm`](https://github.com/moeru-ai/airi/tree/main/packages/duckdb-wasm/README.md): Простой обертка для `@duckdb/duckdb-wasm`
|
||||
- [`tauri-plugin-mcp`](https://github.com/moeru-ai/airi/blob/main/crates/tauri-plugin-mcp/README.md): Плагин Tauri для взаимодействия с серверами MCP.
|
||||
- [AIRI Factorio](https://github.com/moeru-ai/airi-factorio): Позволяет AIRI играть в Factorio
|
||||
- [Factorio RCON API](https://github.com/nekomeowww/factorio-rcon-api): RESTful API-обертка для консоли headless-сервера Factorio
|
||||
- [`autorio`](https://github.com/moeru-ai/airi-factorio/tree/main/packages/autorio): Библиотека автоматизации Factorio
|
||||
@@ -407,7 +406,6 @@ flowchart TD
|
||||
XSAI --> F_AGENT
|
||||
XSAI --> MC_AGENT
|
||||
|
||||
Core --> TauriMCP("@proj-airi/tauri-plugin-mcp")
|
||||
Memory_PGVector("@proj-airi/memory-pgvector") --> Memory
|
||||
|
||||
style Core fill:#f9d4d4,stroke:#333,stroke-width:1px
|
||||
|
||||
+1
-3
@@ -275,7 +275,7 @@ pnpm dev:docs
|
||||
|
||||
### Xuất bản
|
||||
|
||||
Vui lòng cập nhật phiên bản trong `Cargo.toml` sau khi chạy `bumpp`:
|
||||
Chạy `bumpp` để cập nhật phiên bản monorepo:
|
||||
|
||||
```shell
|
||||
npx bumpp --no-commit --no-tag
|
||||
@@ -323,7 +323,6 @@ npx bumpp --no-commit --no-tag
|
||||
- [WebAI: Realtime Voice Chat](https://github.com/proj-airi/webai-realtime-voice-chat): Ví dụ đầy đủ về việc triển khai voice realtime của ChatGPT từ đầu với VAD + STT + LLM + TTS.
|
||||
- [`@proj-airi/drizzle-duckdb-wasm`](https://github.com/moeru-ai/airi/tree/main/packages/drizzle-duckdb-wasm/README.md): Trình điều khiển Drizzle ORM cho DuckDB WASM
|
||||
- [`@proj-airi/duckdb-wasm`](https://github.com/moeru-ai/airi/tree/main/packages/duckdb-wasm/README.md): Wrapper dễ sử dụng cho `@duckdb/duckdb-wasm`
|
||||
- [`tauri-plugin-mcp`](https://github.com/moeru-ai/airi/blob/main/crates/tauri-plugin-mcp/README.md): Plugin Tauri để tương tác với máy chủ MCP.
|
||||
- [AIRI Factorio](https://github.com/moeru-ai/airi-factorio): Cho phép AIRI chơi Factorio
|
||||
- [Factorio RCON API](https://github.com/nekomeowww/factorio-rcon-api): RESTful API wrapper cho console máy chủ headless Factorio
|
||||
- [`autorio`](https://github.com/moeru-ai/airi-factorio/tree/main/packages/autorio): Thư viện tự động hóa Factorio
|
||||
@@ -404,7 +403,6 @@ flowchart TD
|
||||
XSAI --> F_AGENT
|
||||
XSAI --> MC_AGENT
|
||||
|
||||
Core --> TauriMCP("@proj-airi/tauri-plugin-mcp")
|
||||
Memory_PGVector("@proj-airi/memory-pgvector") --> Memory
|
||||
|
||||
style Core fill:#f9d4d4,stroke:#333,stroke-width:1px
|
||||
|
||||
@@ -278,7 +278,7 @@ pnpm dev:docs
|
||||
|
||||
### 发布
|
||||
|
||||
运行 `bumpp` 后,请记得在 `Cargo.toml` 中更新版本号:
|
||||
运行 `bumpp` 来更新 monorepo 版本:
|
||||
|
||||
```shell
|
||||
npx bumpp --no-commit --no-tag
|
||||
@@ -327,7 +327,6 @@ npx bumpp --no-commit --no-tag
|
||||
- [WebAI: Realtime Voice Chat](https://github.com/proj-airi/webai-realtime-voice-chat):从零实现 ChatGPT 实时语音(VAD + STT + LLM + TTS)的完整示例
|
||||
- [`@proj-airi/drizzle-duckdb-wasm`](https://github.com/moeru-ai/airi/tree/main/packages/drizzle-duckdb-wasm/README.md):DuckDB WASM 的 Drizzle ORM 驱动
|
||||
- [`@proj-airi/duckdb-wasm`](https://github.com/moeru-ai/airi/tree/main/packages/duckdb-wasm/README.md):好用的 `@duckdb/duckdb-wasm` 封装
|
||||
- [`tauri-plugin-mcp`](https://github.com/moeru-ai/airi/blob/main/crates/tauri-plugin-mcp/README.md):与 MCP servers 交互的 Tauri 插件
|
||||
- [AIRI Factorio](https://github.com/moeru-ai/airi-factorio): 让 AIRI 玩 Factorio
|
||||
- [Factorio RCON API](https://github.com/nekomeowww/factorio-rcon-api): Factorio 无头服务器控制台的 RESTful API 封装
|
||||
- [`autorio`](https://github.com/moeru-ai/airi-factorio/tree/main/packages/autorio): Factorio 自动化库
|
||||
@@ -408,7 +407,6 @@ flowchart TD
|
||||
XSAI --> F_AGENT
|
||||
XSAI --> MC_AGENT
|
||||
|
||||
Core --> TauriMCP("@proj-airi/tauri-plugin-mcp")
|
||||
Memory_PGVector("@proj-airi/memory-pgvector") --> Memory
|
||||
|
||||
style Core fill:#f9d4d4,stroke:#333,stroke-width:1px
|
||||
|
||||
@@ -19,12 +19,10 @@ export default defineConfig({
|
||||
'**/assets/live2d/models/**',
|
||||
'apps/stage-tamagotchi/out/**',
|
||||
'apps/stage-tamagotchi/src/bindings/**',
|
||||
'apps/stage-tamagotchi/src-tauri/**',
|
||||
'apps/stage-tamagotchi-electron/out/**',
|
||||
'apps/stage-tamagotchi-electron/src/renderer/bindings/**',
|
||||
'apps/stage-pocket/ios/**',
|
||||
'apps/stage-pocket/android/**',
|
||||
'crates/**',
|
||||
'**/drizzle/**',
|
||||
'**/.astro/**',
|
||||
'.agents/**',
|
||||
|
||||
@@ -28,12 +28,10 @@
|
||||
"build:tamagotchi": "pnpm -rF @proj-airi/stage-tamagotchi run app:build",
|
||||
"build:apps": "turbo run build -F=\"./apps/*\"",
|
||||
"build:packages": "turbo run build -F=\"./packages/*\"",
|
||||
"build:crates": "cargo build --workspace",
|
||||
"test": "vitest --coverage",
|
||||
"test:run": "vitest run",
|
||||
"lint": "moeru-lint .",
|
||||
"lint:fix": "moeru-lint --fix .",
|
||||
"lint:rust": "cargo fmt --check && cargo clippy --workspace",
|
||||
"lint:swift": "pnpm -rF @proj-airi/stage-pocket run lint:swift",
|
||||
"to-avif": "tsx docs/scripts/avif.ts",
|
||||
"sponsors:generate": "sponsorkit --output-dir docs/content/public/assets/sponsors",
|
||||
@@ -67,7 +65,6 @@
|
||||
"publint": "^0.3.18",
|
||||
"rollup": "^4.60.0",
|
||||
"simple-git-hooks": "^2.13.1",
|
||||
"smol-toml": "^1.6.1",
|
||||
"sponsorkit": "^17.0.0",
|
||||
"sponsors-svg": "^0.3.0",
|
||||
"taze": "^19.10.0",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# tauri-plugin-mcp
|
||||
|
||||
[Docs here](../../crates/tauri-plugin-mcp/README.md)
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"name": "@proj-airi/tauri-plugin-mcp",
|
||||
"type": "module",
|
||||
"version": "0.9.0-beta.2",
|
||||
"description": "A Tauri plugin for interacting with MCP servers.",
|
||||
"author": {
|
||||
"name": "Moeru AI Project AIRI Team",
|
||||
"email": "airi@moeru.ai",
|
||||
"url": "https://github.com/moeru-ai"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/moeru-ai/airi.git",
|
||||
"directory": "packages/tauri-plugin-mcp"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"files": [
|
||||
"README.md",
|
||||
"dist",
|
||||
"package.json"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "pkgroll"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.10.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"pkgroll": "^2.27.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.mts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.mts"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
export interface ToolInputSchema {
|
||||
required: string[]
|
||||
title: string
|
||||
type: 'object'
|
||||
properties: Record<string, {
|
||||
title: string
|
||||
type: string
|
||||
default?: any
|
||||
}>
|
||||
}
|
||||
|
||||
export interface CallToolResult {
|
||||
content: {
|
||||
type: string
|
||||
text: string
|
||||
}[]
|
||||
isError: boolean
|
||||
}
|
||||
|
||||
export interface Tool {
|
||||
name: string
|
||||
description: string
|
||||
inputSchema: ToolInputSchema
|
||||
}
|
||||
|
||||
export async function connectServer(command: string, args: string[]) {
|
||||
await invoke('plugin:mcp|connect_server', { command, args })
|
||||
}
|
||||
|
||||
export async function disconnectServer() {
|
||||
await invoke('plugin:mcp|disconnect_server')
|
||||
}
|
||||
|
||||
export async function listTools(): Promise<Tool[]> {
|
||||
return await invoke('plugin:mcp|list_tools')
|
||||
}
|
||||
|
||||
export async function callTool(name: string, args: Record<string, unknown>): Promise<CallToolResult> {
|
||||
return await invoke('plugin:mcp|call_tool', { name, args })
|
||||
}
|
||||
Generated
-444
@@ -394,9 +394,6 @@ importers:
|
||||
simple-git-hooks:
|
||||
specifier: ^2.13.1
|
||||
version: 2.13.1
|
||||
smol-toml:
|
||||
specifier: ^1.6.1
|
||||
version: 1.6.1
|
||||
sponsorkit:
|
||||
specifier: ^17.0.0
|
||||
version: 17.0.0
|
||||
@@ -3109,16 +3106,6 @@ importers:
|
||||
|
||||
packages/stream-kit: {}
|
||||
|
||||
packages/tauri-plugin-mcp:
|
||||
dependencies:
|
||||
'@tauri-apps/api':
|
||||
specifier: ^2.10.1
|
||||
version: 2.10.1
|
||||
devDependencies:
|
||||
pkgroll:
|
||||
specifier: ^2.27.0
|
||||
version: 2.27.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
|
||||
packages/ui:
|
||||
dependencies:
|
||||
'@vueuse/core':
|
||||
@@ -4829,12 +4816,6 @@ packages:
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/aix-ppc64@0.26.0':
|
||||
resolution: {integrity: sha512-hj0sKNCQOOo2fgyII3clmJXP28VhgDfU5iy3GNHlWO76KG6N7x4D9ezH5lJtQTG+1J6MFDAJXC1qsI+W+LvZoA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.2':
|
||||
resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -4853,12 +4834,6 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm64@0.26.0':
|
||||
resolution: {integrity: sha512-DDnoJ5eoa13L8zPh87PUlRd/IyFaIKOlRbxiwcSbeumcJ7UZKdtuMCHa1Q27LWQggug6W4m28i4/O2qiQQ5NZQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -4877,12 +4852,6 @@ packages:
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.26.0':
|
||||
resolution: {integrity: sha512-C0hkDsYNHZkBtPxxDx177JN90/1MiCpvBNjz1f5yWJo1+5+c5zr8apjastpEG+wtPjo9FFtGG7owSsAxyKiHxA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.27.2':
|
||||
resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -4901,12 +4870,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.26.0':
|
||||
resolution: {integrity: sha512-bKDkGXGZnj0T70cRpgmv549x38Vr2O3UWLbjT2qmIkdIWcmlg8yebcFWoT9Dku7b5OV3UqPEuNKRzlNhjwUJ9A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.27.2':
|
||||
resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -4925,12 +4888,6 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-arm64@0.26.0':
|
||||
resolution: {integrity: sha512-6Z3naJgOuAIB0RLlJkYc81An3rTlQ/IeRdrU3dOea8h/PvZSgitZV+thNuIccw0MuK1GmIAnAmd5TrMZad8FTQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -4949,12 +4906,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.26.0':
|
||||
resolution: {integrity: sha512-OPnYj0zpYW0tHusMefyaMvNYQX5pNQuSsHFTHUBNp3vVXupwqpxofcjVsUx11CQhGVkGeXjC3WLjh91hgBG2xw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.27.2':
|
||||
resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -4973,12 +4924,6 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.26.0':
|
||||
resolution: {integrity: sha512-jix2fa6GQeZhO1sCKNaNMjfj5hbOvoL2F5t+w6gEPxALumkpOV/wq7oUBMHBn2hY2dOm+mEV/K+xfZy3mrsxNQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -4997,12 +4942,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.26.0':
|
||||
resolution: {integrity: sha512-tccJaH5xHJD/239LjbVvJwf6T4kSzbk6wPFerF0uwWlkw/u7HL+wnAzAH5GB2irGhYemDgiNTp8wJzhAHQ64oA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.2':
|
||||
resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5021,12 +4960,6 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm64@0.26.0':
|
||||
resolution: {integrity: sha512-IMJYN7FSkLttYyTbsbme0Ra14cBO5z47kpamo16IwggzzATFY2lcZAwkbcNkWiAduKrTgFJP7fW5cBI7FzcuNQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5045,12 +4978,6 @@ packages:
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.26.0':
|
||||
resolution: {integrity: sha512-JY8NyU31SyRmRpuc5W8PQarAx4TvuYbyxbPIpHAZdr/0g4iBr8KwQBS4kiiamGl2f42BBecHusYCsyxi7Kn8UQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.27.2':
|
||||
resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5069,12 +4996,6 @@ packages:
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.26.0':
|
||||
resolution: {integrity: sha512-XITaGqGVLgk8WOHw8We9Z1L0lbLFip8LyQzKYFKO4zFo1PFaaSKsbNjvkb7O8kEXytmSGRkYpE8LLVpPJpsSlw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.27.2':
|
||||
resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5093,12 +5014,6 @@ packages:
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.26.0':
|
||||
resolution: {integrity: sha512-MkggfbDIczStUJwq9wU7gQ7kO33d8j9lWuOCDifN9t47+PeI+9m2QVh51EI/zZQ1spZtFMC1nzBJ+qNGCjJnsg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.27.2':
|
||||
resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5117,12 +5032,6 @@ packages:
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.26.0':
|
||||
resolution: {integrity: sha512-fUYup12HZWAeccNLhQ5HwNBPr4zXCPgUWzEq2Rfw7UwqwfQrFZ0SR/JljaURR8xIh9t+o1lNUFTECUTmaP7yKA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.2':
|
||||
resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5141,12 +5050,6 @@ packages:
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.26.0':
|
||||
resolution: {integrity: sha512-MzRKhM0Ip+//VYwC8tialCiwUQ4G65WfALtJEFyU0GKJzfTYoPBw5XNWf0SLbCUYQbxTKamlVwPmcw4DgZzFxg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.2':
|
||||
resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5165,12 +5068,6 @@ packages:
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.26.0':
|
||||
resolution: {integrity: sha512-QhCc32CwI1I4Jrg1enCv292sm3YJprW8WHHlyxJhae/dVs+KRWkbvz2Nynl5HmZDW/m9ZxrXayHzjzVNvQMGQA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.2':
|
||||
resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5189,12 +5086,6 @@ packages:
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.26.0':
|
||||
resolution: {integrity: sha512-1D6vi6lfI18aNT1aTf2HV+RIlm6fxtlAp8eOJ4mmnbYmZ4boz8zYDar86sIYNh0wmiLJEbW/EocaKAX6Yso2fw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.27.2':
|
||||
resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5213,12 +5104,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.26.0':
|
||||
resolution: {integrity: sha512-rnDcepj7LjrKFvZkx+WrBv6wECeYACcFjdNPvVPojCPJD8nHpb3pv3AuR9CXgdnjH1O23btICj0rsp0L9wAnHA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.27.2':
|
||||
resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5231,12 +5116,6 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.26.0':
|
||||
resolution: {integrity: sha512-FSWmgGp0mDNjEXXFcsf12BmVrb+sZBBBlyh3LwB/B9ac3Kkc8x5D2WimYW9N7SUkolui8JzVnVlWh7ZmjCpnxw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5255,12 +5134,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.26.0':
|
||||
resolution: {integrity: sha512-0QfciUDFryD39QoSPUDshj4uNEjQhp73+3pbSAaxjV2qGOEDsM67P7KbJq7LzHoVl46oqhIhJ1S+skKGR7lMXA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.2':
|
||||
resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5273,12 +5146,6 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.26.0':
|
||||
resolution: {integrity: sha512-vmAK+nHhIZWImwJ3RNw9hX3fU4UGN/OqbSE0imqljNbUQC3GvVJ1jpwYoTfD6mmXmQaxdJY6Hn4jQbLGJKg5Yw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5297,12 +5164,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.26.0':
|
||||
resolution: {integrity: sha512-GPXF7RMkJ7o9bTyUsnyNtrFMqgM3X+uM/LWw4CeHIjqc32fm0Ir6jKDnWHpj8xHFstgWDUYseSABK9KCkHGnpg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.2':
|
||||
resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5315,12 +5176,6 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/openharmony-arm64@0.26.0':
|
||||
resolution: {integrity: sha512-nUHZ5jEYqbBthbiBksbmHTlbb5eElyVfs/s1iHQ8rLBq1eWsd5maOnDpCocw1OM8kFK747d1Xms8dXJHtduxSw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5339,12 +5194,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/sunos-x64@0.26.0':
|
||||
resolution: {integrity: sha512-TMg3KCTCYYaVO+R6P5mSORhcNDDlemUVnUbb8QkboUtOhb5JWKAzd5uMIMECJQOxHZ/R+N8HHtDF5ylzLfMiLw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/sunos-x64@0.27.2':
|
||||
resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5363,12 +5212,6 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-arm64@0.26.0':
|
||||
resolution: {integrity: sha512-apqYgoAUd6ZCb9Phcs8zN32q6l0ZQzQBdVXOofa6WvHDlSOhwCWgSfVQabGViThS40Y1NA4SCvQickgZMFZRlA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-arm64@0.27.2':
|
||||
resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5387,12 +5230,6 @@ packages:
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.26.0':
|
||||
resolution: {integrity: sha512-FGJAcImbJNZzLWu7U6WB0iKHl4RuY4TsXEwxJPl9UZLS47agIZuILZEX3Pagfw7I4J3ddflomt9f0apfaJSbaw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.27.2':
|
||||
resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5411,12 +5248,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.26.0':
|
||||
resolution: {integrity: sha512-WAckBKaVnmFqbEhbymrPK7M086DQMpL1XoRbpmN0iW8k5JSXjDRQBhcZNa0VweItknLq9eAeCL34jK7/CDcw7A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.27.2':
|
||||
resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -8443,15 +8274,6 @@ packages:
|
||||
'@rolldown/pluginutils@1.0.0-rc.9':
|
||||
resolution: {integrity: sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==}
|
||||
|
||||
'@rollup/plugin-alias@6.0.0':
|
||||
resolution: {integrity: sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
peerDependencies:
|
||||
rollup: '>=4.0.0'
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
|
||||
'@rollup/plugin-babel@5.3.1':
|
||||
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
@@ -8463,33 +8285,6 @@ packages:
|
||||
'@types/babel__core':
|
||||
optional: true
|
||||
|
||||
'@rollup/plugin-commonjs@29.0.0':
|
||||
resolution: {integrity: sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==}
|
||||
engines: {node: '>=16.0.0 || 14 >= 14.17'}
|
||||
peerDependencies:
|
||||
rollup: ^2.68.0||^3.0.0||^4.0.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
|
||||
'@rollup/plugin-dynamic-import-vars@2.1.5':
|
||||
resolution: {integrity: sha512-Mymi24fd9hlRifdZV/jYIFj1dn99F34imiYu3KzlAcgBcRi3i9SucgW/VRo5SQ9K4NuQ7dCep6pFWgNyhRdFHQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
|
||||
'@rollup/plugin-json@6.1.0':
|
||||
resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
|
||||
'@rollup/plugin-node-resolve@15.3.1':
|
||||
resolution: {integrity: sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -8499,15 +8294,6 @@ packages:
|
||||
rollup:
|
||||
optional: true
|
||||
|
||||
'@rollup/plugin-node-resolve@16.0.3':
|
||||
resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
rollup: ^2.78.0||^3.0.0||^4.0.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
|
||||
'@rollup/plugin-replace@2.4.2':
|
||||
resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==}
|
||||
peerDependencies:
|
||||
@@ -8973,9 +8759,6 @@ packages:
|
||||
peerDependencies:
|
||||
vue: ^2.7.0 || ^3.0.0
|
||||
|
||||
'@tauri-apps/api@2.10.1':
|
||||
resolution: {integrity: sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==}
|
||||
|
||||
'@tokenizer/token@0.3.0':
|
||||
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
|
||||
|
||||
@@ -10456,10 +10239,6 @@ packages:
|
||||
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
astring@1.9.0:
|
||||
resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==}
|
||||
hasBin: true
|
||||
|
||||
async-exit-hook@2.0.1:
|
||||
resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
@@ -12064,11 +11843,6 @@ packages:
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
esbuild@0.26.0:
|
||||
resolution: {integrity: sha512-3Hq7jri+tRrVWha+ZeIVhl4qJRha/XjRNSopvTsOaCvfPHrflTYTcUFcEjMKdxofsXXsdc4zjg5NOTnL4Gl57Q==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
esbuild@0.27.2:
|
||||
resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -12333,9 +12107,6 @@ packages:
|
||||
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
||||
estree-walker@0.6.1:
|
||||
resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==}
|
||||
|
||||
estree-walker@1.0.1:
|
||||
resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==}
|
||||
|
||||
@@ -13384,9 +13155,6 @@ packages:
|
||||
is-promise@4.0.0:
|
||||
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
|
||||
|
||||
is-reference@1.2.1:
|
||||
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
|
||||
|
||||
is-regexp@1.0.0:
|
||||
resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -14989,16 +14757,6 @@ packages:
|
||||
pkg-types@2.3.0:
|
||||
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
|
||||
|
||||
pkgroll@2.27.0:
|
||||
resolution: {integrity: sha512-Huw5ZRxWTWeQ0PbNNKdbkAl52bPMy009RXdB4u3qjb47AMEBLG5VVF0V6oOD+YYiZaHaFxvjEeX+A2T9ckaClQ==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: ^4.1 || ^5.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
platform@1.3.6:
|
||||
resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==}
|
||||
|
||||
@@ -15605,18 +15363,6 @@ packages:
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
|
||||
rollup-plugin-import-trace@1.0.1:
|
||||
resolution: {integrity: sha512-dWOKrdYba2BXDJh82kkuM4pAR3M6r7WFp6vbVxAgvLfug2WniMFAg2uZ5sNQ/8CoQWo5l2N5EXDG8+QClKk1YQ==}
|
||||
engines: {node: '>=20.20.0'}
|
||||
peerDependencies:
|
||||
rollup: ^3.0.0 || ^4.0.0
|
||||
vite: ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
rollup-plugin-visualizer@5.14.0:
|
||||
resolution: {integrity: sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -15630,9 +15376,6 @@ packages:
|
||||
rollup:
|
||||
optional: true
|
||||
|
||||
rollup-pluginutils@2.8.2:
|
||||
resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==}
|
||||
|
||||
rollup@2.80.0:
|
||||
resolution: {integrity: sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
@@ -19267,9 +19010,6 @@ snapshots:
|
||||
'@esbuild/aix-ppc64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/aix-ppc64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19279,9 +19019,6 @@ snapshots:
|
||||
'@esbuild/android-arm64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19291,9 +19028,6 @@ snapshots:
|
||||
'@esbuild/android-arm@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19303,9 +19037,6 @@ snapshots:
|
||||
'@esbuild/android-x64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19315,9 +19046,6 @@ snapshots:
|
||||
'@esbuild/darwin-arm64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19327,9 +19055,6 @@ snapshots:
|
||||
'@esbuild/darwin-x64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19339,9 +19064,6 @@ snapshots:
|
||||
'@esbuild/freebsd-arm64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19351,9 +19073,6 @@ snapshots:
|
||||
'@esbuild/freebsd-x64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19363,9 +19082,6 @@ snapshots:
|
||||
'@esbuild/linux-arm64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19375,9 +19091,6 @@ snapshots:
|
||||
'@esbuild/linux-arm@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19387,9 +19100,6 @@ snapshots:
|
||||
'@esbuild/linux-ia32@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19399,9 +19109,6 @@ snapshots:
|
||||
'@esbuild/linux-loong64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19411,9 +19118,6 @@ snapshots:
|
||||
'@esbuild/linux-mips64el@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19423,9 +19127,6 @@ snapshots:
|
||||
'@esbuild/linux-ppc64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19435,9 +19136,6 @@ snapshots:
|
||||
'@esbuild/linux-riscv64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19447,9 +19145,6 @@ snapshots:
|
||||
'@esbuild/linux-s390x@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19459,18 +19154,12 @@ snapshots:
|
||||
'@esbuild/linux-x64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19480,18 +19169,12 @@ snapshots:
|
||||
'@esbuild/netbsd-x64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19501,18 +19184,12 @@ snapshots:
|
||||
'@esbuild/openbsd-x64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openharmony-arm64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openharmony-arm64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19522,9 +19199,6 @@ snapshots:
|
||||
'@esbuild/sunos-x64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19534,9 +19208,6 @@ snapshots:
|
||||
'@esbuild/win32-arm64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19546,9 +19217,6 @@ snapshots:
|
||||
'@esbuild/win32-ia32@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -19558,9 +19226,6 @@ snapshots:
|
||||
'@esbuild/win32-x64@0.25.12':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.26.0':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.27.2':
|
||||
optional: true
|
||||
|
||||
@@ -22515,10 +22180,6 @@ snapshots:
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-rc.9': {}
|
||||
|
||||
'@rollup/plugin-alias@6.0.0(rollup@4.60.0)':
|
||||
optionalDependencies:
|
||||
rollup: 4.60.0
|
||||
|
||||
'@rollup/plugin-babel@5.3.1(@babel/core@7.29.0)(rollup@2.80.0)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
@@ -22528,34 +22189,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@rollup/plugin-commonjs@29.0.0(rollup@4.60.0)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.60.0)
|
||||
commondir: 1.0.1
|
||||
estree-walker: 2.0.2
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
is-reference: 1.2.1
|
||||
magic-string: 0.30.21
|
||||
picomatch: 4.0.3
|
||||
optionalDependencies:
|
||||
rollup: 4.60.0
|
||||
|
||||
'@rollup/plugin-dynamic-import-vars@2.1.5(rollup@4.60.0)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.60.0)
|
||||
astring: 1.9.0
|
||||
estree-walker: 2.0.2
|
||||
fast-glob: 3.3.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
rollup: 4.60.0
|
||||
|
||||
'@rollup/plugin-json@6.1.0(rollup@4.60.0)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.60.0)
|
||||
optionalDependencies:
|
||||
rollup: 4.60.0
|
||||
|
||||
'@rollup/plugin-node-resolve@15.3.1(rollup@2.80.0)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.3.0(rollup@2.80.0)
|
||||
@@ -22566,16 +22199,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
rollup: 2.80.0
|
||||
|
||||
'@rollup/plugin-node-resolve@16.0.3(rollup@4.60.0)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.60.0)
|
||||
'@types/resolve': 1.20.2
|
||||
deepmerge: 4.3.1
|
||||
is-module: 1.0.0
|
||||
resolve: 1.22.11
|
||||
optionalDependencies:
|
||||
rollup: 4.60.0
|
||||
|
||||
'@rollup/plugin-replace@2.4.2(rollup@2.80.0)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 3.1.0(rollup@2.80.0)
|
||||
@@ -22955,8 +22578,6 @@ snapshots:
|
||||
'@tanstack/virtual-core': 3.13.13
|
||||
vue: 3.5.30(typescript@5.9.3)
|
||||
|
||||
'@tauri-apps/api@2.10.1': {}
|
||||
|
||||
'@tokenizer/token@0.3.0': {}
|
||||
|
||||
'@tresjs/cientos@5.6.0(@tresjs/core@5.7.0(@types/three@0.183.1)(three@0.183.2)(vue@3.5.30(typescript@5.9.3)))(@types/three@0.183.1)(react@19.2.3)(three@0.183.2)(vue@3.5.30(typescript@5.9.3))':
|
||||
@@ -24990,8 +24611,6 @@ snapshots:
|
||||
|
||||
astral-regex@2.0.0: {}
|
||||
|
||||
astring@1.9.0: {}
|
||||
|
||||
async-exit-hook@2.0.1: {}
|
||||
|
||||
async-mutex@0.3.2:
|
||||
@@ -26524,35 +26143,6 @@ snapshots:
|
||||
'@esbuild/win32-ia32': 0.25.12
|
||||
'@esbuild/win32-x64': 0.25.12
|
||||
|
||||
esbuild@0.26.0:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.26.0
|
||||
'@esbuild/android-arm': 0.26.0
|
||||
'@esbuild/android-arm64': 0.26.0
|
||||
'@esbuild/android-x64': 0.26.0
|
||||
'@esbuild/darwin-arm64': 0.26.0
|
||||
'@esbuild/darwin-x64': 0.26.0
|
||||
'@esbuild/freebsd-arm64': 0.26.0
|
||||
'@esbuild/freebsd-x64': 0.26.0
|
||||
'@esbuild/linux-arm': 0.26.0
|
||||
'@esbuild/linux-arm64': 0.26.0
|
||||
'@esbuild/linux-ia32': 0.26.0
|
||||
'@esbuild/linux-loong64': 0.26.0
|
||||
'@esbuild/linux-mips64el': 0.26.0
|
||||
'@esbuild/linux-ppc64': 0.26.0
|
||||
'@esbuild/linux-riscv64': 0.26.0
|
||||
'@esbuild/linux-s390x': 0.26.0
|
||||
'@esbuild/linux-x64': 0.26.0
|
||||
'@esbuild/netbsd-arm64': 0.26.0
|
||||
'@esbuild/netbsd-x64': 0.26.0
|
||||
'@esbuild/openbsd-arm64': 0.26.0
|
||||
'@esbuild/openbsd-x64': 0.26.0
|
||||
'@esbuild/openharmony-arm64': 0.26.0
|
||||
'@esbuild/sunos-x64': 0.26.0
|
||||
'@esbuild/win32-arm64': 0.26.0
|
||||
'@esbuild/win32-ia32': 0.26.0
|
||||
'@esbuild/win32-x64': 0.26.0
|
||||
|
||||
esbuild@0.27.2:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.27.2
|
||||
@@ -26936,8 +26526,6 @@ snapshots:
|
||||
|
||||
estraverse@5.3.0: {}
|
||||
|
||||
estree-walker@0.6.1: {}
|
||||
|
||||
estree-walker@1.0.1: {}
|
||||
|
||||
estree-walker@2.0.2: {}
|
||||
@@ -28212,10 +27800,6 @@ snapshots:
|
||||
|
||||
is-promise@4.0.0: {}
|
||||
|
||||
is-reference@1.2.1:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
|
||||
is-regexp@1.0.0: {}
|
||||
|
||||
is-relative@0.1.3: {}
|
||||
@@ -30229,25 +29813,6 @@ snapshots:
|
||||
exsolve: 1.0.8
|
||||
pathe: 2.0.3
|
||||
|
||||
pkgroll@2.27.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
dependencies:
|
||||
'@rollup/plugin-alias': 6.0.0(rollup@4.60.0)
|
||||
'@rollup/plugin-commonjs': 29.0.0(rollup@4.60.0)
|
||||
'@rollup/plugin-dynamic-import-vars': 2.1.5(rollup@4.60.0)
|
||||
'@rollup/plugin-json': 6.1.0(rollup@4.60.0)
|
||||
'@rollup/plugin-node-resolve': 16.0.3(rollup@4.60.0)
|
||||
cjs-module-lexer: 2.2.0
|
||||
esbuild: 0.26.0
|
||||
magic-string: 0.30.21
|
||||
rollup: 4.60.0
|
||||
rollup-plugin-import-trace: 1.0.1(rollup@4.60.0)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
rollup-pluginutils: 2.8.2
|
||||
yaml: 2.8.3
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- vite
|
||||
|
||||
platform@1.3.6: {}
|
||||
|
||||
playwright-core@1.58.2: {}
|
||||
@@ -31043,11 +30608,6 @@ snapshots:
|
||||
'@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.9
|
||||
'@rolldown/binding-win32-x64-msvc': 1.0.0-rc.9
|
||||
|
||||
rollup-plugin-import-trace@1.0.1(rollup@4.60.0)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
optionalDependencies:
|
||||
rollup: 4.60.0
|
||||
vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
rollup-plugin-visualizer@5.14.0(rolldown@1.0.0-rc.9)(rollup@2.80.0):
|
||||
dependencies:
|
||||
open: 8.4.2
|
||||
@@ -31068,10 +30628,6 @@ snapshots:
|
||||
rolldown: 1.0.0-rc.9
|
||||
rollup: 4.60.0
|
||||
|
||||
rollup-pluginutils@2.8.2:
|
||||
dependencies:
|
||||
estree-walker: 0.6.1
|
||||
|
||||
rollup@2.80.0:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file
|
||||
|
||||
[toolchain]
|
||||
channel = "1.89.0"
|
||||
# https://rust-lang.github.io/rustup/concepts/components.html
|
||||
components = [
|
||||
"rustc",
|
||||
"cargo",
|
||||
"rustfmt",
|
||||
"rust-analyzer",
|
||||
"clippy"
|
||||
]
|
||||
targets = [
|
||||
"x86_64-apple-darwin",
|
||||
"aarch64-apple-darwin",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"x86_64-pc-windows-msvc"
|
||||
]
|
||||
profile = "minimal"
|
||||
Reference in New Issue
Block a user