chore(tauri-*,crates): now maintaining tauri and rust packages in another repo (#1533)

This commit is contained in:
Neko
2026-03-31 23:56:32 +08:00
committed by GitHub
parent 4e62f15090
commit 8991b541a3
96 changed files with 9 additions and 41358 deletions
-42
View File
@@ -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
-1
View File
@@ -9,7 +9,6 @@
"lokalise.i18n-ally",
"mikestead.dotenv",
"redhat.vscode-yaml",
"rust-lang.rust-analyzer",
"streetsidesoftware.code-spell-checker",
"usernamehw.errorlens",
"Vue.volar",
-12
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
-40
View File
@@ -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
+1 -3
View File
@@ -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()
}
-33
View File
@@ -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'])
},
})
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();
}
@@ -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"]
@@ -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"]
@@ -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"]
@@ -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)
}
@@ -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)
}
}
@@ -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
}
}
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();
}
@@ -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"]
@@ -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,
})
}
}
-4732
View File
File diff suppressed because it is too large Load Diff
-33
View File
@@ -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" ] }
-104
View File
@@ -1,104 +0,0 @@
# `tauri-plugin-mcp`
![Crates.io Version](https://img.shields.io/crates/v/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
-10
View File
@@ -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`"
}
]
}
}
}
-168
View File
@@ -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()
}
}
-4732
View File
File diff suppressed because it is too large Load Diff
-36
View File
@@ -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
View File
@@ -1 +0,0 @@
fn main() {}
-125
View File
@@ -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()
}
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();
}
@@ -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"]
@@ -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"]
@@ -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"]
@@ -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"]
@@ -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,
}
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`
![Crates.io Version](https://img.shields.io/crates/v/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
View File
@@ -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 dimplé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 dautomatisation 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
+1 -3
View File
@@ -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
+1 -3
View File
@@ -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
+1 -3
View File
@@ -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
View File
@@ -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
+1 -3
View File
@@ -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
-2
View File
@@ -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/**',
-3
View File
@@ -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",
-3
View File
@@ -1,3 +0,0 @@
# tauri-plugin-mcp
[Docs here](../../crates/tauri-plugin-mcp/README.md)
-45
View File
@@ -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"
}
}
}
-42
View File
@@ -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 })
}
-444
View File
@@ -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
-20
View File
@@ -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"